“节点拒绝选举请求,只有两种可能。”裴衍之说,“要么是请求本身有问题,要么是节点状态有问题。你检查过节点的状态机了吗?”
“检查了。状态机正常,日志索引一致,任期号一致。”
“那请求本身呢?节点三发送的选举请求里,任期号是多少?”
程司白翻到日志的对应行。“任期号是七。”
“其他节点的任期号呢?”
“也是七。”
“那为什么拒绝?”
“我不知道。”
这三个字说出来的时候,程司白觉得自己的喉咙被什么东西卡住了。她是一个架构师,她的工作就是知道。知道系统怎么运行的,知道故障怎么发生的,知道问题怎么解决的。她不知道的时候,就没有人知道了。她是架构层的负责人,她是最后一道防线。如果她不知道,那道防线就破了。
她盯著萤幕上那几行拒绝记录,手指放在键盘上,没有敲。日志在她眼前变得模糊了,不是因为萤幕的问题,是因为她的眼睛里有什么东西在聚集。她用力睁大眼睛,不让那些东西落下来。
裴衍之站起来,走到她身后,弯下腰看著萤幕。他的肩膀离她很近,近到她能感觉到他的体温。那股柠檬和雪松的味道从他衣服上散发出来,很淡,淡到几乎闻不到,但她闻到了。
“从头推演一遍。”他说,“不要看日志,推演算法。”
程司白闭了一下眼睛,把那点模糊的东西压回去。她打开一个空白的编辑器,开始写推演的步骤。
“Raft算法的领导者选举规则:候选人发送选举请求,请求里包含自己的任期号和最后一条日志的索引。其他节点收到请求之后,比较请求中的任期号和自己的任期号。如果请求中的任期号更小,拒绝。如果更大,同意。如果相等,比较日志索引。如果请求中的日志索引大于等于自己的日志索引,同意。否则,拒绝。”
她一边说一边在编辑器里写,把规则一条一条地列出来,像在写技术文档。字迹工整,逻辑清晰,每一个条件分支都写得很清楚。
“节点三的任期号是七,其他节点的任期号也是七,所以任期号相等。接下来比较日志索引。节点三的最后一条日志索引是多少?”
她翻到日志里的那一行。“一万两千三百四十五。”
“其他节点的呢?”
“也是一万两千三百四十五。”
“日志索引相等,所以其他节点应该同意节点三的选举请求。但它们拒绝了。”裴衍之的声音还是很平静,但程司白听到了他声音底下那层东西——不是疑惑,是某种正在成型的确定。“所以问题不在规则里,在规则之外。”
程司白的手指在键盘上停了一下。“规则之外?”
“规则之外只有一种可能。”裴衍之站直身体,双手交叉在胸前,“节点三发送的选举请求里,日志索引不是一万两千三百四十五。”
“日志里写的是一万两千三百四十五。”
“日志里写的是节点三认为自己发送的值。但实际发送的值可能不一样。”
程司白愣住了。她想到了那个可能性——序列化错误。节点三在构造选举请求的时候,把日志索引从整数类型转换为字节流的时候出了错。整数一万两千三百四十五转换为字节流,再转换回来的时候,可能变成另一个数字。如果变成了一万两千三百四十四,其他节点比较的时候发现请求中的日志索引比自己的小,就会拒绝。
她开始检查序列化的代码。一行一行地看,一个函数一个函数地看。看到第三个文件的时候,她找到了。
一个边界条件没有处理。
整数转字节流的函数在处理大于一万的数字时,会丢失最低位元组。一万两千三百四十五转换之后再转换回来,变成一万两千三百四十四。差了一位。就差了一位。
她的手指停在那一行代码上。这行代码是她写的。三个月前,她在优化序列化性能的时候重写了这个函数,为了减少内存分配,她手动实现了字节转换。她测试了正数、负数、零、边界值,但她没有测试一万以上的数字。她以为那个范围是安全的。
她错了。
“是我写的。”她的声音很轻,轻到像是在自言自语。裴衍之没有说话。他站在她旁边,低头看著萤幕上那行代码。
“三个月前我重写了这个函数。我没有测试到位。这个Bug是我的。”她一个字一个字地说,每一个字都说得很清楚,清楚得像在念一份事故报告。
办公区里很安静。何悦和陆维安不知道什么时候停止了讨论,两个人站在不远处,看著程司白的方向。何悦的表情很担心,她想走过来,但陆维安拉住了她,摇了摇头。
程司白靠在椅背上,看著天花板。那盏旧灯管还是那样,光线不均匀,中间亮两边暗。她盯著那盏灯,觉得自己就像那根灯管——看起来还在亮,但里面已经坏了。有一段灯丝断了,电流过不去,光线不均匀,随时会彻底灭掉。
“我重写那个函数的时候,觉得自己很聪明。”她说,声音在空荡荡的办公区里回荡,像一段没有回声的录音,“手动实现字节转换,减少内存分配,提升性能。我觉得这才是架构师该做的事——优化,改进,追求极致。但我连基本的边界测试都没有做。”
她停了一下。喉咙里那个卡住的东西更大了,大到她几乎说不出话。
“我可能不适合做架构师。”
这句话说出来的时候,何悦忍不住了。她走过来,站在程司白面前。“程姐,你不要这么说。谁都会犯错——”
“你不懂。”程司白打断她,声音比之前更哑了,“这不是犯错。这是失职。我是架构组的负责人,我的代码跑在核心业务上。如果我连基本的测试都做不好,我不配坐在这个位置上。”
何悦张了张嘴,不知道该说什么。她转头看向裴衍之。
裴衍之坐在程司白旁边的椅子上,没有说话。他的表情很平静,平静得像一面没有波澜的湖。他看著程司白,看了大概十秒,然后打开自己的电脑,找到那段序列化的代码,开始看。
“这个函数是你三个月前写的。”他说。
“对。”
“当时你在做什么项目?”
“架构组的性能优化专案。目标是把序列化耗时降低百分之三十。”
“你做到了吗?”
程司白转头看著他。他的表情还是很平静,平静得像在问一个技术问题。但她的眼睛里有什么东西在动,不是眼泪,是某种被压了很久之后开始松动的东西。
“做到了。降低了百分之三十四。”
“那你没有失职。你完成了目标。这个Bug是边界条件没有覆盖到,不是逻辑错误。边界条件遗漏在复杂系统里是正常的,不是失职。”
“正常?”程司白的声音忽然提高了半度,在安静的办公区里显得很刺耳,“灰度上线失败了,数据不一致了,系统崩了。你告诉我这是正常的?”
“我说的是边界条件遗漏是正常的。不是说故障是正常的。”裴衍之的声音还是很平,平得像在解释一个技术概念,“你写了一个性能很好的函数,它跑了一万两千次都没有问题。第一万两千三百四十五次出问题了。这说明你的测试覆盖率不够,需要补充。不是你不配做架构师。”
程司白看著他。她的嘴唇在抖,不是冷,是某种她控制不住的东西。她想说什么,但喉咙里那个卡住的东西太大了,大到她一个字都说不出来。
裴衍之站起来,走到她面前,蹲下来。他的眼睛和她平视,很近,近到她能看清他瞳孔的颜色——深棕色的,在灯光下泛著一点点暖光。
“程司白,你不是神。你不需要完美。”
这句话说出来的时候,程司白觉得那个卡在喉咙里的东西碎了。不是消失了,是碎了,碎成很多小块,每一块都在扎她的喉咙,扎得她生疼。她的眼眶热了,视线模糊了,那些被她压了一整个晚上的东西终于压不住了。
她没有哭。她只是坐在那里,眼睛里有东西在闪,嘴唇在抖,手指攥著键盘边缘,指节发白。
“裴衍之。”她说。声音很哑,哑到不像她自己的声音。
“嗯。”
“我其实不是不相信共识算法。”
裴衍之蹲在她面前,没有动。他的手放在膝盖上,手指微微张开,掌心朝上。那是一个开放的姿势——不设防,不防御,不保护自己。
“我不相信的是——”她的声音断了一下,像一段被中断的传输,信号丢失了,数据不完整,“有人会一直选择我。”
办公区里很安静。何悦站在不远处,手摀住了嘴。陆维安低下了头。空调的嗡嗡声在这一刻变得特别响,像一段填满了所有空白频率的白噪音,没有旋律,没有节奏,只有持续的、不变的、压倒一切的声音。
裴衍之沉默了很久。
这很久里,程司白看著他的眼睛。那双深棕色的眼睛在灯光下很亮,亮得像萤幕上一个稳定的信号——没有波动,没有异常,一切正常。他在看她,没有移开目光,没有闪躲,没有逃避。他只是看著她,像一个在接收数据的节点——信号来了,他收到了,他在处理。
他伸出手,握住了她的手。
她的手很凉,他的手很暖。他的手指穿过她的指缝,扣住了她的手背。力度不重,但很稳,稳到像一个被确认了的连接——握手成功,协议协商完成,数据传输通道已建立。
“程司白。”他的声音很轻,轻到只有她能听见,“你知道共识算法的前提是什么吗?”
她摇了摇头。手指在他掌心里动了一下,没有抽出来。
“是节点之间要互相信任,愿意交换信息。”他看著她的眼睛,一个字一个字地说,“你从来不跟我交换信息。”
程司白觉得自己的心脏被什么东西击中了。不是痛,是一种她说不清楚的感觉——像一个被她关了很久的门被打开了,光从门缝里照进来,刺眼,但她不想把门关上。
她的手指在他掌心里收紧了。不是用力,是回应。像一个节点收到了另一个节点的确认信号之后,发回的那个ACK——收到,理解,同意。
裴衍之没有再说话。他蹲在她面前,握著她的手,掌心贴著掌心,手指扣著手指。两个人的心跳在不同的频率上运行,但通道是开的,数据在传输,共识在达成。
窗外的天开始亮了。不是那种突然的亮,是一种缓慢的、渐进的亮——从深蓝变成浅蓝,从浅蓝变成灰白,从灰白变成一层薄薄的橘色。太阳还没有出来,但光已经到了。光照在办公区的玻璃窗上,照在桌上那个白色的马克杯上,照在两个人交握的手上。
程司白看著窗外的那层橘色,觉得自己像一个刚刚从崩溃中恢复的系统——日志里还有警告,内存里还有碎片,但核心进程已经重新启动了,心跳恢复了,连接重建了,系统在慢慢地、一点一点地回到正常状态。
她没有说话。她也没有放手。
Bug修复之后,程司白没有走。
她把修复后的代码提交了,把测试用例补充了,把边界条件从一万扩大到十万,跑了一遍又一遍。绿色的通过标记在萤幕上跳出来的时候,窗外的天已经从深蓝变成了浅蓝。办公区里只剩她一个人——何悦在凌晨五点的时候被她劝回去了,陆维安也走了,裴衍之去了茶水间。
她靠在椅背上,看著萤幕上那一排绿色的“PASS”。眼睛很干,干到眨眼睛的时候会痛。但她不想闭上。闭上就会看到那行代码——三个月前她写的,她觉得很聪明的,她没有测试到位的那一行。一行代码,让整个系统崩了。一行代码,让她发现自己没有自己以为的那么好。
裴衍之端著两杯水走进来。一杯放在她桌上,一杯自己拿著。他没有说话,只是拉了一把椅子坐在她旁边,和她并排面对著萤幕。
两个人沉默了很久。