第二个通宵:我试了三种方案,都翻车了第三个通宵:终于找到了能用的方案做完了回头看,才懂的道理
"会话列表那个红色数字,群里超过99就显示99+,对吧?跟某信一样。"
产品经理丢过来这句话的时候,我正在喝咖啡。看了一眼需求,心里想:不就是个未读计数吗?收一条消息加1,点进去清零,撑死了把上限卡到99完事。
我当时的内心OS:这活儿,半小时搞定。
呵,天真。后来我才知道,就是这个看起来人畜无害的小红点,让我熬了整整三个通宵。
第一次上线,测试姐姐就来找我了。
"手机上看完了,平板打开还顶着3条未读。""有人撤回了消息,未读数没跟着减。""网络抖了一下重连,红点从5跳到8又跳回5。"
我整个人都懵了。不是,它就是个计数器啊,怎么还能自己玩出花来?
后来我才意识到,未读数看着是个数字,本质上其实是"某个用户在某个会话里还有多少条消息没看过"这个命题的分布式一致性问题。它横跨客户端、接入层、存储,多端要对齐,弱网要自愈,还得扛住群消息的扇出量级。
![]()
我犯的第一个错误:以为它只是前端的活儿。实际上,这是整个后端架构都要参与的深坑。
先说第一个方案——客户端自己算。服务端不管,客户端收到消息就本地+1,点进去清零。听起来是不是很清爽?
坑在哪儿呢?多端不一致。手机读了,平板没收到通知,红点还在。用户会怀疑你是不是漏消息了。而且一旦重装App,所有未读数归零,哪怕群里还有几百条没读。
好,那我上第二个方案——服务端存计数器。每个用户的每个会话存一个unreadCnt,服务端说了算。
这回多端一致了,但新问题来了:不幂等。
打个比方,一条消息因为网络重试被处理了两次,计数器就多加了一次。你以为用户只收到一条消息,但红点已经+2了。而且群消息一发就是上千人,每次都要写计数,量一上来直接撑不住。
换个说法就是:一个1000人的群,发一条消息,数据库就要做1000次写入。你扛得住吗?反正我的服务器扛不住。
![]()
最后我们上了混合模型——已读水位 + 计数器的组合。
核心思路是把"计数"和"已读边界"拆成两个东西:
- 已读水位:记录用户在这个会话里读到哪个序号,只升不降
- 未读计数器:一个加速用的缓存,不用每次都去算差值
水位"只升不降"这个设计简直是神来之笔——它让整个流程对乱序和重放天然免疫。无论已读请求怎么乱序到达,水位只接受比当前大的值,小的直接丢掉。
这就把"幂等"从"靠客户端去重"变成了"靠单调水位兜底",可靠性直接上了一个台阶。
另外还有个容易被忽略的细节:不是所有消息都该算未读。撤回消息、系统提示、编辑通知——这些在序号上占了位置,但不能让红点+1。我们加了个反向修正项来抵消这种情况。
就为了一个"跟某信一样"的小红点,我团队花了足足两周。产品经理不会知道,他随口一句话背后是三种计数模型、无数次线上故障排查、和一堆分布式系统的深坑。
但也正是这种经历让我明白了两件事:
第一,看起来越简单的需求,往往越不简单。产品说"就一个计数器"的时候,你要多问一句"什么场景?几个人用?多端同步吗?"。问清楚再动手,比先干了再返工省十倍时间。
第二,技术选型没有银弹。某信用客户端计算方案跑得好好的,因为人家体量大、场景不同。我们这种中小企业IM,用户量级小两个数量级,但对多端一致的容忍度也低得多。适合自己的才是对的。
你现在打开手机,看到那个红色数字,知道它背后经历了什么吗?
你有遇到过类似"看起来简单、做起来要命"的需求吗?评论区聊聊你的血泪史。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.