第 13 章
群资料与群成员同步
Ana 打开徒步群,每条消息要显示发的人在群里叫什么,输入 @ 要列出群里的人。手机上这份成员名单从哪来?怎么知道它变了?
这一章是初稿,会在写完前八章后再修订。
第 12 章讲的是服务端:谁在群里、每人能看哪一段。可 Ana 的手机也要一份。
打开徒步群,每条消息要显示发的人在群里叫什么;输入 @,要列出群里的人;会话列表里还要有群名、群头像。这些来自两份数据:群资料(群名、头像、公告)和成员名单(每人的群昵称、角色)。每人自己的昵称和头像是用户资料,另一回事,这一章不讲。
1. 每次打开都拉一遍
最简单的做法:打开群时,向服务端要整份成员名单。一个成员约 40 字节(用户 ID、群昵称、角色),徒步群 500 人就是 20 KB。
一天打开 20 次,就是 400 KB,是 Ana 一天收到的文字(第 11 章)的 7 倍多。服务端每次也要读 500 行。可这一天里,成员只变了几次。
省流量有两种常见做法:
- 缓存一小时,过期才重拉。流量少了一半多,可缓存期间的变化看不到:新进群的人 @ 不到,刚改的群昵称还是旧的。
- 带上手里那份的哈希,没变就回“没变”。名单总是新的,可只要变了一个人,就整份重发:徒步群一天变 6 次,就是 120 KB。
下面是 Ana 的一天,三个群,每次打开一根柱,柱高是这次为名单下载了多少:
一次打开下载的(高度)用了旧名单成员变化
这一天三个群共下载 523.8 KB;每次打开名单都是最新的。
- 每次全拉:一天 524 KB。
- 缓存 1 小时:少了一半,可有几次打开(红圈)用的是旧名单。
- 带哈希:只有变了之后的那次是高柱,一天 144 KB。
哈希只能说“变没变”,说不出“变了哪几个”。
2. 办法:每个群一个成员版本号
- 服务端: 群的那一行(第 12 章取 seq 时锁的那一行)多记一个成员版本号成员版本号member version每个群一个递增的号:进群、退群、改群昵称、改角色都加一,并写在那个成员的行上。手机存着名单和它的版本号,打开群时带上,服务端没变就只回版本号,变了只回变了的行(第 13 章)。在术语表里查看。进群、退群、改群昵称、改角色,都在同一个事务里给它加一,并把新版本号写在那个成员的行上(退了的行不删,第 12 章)。
- 手机: 存一份名单,记着它是第几版。打开群时带上版本号:“我是第 37 版。”
- 回复: 没变,就回一个版本号,4 字节;变了,只回版本号比 37 大的那几行,每行约 44 字节。成员表上按“群 + 版本号”建个索引,读的就只是这几行。
在上面的图里选“带版本号”:同样的一天,三个群加起来半 KB 左右,每次名单都是新的。
这和第 11 章的记录版本是同一个想法:那个版本号按人记,贴在会话记录上;这个按群记,贴在成员的行上。
3. 群资料和“我加入的群”走会话记录
群名、头像、公告改了,会话列表要跟着变,不能等到打开群才知道。它们变得很少,就直接写进每个成员的会话记录,像第 11 章的置顶、免打扰一样涨记录版本,会话同步就把它带到每台设备。群里另发一条系统消息“Ana 把群名改成‘周六徒步’”,给人看。
新设备第一次登录时,群名和头像跟着会话记录一起下来(第 10 章)。
“我加入的群”也不用另一个版本号。Ana 进一个群,就多一条会话记录;退群,那条标成已退出(第 11 章);两样都涨她的记录版本。通讯录里“我的群聊”,就是会话记录里她还在的那些群。在会话列表里删掉一个群聊,只是把记录标成隐藏,人还在群里。
于是一共两个版本号,各管一摊:
| 版本号 | 按谁记 | 管哪些数据 |
|---|---|---|
| 记录版本(第 11 章) | 按人 | 会话列表、我加入的群、群名头像公告 |
| 成员版本号(这一章) | 按群 | 一个群的成员名单 |
进群、退群本来就是系统消息(第 12 章),群开着的时候,手机能顺手把名单改了。版本号没跟着涨,下次打开会再收到这几行,覆盖一遍,不出错。改群昵称这种不发消息的变化,等下次打开时按版本号补上。
4. 算一下
- 一次打开: 全拉,徒步群 20 KB;带版本号,没变时 4 字节,变了一个人约 44 字节。
- 一天: 图上三个群,全拉 524 KB,带哈希 144 KB,带版本号约 0.5 KB。
- 服务端: 全拉每次读 500 行;带版本号,没变时只读群的那一行。
5. 代价
- 成员表多一列、多一个索引。 每次成员变化,多改那个成员的一行。
- 手机多存一份名单。 只存打开过的群就行。
- 不发消息的变化有延迟。 群开着的时候改了群昵称,要到下次打开才看到;想立刻看到,就得多推一个通知。
- 退群很久的行挪走以后, 版本太旧的手机看不出谁走了,只能整份重拉一次。
6. 其他答案
- 每次打开全拉。 小群就这么做也行:家人群 12 人,一次 480 字节。
- Telegram:小群带版本号,大群带哈希。 普通群的成员名单(
chatParticipants)带一个群版本号version,每条成员变化的更新(如updateChatParticipantAdd)也带着它,客户端据此丢掉旧的,和这一章的成员版本号是一回事,只是变化是推过来的。超级群可能有几十万人,成员按页拉(channels.getParticipants),带上手里那页的哈希,没变就回channelParticipantsNotModified;一旦变了,整页重发。 - 只拉看得到的人。 Matrix 可以只下发当前这页消息的发送者的成员信息(lazy-loading room members),完整名单要用时再拉。适合很大的房间。
- 每次变化推给每个成员。 名单总是新的,可一次变化要推全群,离线的人回来还是得补。
- 超大群。 几万人的群,名单不整份下发,按需分页、搜索,留到第 40 章。
7. 这一章的决定
群里的事,服务端和手机都清楚了。可私聊呢?陌生人能不能给 Ana 发,被她拉黑的人还能不能发,买家能不能找卖家,每个产品都不一样。下一章:谁能给谁发消息。