以前用workman搭建websocket集群服务时,也曾考虑过他的中心服务器是单机的,虽然业务可以交给business服务器,但是对于regesiter服务始终还是存在单点故障的危险。机缘巧合,看了一篇websocket的分布式部署的文章,以及向公司的同事请教了下关于现在公司的自研im的架构,特整理一下。
一、ws服务
1、架构图
基本流程为:用ws协议连接本服务,得到一个clientId,由客户端上报这个clinetId给服务端,服务端拿到这个clientId之后,可以给这个客户端发送信息,绑定这个客户端都分组,给分组发送消息。
2、时序图
核心点:
1、redis映射了每个客户端所对应的ws服务器的ip地址和端口。
2、当服务器需要发送消息时,需要判断这个client_id是不是属于本服务器:用reids的映射去验证。当不属于本服务器的client时,则通过rpc告知相应的ws服务器,让它去处理这个消息。
3、群发消息时,通过队列来处理。公司目前用的kafka,保证了有序性。
二、消息模型
IM离不开消息模型,在以前的工作中使用的是传统的推模式。
传统模式大体如下:对于在线的用户,消息会直接实时同步到在线的接收方,如:手机端和pc端。而对于离线的用户或者消息有两种方式:一种是专门的离线库储存,未读和离线的消息;另一种是保存一个标记点,记录离线或者是未读的数量。无论哪一种,这种方式都是在同步后再储存消息的模式,存在着丢消息、消息不同步,也不支持消息漫游等功能。如果强行一致,那数据库的压力则会非常大。
另外一种模型是Timeline模型。
TimeLine模型
Timeline可以简单理解为是一个消息队列,但这个消息队列有如下特性:
- 每个消息拥有一个顺序ID(SeqId),在队列后面的消息的SeqId一定比前面的消息的SeqId大,也就是保证SeqId一定是增长的,但是不要求严格递增。
- 新的消息永远在尾部添加,保证新的消息的SeqId永远比已经存在队列中的消息都大。
- 可根据SeqId随机定位到具体的某条消息进行读取,也可以任意读取某个给定范围内的所有消息。
有了这些特性后,消息的同步可以拿Timeline来很简单的实现。图中的例子中,消息发送方是A,消息接收方是B,同时B存在多个接收端,分别是B1、B2和B3。A向B发送消息,消息需要同步到B的多个端,待同步的消息通过一个Timeline来进行交换。A向B发送的所有消息,都会保存在这个Timeline中,B的每个接收端都是独立的从这个Timeline中拉取消息。每个接收端同步完毕后,都会在本地记录下最新同步到的消息的SeqId,即最新的一个位点,作为下次消息同步的起始位点。服务端不会保存各个端的同步状态,各个端均可以在任意时间从任意点开始拉取消息。
消息漫游也是基于Timeline,和消息同步唯一的区别是,消息漫游要求服务端能够对Timeline内的所有数据进行持久化。
参考:https://github.com/woodylan/go-websocket