周五晚上,公司分布式集群迁移项目出了个状况——不是大问题,但很折腾人。
正式迁移的时间定在十一月中旬,这周是做最后一次全量预演。林泠对这个项目不算陌生,从年初开始他就断断续续在跟——分布式存储集群从旧数据中心迁移到新数据中心,数据总量将近两百tb,涉及十二个业务系统、三十多个上下游接口、几百张数据库表的增量同步。他的角色不是项目经理也不是架构师,是数据质量校验组的负责人,专门负责写迁移前后的数据一致性校验脚本,确保每一条记录在搬家的过程中没有被改坏、弄丢、或者莫名其妙地多出几个字节。
预演从周五下午六点开始启动,按照计划应该在周六凌晨六点之前跑完。林泠没打算通宵——他的校验脚本是最后一环,等数据同步完了才轮到他上场,预计最快也要凌晨两三点才能开始跑。他跟项目经理打了声招呼,晚上九点先回家洗了个澡睡了三个小时,闹钟定在凌晨一点。到公司的时候整栋楼只有机房所在的六层亮着灯,走廊里空调外机的嗡鸣声在深夜显得格外响。机房里冷气开得很足,他裹着公司发的薄羽绒马甲坐在工位上,面前三个显示器一字排开——左边是源库的监控面板,中间是目标库的状态日志,右边是他自己的校验脚本窗口。
凌晨两点半,数据同步完成。他开始跑校验脚本。全量校验分了十六个并行线程,每个线程负责扫描一部分数据区块,计算源库和目标库的哈希值然后逐一比对。跑了一个多小时,到凌晨三点四十分的时候,第十六号线程跳了一个警告——三个区块校验失败。三百万条记录里只有三个区块校验失败,比例小到可以忽略不计,换成别人可能就直接标记为“偶发异常,重跑即可”然后关掉屏幕回家睡觉了。但林泠没有。他把这三个区块的源库数据和目标库数据分别导出来,在本地开了一个临时数据库,逐条比对。因为他知道一个原则:凌晨三点发生的校验失败最值得警惕。那个时间点正好是数据库自动备份任务和增量同步任务同时运行的窗口期,两个高负载任务叠加,存储节点的Io压力会达到全天峰值。如果校验失败跟Io峰值有关,说明现有的校验脚本在极端负载下可能存在漏判风险——不是bug,是边界条件没覆盖到。而迁移正式跑的时候数据量比预演大一个数量级,偶发错误的比例哪怕不变,绝对值也会被等比放大。到时候再排查就不是凌晨三点蹲机房的问题了,是整个业务系统可能要回滚。
他在机房的冷气里蹲到半夜四点,一行一行地翻那三个区块的比对日志。源库和目标库的数据一模一样,字段数、记录数、主键值全部对得上,但哈希值就是不一致。他把哈希计算的中间过程拆开一步步排查,最终发现了一个极其隐蔽的问题——预演用的测试数据里,那三个区块的时间戳字段存在毫秒级重复。同一个毫秒内写入了两条不同的记录,校验脚本在分别对源库和目标库做排序比对的时候,因为两条记录的时间戳完全一致,排序顺序在两个库里出现了随机性差异。哪一条排在前面、哪一条排在后面,取决于数据库引擎的内部实现和当时的内存状态,是不确定的。排序顺序不确定,哈希值就不一致。这不是数据本身有问题,是校验脚本的排序逻辑没有考虑到毫秒级时间戳重复的边界情况。
问题定位之后修复起来很快。他在校验脚本里加了一个联合主键排序的次级排序字段——时间戳相同的情况下按主键值排序,确保两个库的排序顺序在任何条件下都严格一致。重新跑了验证集,全部通过。他把排查过程写进文档,标注了问题根因和修复方案,发到项目组的群里,然后关上电脑靠在椅背上揉了揉眼睛。窗外天还没亮,十一月的夜很长。
周六他睡到上午九点才醒。醒来的时候阳光已经从窗帘缝隙里灌进来,在地板上画出一道斜斜的白色光带。他在被子里翻了个身,没有急着起床——通宵之后的第二天,身体需要补觉,肌肉酸软,太阳穴隐约发紧,这是睡眠债还没还清的身体信号。他又躺了半小时才爬起来洗漱。吃早饭的时候林妈煮了粥,他喝了两碗,就着一碟子酱黄瓜吃了半个馒头。胃里暖和了之后人才真正清醒过来。
吃过早饭他靠在沙发上翻了一会儿手机。钓友群里已经开始刷立冬的鱼情了。今天是立冬,二十四节气里冬天的第一个。张磊一大早去了白马河,发了一张自拍——一只手冻得通红,另一只手举着两条加起来不到半斤的小鲫鱼,配文是“立冬第一钓,冻成狗”。老周在湿地钓了一条两斤多的鲤鱼,说上午十点才开口,窗口期明显比霜降短了一截,不到一个小时就彻底停了。陈浩难得发了条长消息,说他按照霜降直播里讲的深水钓位策略在水库试了一次,钓了四条鲫鱼,专门来感谢林泠。还有人直接@他,问立冬怎么没开直播,是不是偷懒了。
他回了一条:“昨天加班到半夜,今天歇一天。”
群里瞬间歪了楼。张磊秒回:“原来大神也要加班,我心里平衡了。”孙正跟了一句,语气里带着调侃:“泠哥你那份工作还没辞啊?我以为你早就是全职钓鱼博主了。固城湖冠军加千岛湖亚军,随便接两个代言不比上班强?”后面跟了一排幸灾乐祸的表情包。老周说:“人家这是真热爱,不是为了赚钱才钓鱼的。”
林泠看着屏幕笑了一下。孙正的调侃他不是第一次听到,自从千岛湖回来之后,隔三差五就有人在直播间或者私信里问他为什么不辞职全职做钓鱼。他的直播间数据在固城湖夺冠之后翻了三倍,千岛湖之后又翻了一倍,现在日常直播在线人数稳定在四五千,高峰时能破万。这个数据放在钓鱼垂类里已经是头部了,接广告、卖渔具、做付费内容,随便哪条路都比朝九晚五赚得多。但他从来没认真考虑过这个问题。不是不信任钓鱼这条路,是他太清楚自己为什么能把钓鱼做到今天这个程度。
他靠在沙发背上,看着天花板发了一会儿呆,然后拿起手机打了几个字:“工作是不能丢的。钓鱼是爱好,工作是给爱好兜底的。”
发完这句他把手机放在茶几上。这句话不是随口说的漂亮话。三年了,本职工作他从来没耽误过——加班归加班,该交的文档按时交,该修的bug按时修,该上的线按时上。哪怕是在千岛湖拿了亚军之后,哪怕是直播间在线人数破了万之后,他周一早上的闹钟照常响,工位上的显示器照常亮。朝九晚五的稳定工资是他能心无旁骛钓鱼的底气——不用靠钓鱼赚钱,就不用为了流量去整活,不用为了变现去割韭菜,不用为了热度去制造冲突,不用在直播间里卖那些自己都不用的廉价饵料。他可以在直播间花半小时讲窗口期的水温梯度理论,不担心观众跑光——跑光了也没关系,明天照样上班拿工资。这份“没关系”的自由,是用本职工作的稳定换来的。爱好最怕的不是没时间,是被生计绑架。一旦钓鱼变成必须赚钱的手段,那些数据、那些验证、那些本可以纯粹出于好奇心的探索,就会被流量指标和变现压力碾得面目全非。
他把手机上的群消息又刷了两页,张磊还在那里说等开春了组队去水库,孙正说我赞助饵料。他回了个“好”,然后退出微信,打开笔记本把昨晚排查校验失败的过程整理成了一条备忘,写完合上笔记本放在茶几上。立冬的太阳透过梧桐树光秃秃的枝条照进来,在地板上印出一片交错的影子。