设计一个性能好的id系统,业内也有不少很好的实践。比如snowflake算法,它主要是解决在有分布式部署的前提下,如何高性能的生成全局性的ID的问题。解决的主要思路就是将数据的寻址信息/机器信息编码进ID中。这样也可以通过ID来直接匹配对应的work节点进行服务。比如我们在uid中带有服务器索引信息的话,那么就可以在IM server层直接解析出服务器地址,然后通过RPC的方式进行远程调用。
最简单的方式当然是把server都存在一个列表里,然后用hash取模的方法从uid中获取对应的的服务端地址server_id = uid % num_of_servers。当然,这种方法会带来一个很直接的问题:如果我们的IM消息也是分布式存储,那么如何对IM Storage进行扩容呢?对于这种一致性Hash的问题有一些比较复杂的解决方法,比如Google推出的JUMP Hash等。但任何一个问题都需要结合实际应用场景才能得到比较好的tradeoff方案。比如结合我们的实际客服网聊应用场景,只需要把Storage加大些就足以支撑。我个人更倾向于使用snowflake将server索引编码进入uid,即便后续扩容起来也比较容易。
对于IM系统而言,另一个考验就是消息格式的定义。这部分同样需要结合场景来定义它的通用性和扩展性。比如保障消息顺序,消息唯一性,消息篡改等等。
在实际应用中,消息结构体如下:
消息格式:12字节消息header + 消息body
消息header: 4字节消息长度,4字节消息序列号,1字节消息类型,1字节版本号,预留2字节
消息body: 自定义JSON格式 (sender,receiver,timestamp,msgid,content)
消息序列号:msg_seq用于保持同一会话中的消息顺序
消息类型:msg_type用于标明该消息的业务含义,比如auth鉴权,cr_group创建群组等等
消息id: 为了保持消息的唯一性,msgid主要用于应用层控制,比如加载历史/离线消息列表等等
一个 基础的IM系统支持,连接鉴权->消息解析->消息存储->消息发送的流程
IM连接鉴权流程:
定义MSG_AUTH_TOKEN消息类型
Client连接Server时,携带该消息类型
Server此时读取Client信息,初始化连接
为该Client创建IM Route的实例,在该Route Instance上登记Client
至此,建立对应Client的消息路由通道成功
消息确认: MSG_ACK 防止消息丢失,Client告知Server确认收到消息
心跳机制: MSG_PING Client监测Server是否连接正常,可实现自动重连
消息解析流程:
从连接中读取client发送的buffer
根据消息header的定义,校验buffer中前12个字节
根据消息类型,创建消息body或者获取消息路由信息
保存该消息至IM Storage,获取msgid,进入消息发送流程
ClientA发送消息给ClientB:
将该消息MSG_IM发送到对方的IM Storage存储
将该消息包装成MSG_PUBLISH发送至IM Route,同时将该消息的msgid作为Sync_key,发送MSG_SYNC_NOTIFY至B的消息队列。(可能会产生两次消息)
IM Route检测如果B在线,IM Route转发消息给B所在的IM Server。否则发起离线推送通知给B。 ClientB据MSG_SYNC_NOTIFY接收消息:
ClientB收到MSG_SYNC_NOTIFY,根据msgid判断消息是否接受过 没有接收过(离线场景),ClientB携带上次的msgid作为sync_key发送MSG_SYNC到所在的IM Server,IM Server根据ClientB所在的IM Storage获取离线消息列表。 ClientB收到离线消息列表后,保存新的最大的msgid
群消息发送流程:
Client发出一条消息,拷贝N份到各自群成员的消息队列中(消息风暴) Server只保存一份群消息至Storage。群内各自离线Client主动遍历超级群id拉取。
ClientA发送消息到超级群中: 根据消息类型MSG_GROUP_IM交由GroupManager处理 检测该群为超级群,Server把消息保存至IM Storage,发送给群内在线用户
ClientA发送消息到群中:
根据消息类型MSG_GROUP_IM交由GroupManager处理
GroupManager根据GroupId分配GetGroupMessageDeliver,消息包装成MSG_PENDING_GROUP_MESSAGE,deliver循环处理该消息 GroupMessageDeliver临时保存群消息到本地文件(扩容停机保证消息不丢失)
将该消息MSG_GROUP_IM发送到群用户对应的IM Route中
不同于持久性的IM场景,聊天室场景就非常简单,主要因为
1,所有用户都在线
2,消息不被存储,无离线消息,实时发送
聊天室消息处理流程:
Client进入聊天室发送MSG_ENTER_ROOM消息,Server为Client分配路由
Client订阅该聊天室的消息,接收其他在线用户的消息