2020-09-03

tech2022-10-31  109

如何设计一个聊天系统? 基本功能 用户登录注册 通讯录 两个用户互相发消息 群聊 用户在线状态

其他功能 历史消息 多设备登录

• 1B 月活跃用户 • 75% 日活跃 / 月活跃 • 约750M日活跃用户

• 为了计算方便起见,我们来设计一个100M日活跃的聊天系统 • QPS: • 假设平均一个用户每天发20条信息 • Average QPS = 100M * 20 / 86400 ~ 20k • Peak QPS = 20k * 5 = 100k • 存储: • 假设平均一个用户每天发10条信息 • 一天需要发 1B,每条记录约30bytes的话,大概需要30G的存储

服务 • Message Service • 负责信息管理 • Real-time Service • 负责实时推送信息给接受者

存储 Message Table 如何设计?

Message Table (NoSQL) • 数据量很大,不需要修改,一条聊天信息就像一条log一样 • Thread Table (SQL) —— 对话表 • 需要同时 index by • Owner ID + Thread ID (primary key) • Owner ID + Updated time (按照更新时间倒叙排列) • NoSQL 对 secondary index 的支持并不是很好

可行解 • 用户如何发送消息? • Client 把消息和接受者信息发送给 server • Server为每个接受者(包括发送者自己)创建一条 Thread (如果没有的话) • 创建一条message(with thread_id) • 用户如何接受消息? • 可以每隔10秒钟问服务器要一下最新的 inbox • 虽然听起来很笨,但是也是我们先得到这样一个可行解再说 • 如果有新消息就提示用户

扩展 性能上的拓展(支持更多的用户,有更快的响应速度) 功能上的拓展(如支持群聊,在线状态)

Message 是 NoSQL,自带 Scale 属性 Thread 按照 user_id 进行 sharding

每隔10秒钟收一次消息太慢了,聊天体验很差,不实时。

• 需要引入一个新的概念——Socket • 再引入一个新的Service —— Push Service • Push Service 提供 Socket 连接服务,可以与Client保持TCP的长连接 • 当用户打开APP之后,就连接上Push Service 中一个属于自己的socket。 • 有人发消息的时候,Message Service 收到消息,通过Push Service把消息发出去 • 如果一个 用户长期不活跃(比如10分钟),可以断开链接,释放掉网络端口 • 断开连接之后,如何收到新消息? • 打开APP时主动Pull + Android GCM / IOS APNS • Socket 链接 与 HTTP 链接的最主要区别是 • HTTP链接下,只能客户端问服务器要数据 • Socket链接下,服务器可以主动推送数据给客户端

• 用户A打开App后,问 Web Server 要一个 Push Service 的连接地址 • A通过 socket 与push server保持连接 • 用户B发消息给A,消息先被发送到服务器 • 服务器把消息存储之后,告诉 Push Server 让他通知 A • A 收到及时的消息提醒 如何实现群聊 • 问题 • 假如一个群有500人(1m用户也同样道理) • 如果不做任何优化,需要给这 500 人一个个发消息 • 但实际上 500 人里只有很少的一些人在线(比如10人) • 但Message Service仍然会尝试给他们发消息 • Message Service (web server) 无法知道用户和Push Server的socket连接是否已经断开 • 至于 Push Server 自己才知道 • 消息到了Push Server 才发现490个人根本没连上 • Message Service 与 Push Server 之间白浪费490次消息传递

• 解决 • 增加一个Channel Service(频道服务) • 为每个聊天的Thread增加一个Channel信息 • 对于较大群,在线用户先需要订阅到对应的 Channel 上 • 用户上线时,Web Server (message service) 找到用户所属的频道(群),并通知 Channel Service 完成订阅 • Channel就知道哪些频道里有哪些用户还活着 • 用户如果断线了,Push Service 会知道用户掉线了,通知 Channel Service 从所属的频道里移除 • Message Service 收到用户发的信息之后 • 找到对应的channel • 把发消息的请求发送给 Channel Service • 原来发500条消息变成发1条消息 • Channel Service 找到当前在线的用户 • 然后发给 Push Service 把消息 Push 出去

Channel Service 用什么存储数据? 内存就好了,数据重要程度不高,挂了就重启 因为还可以通过 IOS / Android 的 Push Notification 补救

在线状态修改 • Update online status 包含两个部分 • 服务器需要知道谁在线谁不在线(push or pull?) • 用户需要知道我的哪些好友在线(push or pull?)

push模式 • 告诉服务器我来了 / 我走了 • 用户上线之后,与 Push Service 保持 socket 连接 • 用户下线的时候,告诉服务器断开连接 • 问题:服务器怎么知道你什么时候下线的?万一网络断了呢? • 服务器告诉好友我来了 / 我走了 • 用户上线/下线之后,服务器得到通知 • 服务器找到我的好友,告诉他们我来了 / 我走了 • 问题1:同上,你怎么知道谁下线了 • 问题2:一旦某一片区的网络出现具体故障 • 恢复的时候,一群人集体上线,比如有N个人 • 那么要通知这N个人的 N * 100 个好友,㐀成网络堵塞 • 问题3:大部分好友不在线

pull模式 • 告诉服务器我来了 / 我走了 • 用户上线之后,每隔3-5秒向服务器heart beat一次 • 服务器告诉好友我来了 / 我走了 • 在线的好友,每隔3-5秒钟问服务器要一次大家的在线状态 • 综合上述 • 每隔10秒告诉服务器我还在,并要一下自己好友的在线状态 • 服务器超过1分钟没有收到信息,就认为已经下线 • 可以打开你的 Facebook Messenger 验证一下 • 打开 console 点击 network • 大概每隔3-5秒会pull一次服务器

最新回复(0)