甲乙小朋友的房子

甲乙小朋友很笨,但甲乙小朋友不会放弃

0%

系统设计-聊天系统与访问限制系统

大纲

  • Design WhatsApp : 聊天系统
    • Work Solution
    • Real-time Service
    • Online Status: Pull vs Push
  • Design Rate Limiter
  • Design Datadog :一种网站的数据统计,类似于google anaysis

这节课之后您可以学会

  • 设计聊天系统的核心:Realtime Service
  • Pull 与 Push 的进一步比较分析

相关设计题

  • Design Facebook Messenger
  • Design WeChat

聊天系统

Scenario 场景

设计功能

  • 基本功能
    • 登录注册
    • 通讯录
    • 两个用户互相发消息
    • 群聊
    • 用户在线状态
  • 其它功能
    • 历史消息
    • 多机登录 multi devices

系统限制

  • 活跃
    • 1B 月活跃用户
    • 75% 日活跃 / 月活跃
    • 约750M日活跃用户 ——数据来自 Facebook 官方,截止2016年3月

为了计算方便起见,我们来设计一个100M日活跃的WhatsApp

  • QPS:
    • 假设平均一个用户每天发20条信息
    • Average QPS = 100M * 20 / 86400 ~ 20k
    • Peak QPS = 20k * 5 = 100k
  • 存储:
    • 假设平均一个用户每天发10条信息
    • 一天需要发 1B,每条记录约30bytes的话,大概需要30G的存储

Service 服务

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

Storage 存储

最简单方法

既然是聊天软件,自然需要一个message table。那么我们需要在message table里面存什么呢?能想到的就是:

那么如果按照这个表来存储,那么如果要查询A与B之间的对话,则需要:

1
2
3
SELECT * FROM message_table
WHERE from_user_id=A and to_user_id=B OR to_user_id=B and from_user_id=A
ORDER BY created_at DESC;

存在两个问题:

  • 效率太低。
  • 群聊咋办?gg了

改进——引入thread

thread表示两个的对话!如下图所示,左边是thread。右边是message。

那么thread table里面存什么呢?

看起来没什么问题。但我们漏了一个东西——如何查询我参与了哪些thread呢?

优化

thread table里面加一个owner_id

有几个拥有者,那么thread table就复制几份!——这样虽然比较浪费空间,但是thread里面有些东西是私有的,比如昵称什么的。

问题来了,那么thread table的primary key是什么呢?—— 其实就是owner_id + thread_id的组合呀

流程梳理

  • message table,存储每个人发的每条消息,以及对应的thread_id。即[id, thread_id,user_id,content, created_id]
  • thread table, 存储每个thread的信息,每个人独享一份数据。即[owner_id,thread_id, participant_ids, is_muted, nickname, created_at, updated_at]
  • 查询我的所有thread list——从thead_table里查询给定user_id的所有thread_id即可。因此thread_table用SQL
  • 查询我的某个thread的大概信息——从thread_table里查询给定owner_id+thread_id的信息
  • 查询我的某个thread的具体消息——从message_table中,根据thread_id查询所有的消息,并且返回.因此message_table用NoSQL
  • 查询我与另一个用户是否有一个thread?

即:

Word Solution

用户如何发送消息?

  • Client 把消息和接受者信息发送给 server
  • Server为每个接受者(包括发送者自己)创建一条 Thread (如果没有的话)
  • 创建一条message(with thread_id)

用户如何接受消息?

  • 可以每隔10秒钟问服务器要一下最新的 inbox 虽然听起来很笨,但是也是我们先得到这样一个可行解再说
  • 如果有新消息就提示用户

Scale 扩展

sharding

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

每隔十秒要inbox?太慢了,优化——Socket

Socket——TCP长连接

Push Service——提高Socket连接服务,可以与Client保持TCP长连接

  • 当用户打开APP之后,就连接上Push Service中属于自己的socket
  • 有人发消息时,Message Service收到消息,通过Push Service把消息发出去
  • 如果一个用户长期不活跃(比如10分钟),就可以断开连接,释放掉网络断开
  • 断开连接之后,如何收到消息?
    • 打开APP时主动pull + android GCM/IOS APNS
  • Socket连接与HTTP连接的最主要区别;
    • HTTP连接下,只能客户端问服务器要数据
    • Socket连接下,服务器可以主动推送数据给客户端

流程: