IM 融合专题:后端架构师的核心修炼

大家好,这里是「敖行客研发实战日记」GGB。

这次的活儿是把一套外购的即时通讯系统(下称 IM)整体融进我们自研的微服务平台:IM 自己的登录注册整个砍掉,用户体系和平台合成一套,历史聊天数据一条不丢。听起来是"搬家加接线",真动手才发现,最难的不是代码,是两套用户身份攒了几年的历史包袱。下面按踩坑顺序复盘。

一、两套主键:459 处身份引用悬在一根线上

IM 全站的用户身份,是它自己用户表的自增 id------好友、群成员、私聊、群消息、黑名单、收藏,所有业务表的外键都指向这个自增号;而平台侧的用户主键是另一套长整型业务 id。要把"两边的同一个人"真正合成一个人,等于要给 IM 整个社交图谱的主键换血。

把代码整个扒了一遍,先得到一个吓人的数字:全项目 459 处身份引用,散在 21 个文件里。但继续扒下去发现天大的好消息------459 处全部经由"会话上下文取当前用户 id"这一个出口流进各张表,没有一处绕道。也就是说,根本不用碰那 459 处业务代码:只要改"进入会话的那个值",再把存量数据做一次 id 重映射,下游原封不动地跟着新值走。方案的核心就此定型:平台用户表是唯一身份,IM 用户表降级成"扩展资料表",主键直接等于平台用户 id。

**经验:**融合两套系统之前,先找"身份的唯一出口"。出口收得越窄,融合的代价越小;身份取值散落得到处都是,那才是真正的灾难。
图 1:IM 所有身份引用经由会话上下文单一出口流入各业务表

二、注释会说谎:号称"桥接列"的字段只填了 4 行

IM 用户表里有一列,注释明明白白写着"平台用户 id",看起来天生就是为融合准备的桥。差点直接拿它当迁移依据------好在动手前先跑了一轮只读诊断:35 个 IM 用户里,这一列只填了 4 个。再回头翻代码,更狠:IM 自己的代码从头到尾没有任何一处写这个列,它是当年不知道谁手工补的数据。

**经验:**注释和字段名描述的是"愿望",代码和数据才是"事实"。任何要拿来当迁移依据的字段,先数一遍填充率,再确认到底有没有人在写它。

三、数字虚高二十倍:JOIN 扇出与逻辑删除的幽灵

正式迁移前做匹配预演:明明只有 35 个用户,预演统计却跑出 780 条匹配。数字对不上,立刻停手。

根因有两层。第一层是统计 SQL 里四个 LEFT JOIN 发生了笛卡尔扇出,一个用户的手机号能 JOIN 出几十行;第二层才是真凶------平台用户表里躺着大量"逻辑删除"的历史账号,它们还揣着和活人一模一样的手机号和用户名,是这批幽灵把 JOIN 撑爆的。把逻辑删除的账号滤掉再跑,数字严丝合缝:35 = 16 个能匹配 + 19 个纯 IM 历史孤儿,且无一冲突。

这一步在迁移脚本里被做成了硬性"STOP 闸门":只读诊断的数字对不上总量,后面的破坏性阶段一律不准执行。事后看,这道闸门至少避免了一次"把两个人的聊天记录并到一个人头上"的事故。

**经验:**数据迁移脚本必须分段执行,且第一段永远是只读诊断;预演数字和总量对不上,说明有你还没看见的数据质量问题在前面等着。
图 2:LEFT JOIN 扇出示意------逻辑删除账号携带重复手机号撑爆匹配统计

四、匹配规则:从四级优先级砍到一条

最初的匹配设计很"完备":桥接列、手机号、用户名、邮箱四级优先级层层兜底,尽可能多地把人对上。和项目经理对完,最后砍成一条:未删除账号且手机号一致才映射,其余一律不映射,宁可留孤儿。

孤儿并不可怕:老 id 和新体系的 id 永不冲突,历史消息原样可查,无非是这批账号没有平台身份、登不进来。错误映射才可怕------把 A 的聊天记录挂到 B 头上,在公司里这叫事故。为了保险,映射表上还压了一条唯一约束:任何情况下都不允许两个 IM 账号并到同一个平台账号,撞上就报错停下,绝不静默吞掉。

**经验:**迁移匹配规则,"简单且可解释"压倒"覆盖率高"。每多一级模糊兜底,就多一分把人对错的概率。

五、差点白干三周:动手之前先盘家底

用户合并方案定了,下一步是把 IM 从新框架降级改造成平台的老框架微服务:343 个 Java 文件,包名、依赖体系、模块拆分全要动,怎么估都是两三周起步的体力活。

动手前习惯性翻了翻历史工程,结果发现一个月前有个实验版本已经把这件事干完了:降级、五模块拆分、连"共享会话登录"都写好了。把两边的类集合做了个 diff:341 对 344,业务类几乎完全一致,差异清一色是启动类和配置类。于是策略从"从头降级"变成"整目录搬移加三处构建配置",第一次编译就通过,预估三周的活变成了一天。

**经验:**接到"体力活"先别埋头干,花半小时盘一遍家底。团队的历史分支和旧工程里,往往埋着已经趟平的路。
图 3:两条路线对比------从头降级改造 vs 复用历史版本整体搬移

六、最后一个急转弯:把"同步"整个扔掉

资料一致性的初版方案是"改哪同步哪":平台改了昵称头像,就调接口把 IM 侧的数据刷一遍,登录时兜底同步一次,搜索时再落一次库------三条链路维护两份数据。写到一半越写越不对劲:这分明是在用三个补丁去掩盖"数据存了两份"这个根问题。

推翻重来。IM 侧所有"这个人叫什么、头像是什么"的展示读取,收拢到一个资料门面:读的时候实时批量取平台数据,平台查不到的历史孤儿账号回落本地兜底。IM 自己的用户表彻底降级成"扩展资料表",只存 IM 专属的设置项;平台侧一行代码都不用改。"同步"这个概念直接消失了------不存在同步不同步,因为数据只有一份。

顺带着,"登录"的认知也被扭了过来:融合之后不存在"平台登录之后 IM 再自动登录一次"这回事。两边共享同一套会话,登录了平台,访问 IM 天然就是登录态;真正要接的只剩前端拿一张临时凭证去建立长连接,一个接口的事。

**经验:**当你发现自己在写第三条同步链路的时候,停下来想想能不能改成"只有一份数据"。同步是补丁,单一数据源才是解。
图 4:资料读取链路------展示数据实时取自平台,孤儿账号本地回落

七、小结

图 5:本次融合问题总览(现象 → 根因 → 解法)

  • 融合两套系统,先找"身份的唯一出口":出口越窄,换血的代价越小。

  • 迁移必须设只读诊断闸门:数字对不上就停,别让破坏性操作跑在疑问上面。

  • 注释、字段名、文档都会说谎,代码和数据不会:迁移依据只认查证过的事实。

  • 匹配规则宁简勿全:留下孤儿是遗憾,把人对错是事故。

  • 动手之前盘家底:历史工程里往往埋着已经趟平的路,半小时能省三周。

  • 能实时读就不要同步:单一数据源,永远优于多份拷贝加一堆补丁。

至此,用户体系合一、历史数据分毫未丢、一次登录全端通行,IM 从一座孤岛变成了平台里的一个普通微服务。融合这种活没有银弹,能做的就是让每一步都踩在"验证过的事实"上,而不是"看起来对"的假设上。

相关推荐
进击的程序猿~1 小时前
Go Slice源码深度解析指南
开发语言·后端·golang
Hesionberger1 小时前
快速求解完全平方数的最少数量
开发语言·数据结构·python·算法·leetcode·c#
翔云1234561 小时前
MySQL 中 CURRENT_TIMESTAMP 到底取的是 INSERT 时间,还是 COMMIT 时间?
数据库·mysql
HAPPY酷1 小时前
【ROS2】16G 内存笔记本跑 ROS2 仿真?VMware 虚拟机“保姆级”配置指南 (R9 7940HX + RTX 4060)
java·linux·ide·windows·pycharm
矜持的左手1 小时前
硬件故障后数据文件大小不对故障处理—Oracle碎片扫描恢复
数据库·oracle
欢呼的太阳9 小时前
数据库设计Step by Step (10)——范式化
服务器·数据库·oracle
折哥的程序人生 · 物流技术专研9 小时前
第4篇:Lambda 简化策略模式(Java 8+)
java·设计模式·策略模式·函数式编程·lambda·代码简化·扩充系列
researcher-Jiang10 小时前
高性能计算之OpenMP——超算习堂学习1
android·java·学习
喜欢的名字被抢了10 小时前
MySQL基础入门:从零理解数据库与SQL
数据库·mysql·教程