第 0 章
全景
一个 IM 系统由哪些部分组成,一条消息怎样从 Ana 的手机走到 Ben 的每一台设备?
这一章是初稿,会在写完前八章后再修订。
发一条消息,看起来再简单不过:Ana 把消息发给服务器,服务器转给 Ben。一个下午就能写出能跑的版本,在办公室的 Wi-Fi 下,它也确实能跑。
问题出在办公室之外:
- Ana 在电梯里点了发送,信号断了一秒。消息到底发出去没有?她的 App 不知道。
- App 只好再发一次,结果 Ben 收到两条一模一样的消息。
- Ana 和 Ben 几乎同时说话,两个人看到的先后顺序不一样。
- Ben 电脑上的网页版一整天没打开。打开时它要知道自己缺了哪些消息,又不能把几百个会话挨个问一遍。
- 群里有 500 人,一句话要送到大约 750 台设备;直播间里有 100 万人,一句话要推 100 万次。
- 100 万条长连接长连接long-lived connection设备和服务端之间一直开着的一条 TCP 或 WebSocket 连接。有了它,服务端可以随时主动把消息推下去,不用等客户端来问。在术语表里查看,大部分时候一个字节都不传,却每一条都占着内存。运营商的路由器会悄悄忘掉太久没说话的连接;一次发布重启,十万台手机会在同一秒冲回来。
而用户对聊天的要求很高:不能丢,不能重复,不能乱序,还要马上到。一个网页慢半秒,很少有人察觉;一条消息丢了,对方马上会问“你收到了吗?”。
IM 难,不是因为其中哪一个问题特别难,而是它们同时出现,并且互相牵扯:为了不丢要重传,重传带来重复;为了实时要保持长连接,长连接带来百万连接和重连风暴;为了省电少发心跳,少发心跳又发现不了断线。每解决一个问题,都会引出下一个。
这个系列叫“深入理解 IM 系统”。它一步步讲,一个 IM 系统怎样从“一个下午的版本”长成能扛住这些的样子。开始之前,先把整个系统看一遍。
1. 一个 IM 系统要做到什么
大多数后端是“客户端问,服务端答”:你刷新一下,它返回一页。IM 反过来:Ben 什么也没做,服务端就要把 Ana 的消息主动送到他的手机上。这种能力来自长连接,严格说属于连接层,而不是 IM 本身:同一个长连接层也可以拿来做直播弹幕、推送、设备控制(第 19 章)。IM 是在这个能力之上,再加上一整套保证。
用一句话说:Ana 发出的每一条消息,都要出现在 Ben 的每一台设备上,只出现一次,顺序和所有人看到的一样,哪怕 Ben 的某台设备离线了一整天。
这句话里每个词都有代价:
- “每一条”:网络会丢包,所以要确认ACK(确认)接收方回给发送方的“收到了”。在本书里,服务端的 ACK 表示消息已经写进消息日志,不表示对方已经收到。在术语表里查看和重传。
- “每一台设备”:Ben 有手机,也有电脑上的网页版,它们的状态要对得上。
- “只出现一次”:网络只能做到“至少送到一次”,重传会带来重复,所以要按消息 ID 去重去重dedup同一个消息 ID 再来一次时,返回第一次的结果,不再存一份。网络只能做到“至少送到一次”,去重让每条消息只显示一次。在术语表里查看,让它只显示一次。
- “顺序一样”:每个人的时钟都不一样准,所以不能按时间戳排。
- “离线一整天”:回来时要知道自己缺了什么,并且高效地补上。
再把“Ben”换成“一个 500 人的群”,或者“一个 100 万人的直播间”,同样的要求就变成了完全不同的工程问题。
2. 贯穿全书的例子
这个 App:一个普通的社交聊天 App,有单聊,也有最多 500 人的群;手机、电脑、网页都能用。它没有名字,免得像任何一个真实的产品。
人物:Ana 和 Ben 互相聊天,也都在“徒步群”里(500 人)。Ben 有一部手机,还有电脑上的网页版。
数字:全书共用一套假设,每一章的估算都从这里来。比如:高峰时 10% 的用户在线;每人每天发 40 条消息;每人平均 1.5 台设备;每条消息约 200 字节;丢包率 10%(故意定得很高,好让故障看得见,好的网络远低于这个数)。所以徒步群里的一条消息,要写进 500 个人的信箱信箱inbox每人一份的收件索引,记着“有哪些新消息要给你”,有自己按人递增的序号。写扩散时写它,设备按同步游标从它这里拉。在术语表里查看,推到大约 500 × 1.5 = 750 台设备。到 v3,这个 App 有 1,000 万日活用户,高峰时 100 万人同时在线。
这些都是为了举例选的整数,不是任何真实公司的数据。
3. 整个系统,和一条消息的旅程
下面这张图是这个系列最后会搭出来的系统(主干的 v3 版本),按职责分层画出:
- 最上面是两台设备:发出消息的 Ana,收消息的 Ben。
- 跨过公网,是 IM 服务端的四层:连接层(为每台设备保持一条长连接)、消息层(收下消息)、分发层(送到每一台设备)、存储层,最底下是各层共用的基础设施。
- 左边一列是业务层:账号、好友和群、审核这些规则。它在旁边,因为消息并不从它这里流过,而是各层需要时来问它;它也是改得最频繁的部分,放在旁边,其他几层才能保持稳定。
- 虚线框里的业务系统(你的 App 后端)和系统推送系统推送OS push通过苹果 APNs、谷歌 FCM 或手机厂商的通道,发给没有在线连接的手机的通知。它属于第三方,尽力而为,不保证送达。在术语表里查看(苹果、谷歌、厂商)不属于 IM。
消息层和分发层之间有一条点线:上面是“收下”,发送方等着它回 ACK;下面是“送达”,从这里起是异步的。消息日志消息日志message log持久化、只追加的队列,收和发之间的交接点。消息写进去才算收下,服务端这时回 ACK。通常建在消息队列上,比如一个按会话分区的主题。在术语表里查看就是两边唯一的交接点。业内常说的“接入层、逻辑层、存储层”里,连接层就是接入层,消息层和分发层合起来就是逻辑层。
先看全图,点任意一层或卡片看它做什么、难在哪。然后点“跟着这条消息走一遍”:Ana 在徒步群里问了一句“周六还去吗?”,图会把它走过的 8 步一步步点亮。首页简版的 4 步,对应这里的 1–2、3–4、5–7 和 8。
一条消息怎样穿过系统:先可靠地收下(步骤 1–4),再异步地送到每台设备(5–7);离线的设备回来时自己拉取(8)。
- 发送:消息从 Ana 的长连接进入网关
- 网关看会话 ID,转给负责这个会话的消息服务
- 先看消息 ID 最近见没见过(重发就原样回上次的序号和 ACK;更晚的重发由存储里的唯一键挡住),再检查能否发言、分配会话内的序号,写入消息日志(持久化的队列)
- 写入成功后,消息服务回 ACK:发送方只等到这一步
- 分发层从日志读取,写入消息存储,再去分发(从这里起是异步的)
- 确定接收方,扩散(写扩散时写入每人的信箱),查在线路由表
- 经 Ben 手机所在的网关推送。没有在线连接的手机改走系统推送,它只是提醒;网页这里不用系统推送,下次打开时补齐
- 离线的设备回来后带着同步游标来拉(游标指向它在自己信箱里读到的位置),同步服务从信箱(写扩散)或群的时间线(读扩散)返回缺的消息;在线的设备发现会话序号断档也会去拉,推送和拉取重叠时按序号去重
蓝色实线:消息的路径蓝色虚线:拉取 灰线:调用、写入、通知虚线框:IM 之外点线:同步与异步的分界
业内常说的“接入层、逻辑层、存储层”:连接层就是接入层,消息层和分发层合起来就是逻辑层。消息日志通常用基础设施里的消息队列实现,比如一个按会话分区的主题。
点任意一层或卡片,看它做什么、难在哪、在哪几章深入讲。
几个词先说清楚,后面每一章都会用到(全部术语见术语表):
- 消息日志是收和发之间的交接点,消息写进去才算收下;消息存储是长期保存的历史(每个会话一条时间线);信箱是每个人一份的收件索引,记着“有哪些新消息要给你”。
- 写扩散:发的时候给每个成员的信箱各写一条,读的时候只看自己的信箱。读扩散:消息只在群的时间线里存一份,每个人读的时候自己去取。500 人的徒步群用写扩散;更大的群写不起,改用读扩散(第 10 章)。
- 两种序号:群里的序号(4,811、4,812……)决定这个群的顺序,用来排序和去重;信箱有自己按人递增的序号,每台设备的同步游标就记在它上面。
这里只是走一遍,看每件事发生在哪里。从第 1 章开始,每一章会拿掉其中一个机制,让你在模拟器里亲眼看到会出什么问题。
4. 群和直播间:分发层在这里分岔
同样是“一条消息发给很多人”,群和直播间走的是两条不同的路,差别只在分发这一段。注意 100 万观众的直播间已经是另一种产品了:它相当于这个 App 高峰时的全部在线人数。图里假设一个热门房间每秒 100 条消息,观众分布在 20 台网关上,每台 5 万个连接。
群里每个人都重要,每一条都要送到、都能补齐,所以要逐个成员去找设备、逐个写信箱。直播间里一百万人同时在看,屏幕一次只显示十几条,晚到的消息已经没有意义,所以只找网关网关gateway连接层的服务器。每台持有大量长连接,负责鉴权、心跳和收发,不看消息内容。它是服务端里少数有状态的部分:重启会断开身上所有的连接。在术语表里查看、不找人,允许丢。第 40 章会从数字一步步推出这个设计。
直播间只是其中一个例子。最后几章(第 38–44 章)还会换成别的产品,看同样的决策怎样反过来。
5. 这个系统是怎样长大的
上面的图是终点。这个系列从起点开始:跟着一个聊天 App 从 1,000 个用户长到 100 万人同时在线。四层从一开始就都在,只是一开始挤在一个程序里;每一次增长,都会逼着其中一部分被拆出来、做得更深。切换下面的版本看看:
1,000 用户“App 里要加个聊天。”先做出能用的:一个程序加一张表,客户端先轮询,后来改成长连接推送。
6. 怎么读一章
从第 1 章开始,每一章只做一个设计决策,并且都按首页写的那 5 步走:问题,看它坏掉,估算,修好它,代价和其他答案。每章最后,架构图上会多出一块,配一张决策卡。模拟器是一个沙盒,不连真的服务器,可以放心把它弄坏:把丢包率调高,把某个机制关掉,看看会怎样。