文章目录
需求
- 有类似功能的软件:
- Facebook Messenger
- 微博
- 微信
功能需求:
- 用户在线、离线; 扩展:状态变更通知
- 1对1对话; 扩展:群聊
- 对话历史保存
非功能需求:
- 聊天延迟低
- 聊天记录一致
- 为保证一致性,可容忍较低的可用性
规模预估
假设
- 每天有5亿活跃用户,
- 平均每个用户每天发送40条消息
- 一条消息平均为100 B
每天消息 = 5亿 * 40条 = 20G条
流量
= 20G * 100 B = 2 TB
带宽
传入 = 2 TB/86400秒 ~= 25 MB/s
下载 = 传入
存储
200亿条消息100字节=>2 TB/天
五年 = 2 TB365天*5年~=3.6 PB
缓存
28原则,0.4TB
数据模型
概要设计
详细设计
功能用例:
- client 接收传入消息,传递消息
- 数据库存储消息,检索消息
- 记录用户状态,变更是通知其他用户
消息推拉模式
1.拉模式
- 流程
client:用户定期拉取,询问服务器是否有任何新消息。
server:服务器需要跟踪记录所有未拉取的消息,用户拉取时全部返回 - 缺点:容易浪费网络、IO资源
为了实时性,要频繁检索数据库里未拉取的消息
如果没有消息会空拉
2.推送模式
-
流程
client:保持与服务器的连接打开
server:跟踪client连接,有新消息就通知用户 -
缺点:保持连接难
轮询的效率低,非常浪费资源。
HTTP要始终打开,有超时、断开连接情况
推送模式连接问题 比起拉模式网络、io资源问题 容易处理。
综上,选取推送模式
。
处理推送模式连接问题
-
client连接保持方法
- http长轮询
- webSocket
-
服务端跟踪client连接方法
维护一个hash表(k=clientId,v=连接对象) -
接收方client断开情况
- 通知发送方发送失败,由发送方自动重试发送
- 服务端缓存一段时间消息,由服务端重试发送
服务器
-
数量
5亿活跃用户,要保证5亿连接
每台服务器50K 并发连接。需要10k个服务器 -
流程
- 将消息存储在数据库中
- 将消息发送给接收者
- 向发送者发送确认
消息顺序问题
client1 顺序为:M1 - M2
client2 顺序为:M2 - M1
需要为每个客户端的每条消息保留一个序列号。此序列号将确定每个用户消息的确切顺序
消息落库
服务器收到新消息时,有两种落库选择
- 向数据库发送异步请求以存储消息。
- 启动一个单独的线程,该线程将与数据库一起存储消息。
- 失败重试机制
服务器推送消息
自动分页。不同的客户端,页面大小可能不同,
- 手机屏幕较小,因此我们需要在视口中减少消息/对话的数量。
- 电脑屏幕大,适量多展示数量
更新用户状态
5亿用户,广播给所有相关的活动用户,将消耗大量资源
所以不能状态变更就立刻广播状态,
要不频繁地更新状态
更新状态情况如下:
- 【client启动时】:提取其好友列表 中所有用户的当前状态。
- 【client刷新其他用户状态】:这不应该是一个频繁的操作,因为服务器正在广播联机状态,我们可以暂时忍受用户陈旧的脱机状态。
- 【每当client联机时】:服务器总是可以以几秒钟的延迟广播该状态秒,以查看用户是否没有立即脱机。
- 【每当client开始与另一个用户进行新的聊天时】,都获取当时client的状态。
- 【每当client1向另一个已脱机的client2发送消息时】:通知client1发送失败,并更新client2的状态。
存储
数据库选择
- 支持更新率高
- 存储海量数据
MySQL 不能满足高io更新需求
redis、MongoDB不满足海量存储需求
用宽列数据库 HBase
原因:
- 面向列的键值NoSQL数据库
- 运行在Hadoop分布式文件系统(HDFS)、快速存储大量小数据
- 可以通过键或扫描行范围获取行
数据分区
5年3.6PB,我们需要将其分发到多个数据库服务器上。
2个分区方案:
-
基于ClientID的hash进行分区
一个用户消息落在一个分区
如果一个DB分区是4TB,五年将拥有 “3.6PB/4TB~=900” 个分区。为了简单起见取整,我们保留1000个分区。
程序将通过“hash(UserID)%1000”找到分区号,然后从中存储/检索数据。此分区方案还可以非常快速地获取任何用户的聊天历史记录。 -
基于MessageID的分区:
一个用户的消息落在 多个分区上
则获取用户的一系列聊天消息需要对多个分区io,将非常缓慢,因此我们不应采用此方案。
性能、可用
缓存
缓存最近聊天的client 的最新的一些消息
如:最近1万个发消息的client,每个client缓存100条message
由于我们决定将用户的所有消息存储在一个碎片上,因此用户的缓存也应该完全驻留在一台机器上。
负载平衡
- server负载均衡
- cache负载均衡
容错和副本
情况分析:
服务器宕机
- 将client连接转移到其他服务器(难实现)
- client重连
数据库宕机
- 备份数据,不同的服务器上存储数据的多个副本
- 使用Reed-Solomon encoding(纠错码)等技术来分发和复制数据。
扩展需求实现
群聊
创建群组聊天对象GroupChat,存储在聊天服务器上
基于GroupChatID分区的单独表
提醒推送
-
背景
目前client只能向活动用户发送消息,如果向脱机状态用户发消息将会返回失败。 -
提醒推送 功能:
使我们的系统能够向脱机用户发送消息。 -
设计
- client选择开启 提醒推送功能
- 单独创建一个通知server,在server为离线client创建一个暂存消息的对象。
- 离线client收到消息后,记录在通知server
- client上线后通知server将这些通知推送到client。