上篇文章我们从高阶设计角度聊了下聊天软件设计(链接)。这篇文章会详细介绍一下各具体模块。
服务发现
服务发现的主要职责是根据一些条件,比如说地理位置,服务器容量等等,来给客户端推荐最好的聊天服务器。Apache Zookeeper是主流的服务发现开源解决方案。它根据预设条件注册所有的可能聊天服务器并且为客户端选择最好的聊天服务器。
用户A尝试登陆到app
负载均衡发送登陆请求到API服务器
验证好用户后,服务发现为用户A找到最适合的聊天服务器。在这个例子中,选择了服务器2并将服务器信息返回给用户A
用户A通过websocket连接到服务器2
信息流
1对1聊天流
下图解释了当用户A发送信息给用户B发生了什么。
1. 用户A发送聊天信息给到聊天服务器1
2. 聊天服务器1从ID生成器中获取到了信息ID
3. 聊天服务器1发送信息到信息同步队列
4. 信息被存储在键值数据库中
5. a. 如果用户B在线,信息会被推送到用户B所连接的聊天服务器2
b. 如果用户B离线,推送通知服务器会发送推送服务
6. 聊天服务器2发送信息到用户B。用户B和聊天服务器2之间会有持续的websocket连接
在多设备之间的信息同步
通常情况下用户有多台设备。
上图中,用户A有两台设别:一部手机会一台电脑。当用户A用手机登陆到聊天软件,那么手机端便与聊天服务器1建立了websocket连接。类似的,电脑端和聊天服务器1之间也有连接。
每台设备都会有一个变量叫cur_max_message_id,这个变量用于追踪设备上的最新message id。满足以下两个条件的信息可以被认为是新信息:
recipient id = 当前登陆 user id
在键值存储中的message id大于cur_max_message_id
因为每台设备上有*cur_max_message_id*,所以同步是比较容易的因为每台设备可以据此从键值存储中获取最新的信息。
小组群聊流
对比起1对1聊天,群聊的逻辑会更复杂。
上图中解释了当用户A在群组聊天的时候发送信息会发生什么。设想群组里一共3个成员(用户A,用户B,用户C)。首先,User A发送的信息会被复制到每个群组成员的信息同步队列中。你可以认为信息同步队列相当于接收人的收信箱。这种设计选择适用于小群组群聊是因为:
简化了信息同步流因为每个客户端只需要检查自己的收信箱是否有新信息
当群组成员人数较小时,在每个收件人的收信箱存储备份的成本并不高
微信使用的是相似的策略,并且它限制群聊人数上限为500人。然而,对于有许多用户的群来说,为每个群组成员存储信息备份是不可接受的。
在线状态
在线状态指示器是许多聊天app的一个重要功能。通常,你可以在用户的头像或用户名旁看到一个绿点。
在高层设计中,存在服务器负债总额管理在线状态,并通过websocket与客户端通信,有以下几个流程会出发在线状态的变化
用户登陆
用户登陆流已经在“服务发现”部分解释过了。在websocket连接建立之后,用户A的在线状态和*last_active_at*时间戳会被存储在键值存储中。当用户登陆在线状态显示器会显示用户在线状态。
用户登出
以下是用户登出流。在键值存储中在线状态会被更改为离线状态。
用户断开连接
在真实的使用场景中,用户的网络可能并不稳定。当用户从互联网断开连接时,客户端与服务器之间的持久连接就会丢失。处理用户断开连接的一种简单方法时将用户标记为离线,并在连接重新建立时改变状态为在线。然而,这种方法有一个主要缺陷。用户频繁的在短时间内断开和重新连接互联网是很常见的。例如,当用户穿过隧道时,网络连接可能会断断续续。每次断开/重连时都更新在线状态会使得在线状态指示器变化太频繁,导致糟糕的用户体验。
我们引入心跳机制来解决这个问题。在线客户端定期向存在服务器发送心跳事件。如果存在服务器在一定时间内,比如说从客户端开始的x秒内,收到心跳时间,那么用户被认为是在线的。否则,就是离线的。
下图中,客户端每5秒发送心跳事件到服务器。当发送3次心跳事件后,客户端失联并且在x=30秒内并没有重新连接。那么在线状态会被更改为离线。
在线状态扩散
用户A的好友如何知道用户A的状态变化呢?下图解释了运行机制。在线显示服务器使用publish- subscribe模型,也就是说每个好友对中会维护一个通道。当用户A的在线状态改变的时候,会发送事件到三个通道中,通道A-B,A-C,A-D。这三个通道分别被用户B,C,D订阅。这样,他们便能获取到好友的状态变化。客户端和服务器之间的通信是通过实时的websocket。
上述设计对于小群组是比较高效的。例如,微信使用类似的策略因为它的群聊用户上限为500人。对于大型群组来说,通知所有成员在线状态的成本是很高而且也很耗时。假设一个群组有100000名成员,那么每个状态改变会生成100000次事件。为了解决这个瓶颈,一个可能的方案是只有当用户进入到群组或手动刷新好友列表的时候才会去获取好友在线状态。