https://www.slideshare.net/brizzzdotcom/facebook-messages-hbase
设计what's app, facebook messenger, wechat
要求支持群聊
4.7. 2019的第二版
Clarify
Scenario
Api
sendMessage(userId, content, timestamp, thread)
GetRecentMessage(userId)
Services
Chat Service
User Service
Storage
Relations:
User Table
user id, name, password (md5), last login
Thread Table
ThreadId, userId threadName, participant ids, createdAt, lastUpdatedAt,
Message Table
ThreadId, MessageId, content (Message Id包含timestamp和senderId)
Message User Table
MessageId, UserId, ReadOrNot
其实这个table有点大, 可以不做,用user的last pulled time来做就好。 这个要跟面试官讨论。
具体某个消息我pull下来但是没有读过,可以由本地的客户端来处理。
加速查询/Index
use case是要时间倒序显示thread
对一个thread要按显示最近的message
对一个用户要看他在哪个thread里面。
对同一组人,看它之前有没有开过一个会话。
所以对thread table要按userId + last updatedAt 建index(就是这个index要经常更新),
对message table, 要按threadId + messageId 建index(这个是只读操作倒还好, messageId是按 timeStamp生成的) 。
MessageTable的messageId用timestamp + userId来实现。
为了实现对同一组人不建两个群聊的方法, 可以有如下两种做法:
- 对thread table按participantIds(String) 建index,
查的时候先根据用户的id,去确定哪台机器上肯定有。然后在这台机器上找有没有这个participantsIds,
participantId内部要排序。
2。 也可以单独建一个participantIds Thread table. 先生成 participantIds字符串,再去对应的机器上查找有没有这个thread, 这样虽然多了一张表,但是这张表很小呀。第一种方式那个index文件其实也可以是蛮大的。
分页
Select * From thread table where userId = user1 order by lastUpdatedAt desc
offset 10 fetch next 50.
Scale
Sharding:
User table按user id sharding,
thread table 按user id sharding
message table 按thread id sharding
messageUser table按user id sharding (这个表可要可不要)
如果是Slack channel的话,对于一个post下面可能有回复。
这的话可以在message table里面加一个parent message 那一项。
消息推送服务
用户上线之后可以每十秒钟问服务器要一下我最新的inbox,
getMostRecentMessages(userId)
Post(user, Id, threadId, content),
Post(user, Id, participantIds, content)
对于实时性要求高的聊天来说,需一个real time service给用户进行推送。
用户登录以后,web server会给用户指派一个real timer server让用户去联接,用户用web socket和real time server保持联接。
另外一个用户B给这个用户A发消息后,消息服务会找到用户所在的real time service 推送给用户A。
如果是群聊的话, 消息服务直接发找到群聊的channel service, 让channel去考虑推送给谁。
3.31.2019的第一版。
Clarify
1。是否需要保存聊天记录。
2。 如果用户不在线了, 是否向用户移动端发push notification?(先不考虑)
Scenario:
1。多人聊天,发送消息,查看消息,保留聊天记录
2。列出用户参与的聊天小组
3。 给定聊天小组 列出最近的聊天记录
4。支持实时聊天
- 用户在线状态
- 支持FB Scale
其他功能: 历史消息, 多机登录
设计多牛的系统:
MAU, DAU, QPS,都 要仔细 算一算
存储。
写多读少还是读多写少? 写多读少。因为有fanout。上线了才算读。但发消息是一条一条发的。
Service
User1 - Chat Server - Data Storage
User2 |
\
Real Timer server
Message service
Real time service
Working solution
用户登录服务器, 服务器拿着user id去thread table查询用户有哪些会话最近更新了, 并按倒序排序。对每个会话去message table查询它有多少条是未读的。对于每个thread列出最近的一条用来preview。返回给客户端前20条。
用户刚开始通过http协议连接。最基本的操作是用户每十秒钟去找服务器要一下最新的消息。
优化后用户连上之后服务器会给用户安排一个real time service服务器地址, 用户连接那个real time service, 一量有新的消息,就会推送给用户。
发消息: 用户通过Chat Server向Message Table和Thread Table写入最近的消息。
B用户发了消息之后, web service会通过real time service把消息推送给A
用户通过heartbeat来向服务器请安保持存在感。(heartbeat的信息是不是不需要存在table里面,存在内存里就好了吧 ? )
群聊:
群聊的问题是当一条信息发出去之后,服务器可能需要向所有人push消息,如果那个人不在线或者很久没有活跃了,就会浪费资源。 所以对每个群聊,系统保持记录谁还在线,收到消息时只推送给那些在线的用户。 A用户发了消息之后,ThreadBroadCastingTube(其实是channel service , 为了和slack的channel区分起了这个怪名字 ) 看群里还有谁在线, 不在线的话,就不发了。只推送给在线的人。不在线的人登录之后才会看到。
几个难点:
根据最新消息的时间排序 支持分页 如何实现?
为了加快速度,这个要对thread table 按照 owner id + updatedTime 建立index
用数据库读的时候, page number 1, size 30 , 翻滚的时候 再拿 30条
time stamp secondary.
messageId, key, timestamp,
给定聊天小组 列出最近的聊天记录(根据消息发送时间排序 支持分页)如何实现
要对message table根据ThreadId + createdTime 进行secondary index
难点? 如果用no sql实现 message table, 则不好进行secondary index。
用Canssandra应该可以实现,三层结构,Row key 可以是 thread id, colum key是message id...
标记已读如何实现?
增加一个message_user table
Storage
是点对点聊天吗 ? 需要经过服务器吗?需要存下来吗?
User Table (userId, name, password)
Message Table (messageId, userId, threadId, senterId, content, timeStamp, readOrNot)
Thread Table (threadId, channelId, userId, participantIds, createdAt, mutedOrNot, lastUpdatedTime )
SlackChannel Table (channelId, channel_name, description, create date)
Subscription Table (channelId, userId)
ThreadBroadCastingTube(ThreadId, ListOfActiveIds) 这个并不需要存table, 直接内存就可以。
Message Table也可以拆成 Message Table 和 MessageUser Table:
Message Table (messageId, userId, threadId, senterId, content, timeStamp, readOrNot)
MessageUser Table (messageId, userId, readOrNot)
用什么数据库
User Table, 用 SQL, 原因结构化的,不经常改
Message Table 用Cassandra: 原因,只有写操作,不会修改,只有。 支持根据Thread然后按messageId 查询。 (Message Id可以前缀放个时间戳)
关于Thread Table里面Participant_ids的问题 :
Thread Table里面为什么 Participant_ids不用拆开,不会经常更新
为什么 like table 必须要拆开 : 很多操作。不能进行原子操作,所以要拆开。
相比存单独的表有什么 好处?查的快。如果用单独的表,还得join。这个操作太频繁了 。
有些Thread信息是私有的 比如isMuted, 未读的,呢称。所以还有些细节需要处理。
需要把Thread table增加一个owner id, 谁的thread.
A, B 聊天会在Thread table里产生 两条记录
在数据库里最好少用join
uuid??
用什么 样的数据库
Message Table存在 No SQL
Thread Table存在SQL.
Owner ID + thread ID (primary index)
Owner ID + updated time (按更新时间排序)
同时可以给participant id建 index,这样如果三个人聊天以前建过群聊就不用再建新的群聊了。
Scale
-
按什么sharding?
User table按user id sharding
message table按thread id sharding -
如何建index
哪些地方要建index,
Thread Table 要对 thread id 按 user id + updated time 建 index
Message Table里面如果用了Canssandra的话,可以直接进行range query(假设message id是按那个时间排序的),就不需要再建index了。
Working Solution
如何发消息
Client把消息和接受者信息发给服务器
Server创建thread(如果不是已经存在的话)
创建一条message (with thread)
如何收消息
每隔十秒钟要一次
有新信息就通知用户
网友评论