设计微信或者FaceBook messenger

[TOC]

系统的要求和目标

功能需求

支持1对1聊天

支持离线在线状态

支持永久存储聊天记录

非功能需求

实时聊天体验,延迟越小越好

高度一致,相同的聊天信息在USER的DEVICE上

可以容忍稍微低一点的可用为了一致性

拓展需求

群聊

推送通知

容量估计

500M DAU, 一个用户一天发40条微信。
20 B 消息每天。
QPS 大概在 20 B/100K = 0.2m = 200K
假设每条消息100B。 2T 每天

五年需要 3.6 P的存储

img

image.png

服务

img

image.png

用户A通过聊天服务器向用户B发送消息。

服务器接收消息并向用户A发送确认。

服务器将消息存储在其数据库中,并将消息发送给User-B。

用户B接收消息并将确认发送到服务器。

服务器通知用户A该消息已成功传递给用户B。

img

image.png

img

image.png

存储

自然我们会需要一个MESSAGE TABLE,用来存储用户发送的每一条信息。随后我们为了构建一个1对1的会话,我们需要一个THREAD TABLE。
Message 表有,message_id, from_user,content,created_at,thread_id
Thread 表有, Thread_id, participants, created_at,updated_at,owner_id,is_muted

img

image.png

这里MESSAGE TABLE,像LOG一样,可以用NOSQL直接来存。
使用像HBase这样的宽列数据库解决方案可以轻松满足我们的两个要求。 HBase是一个面向列的键值NoSQL数据库,可以将一个键的多个值存储到多个列中。 HBase以Google的BigTable为模型,运行在Hadoop分布式文件系统(HDFS)之上。 HBase将数据组合在一起以将新数据存储在内存缓冲区中,一旦缓冲区已满,它就会将数据转储到磁盘。这种存储方式不仅可以帮助快速存储大量小数据,还可以通过密钥或扫描范围获取行的行。 HBase也是一个存储可变大小数据的高效数据库,这也是我们服务所需要的。

THREAD TABLE,需要按照更新时间排序,同时需要对OWNER_ID+THREAD_ID来做PK。所以可以用SQL。

如何重试失败的请求?
比如用户发送一条消息,判断有没有收到服务器的ACK。因此,我们可以告诉用户这条消息未能发送过去,让他们选择是否重试。
只有当ACK的MESSAGE 才会被存入DB。

管理用户状态

每当客户启动应用程序时,它都可以将朋友列表中所有用户的当前状态拉出。(每当用户向另一个离线用户发送消息时,我们都可以向发件人发送失败消息并更新客户端上的状态)

每当用户上线时,服务器总是可以延迟几秒钟来广播该状态,以查看用户是否没有立即下线。

客户端可以从服务器获取有关用户视口中显示的用户的状态。这不应该是一个频繁的操作,因为服务器正在广播用户的在线状态,我们可以暂时处理用户的陈旧离线状态。

每当客户端与另一个用户开始新的聊天时,我们就可以在那时提取状态。

用户定期去拿新的好友在线离线状态。

数据分区

Thread_id 基于USERID 分区
基于UserID的分区:假设我们基于UserID的哈希进行分区,这样我们就可以将用户的所有消息保存在同一个数据库中。

MESSAGE 可以基于THREAD_ID来分区。

实时方案

用WEB SOCKET
有人一发送消息,服务器就可以进行推送。
断开连接后,用户如何接受消息
可以通过下次上线的PULL 或者IOS NOTIFICATION
这里要引进一个PUSH SERVER。
当用户上线时,首先问WEB SERVER 要一个PUSH SERVER的IP。随后和PUSH SERVER注册一个WEB SOCKET的双向连接。
之后用户B发给A消息,会先发给WEB SERVER,WEBSERVER知道A在哪个PUSH SERVER,把消息转发过去。

img

image.png

群聊支持

群聊的问题主要是,有一些人没上线。如果所有人都在线,其实就是你发了一条消息,我就再向这个群的另外所有人都发送。
如果有很多人不在线的时候,WEB SERVER就会往PUSH SERVER空发很多信息。
让费了资源。

这里我们再引入一个CHANNEL SERVICE。当用户上线了,就订阅到每一个THREAD_ID(群聊的) 对应的CHANNEL SERVICE。 当用户下线,PUSH SERVER 会把这个用户从那些CHANEL 上移除。
这样MESSAGE SERVICE(web server)就只要向CHANEL SERVICE发信息。
CHANNEL SERVICE向当前在线的用户发消息给PUSH SERVICE。

容错

聊天服务器出现故障会发生什么? 我们的聊天服务器与用户保持着联系。 如果服务器出现故障,我们是否应该设计一种机制将这些连接转移到其他服务器? 将TCP连接故障转移到其他服务器非常困难; 更简单的方法是让客户端在连接丢失时自动重新连接。

我们应该存储多个用户消息副本吗? 我们不能只有一个用户数据副本,因为如果持有数据的服务器崩溃或永久停机,我们没有任何恢复该数据的机制。 为此,我们必须在不同的服务器上存储多个数据副本,或者使用Reed-Solomon编码等技术来分发和复制它。