暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

由 TCP Handshake 失败引发的思考

Share and Fun喜来分 2021-02-05
376

文章撰写日期:2021 年 1 月 30 日 星期六

建议阅读时长:15分钟


托尔斯泰在《安娜・卡列妮娜》说过一句名言,“幸福的家庭都是相似的,不幸的家庭各有各的不幸”。这句话在各种场合以各种方式被人们演绎,在过去的一个月,我把它演绎为 “幸福的程序猿都是相似的,不幸的猿们各有各的不幸”。这篇文章,就是来给大家分享下这三周的不幸和最后的幸福。


事情大约要追溯到一个半月之前,有个头部国际客户需要进行 POC 支持,既然是头部客户,必然有头部待遇,所以项目经理组织资源迅速到位,按照客户一些初步的意向以及提供的有限文档做出了及时的响应,该开发的开发,该计划的计划,虽然有些忙乱但也都有条不紊。


项目节奏也非常快,两周之后,客户提出了可以联调测试。我们积极配合,组织了攻关小组并且总部冒着新冠疫情强劲反弹的风险,派出了两名工程师飞往疫情严重的欧洲某国现场支持。接下来,按照猿们既定的幸福,那就是设备集成,数据上报,内容展示一气呵成。


然而,不幸从集成开始了... 是的,集成测试,往往是各种奇葩异象风起云涌的舞台,这一次也没有例外,再一次演绎了猿们的另类不幸。


集成测试是固定在了每天下午五点开始,七点结束,计划持续两周,需要将客户的模拟设备集成到我们的 POC 环境,接受设备上报的数据,按照客户提供的协议规范解析数据并且转换为我们内部格式同时走通我们系统的全部流程并通过手机 APP 展示给客户。是的,你又猜对了,这是一个典型的 IOT 项目,POC 的目的就是证明我们的平台能兼容客户的设备和数据,所以看上去一切都尽在掌握。


我们一开始也认为确实很简单。于是轻装上阵,来吧,证书一配,MQTT 一连,完美。然而,猿们的直觉再次验证了你以为很简单的事情往往都不简单。


第一个不简单就来得猝不及防,没想到客户对于自己的模拟设备这么的不熟悉。当然,不熟悉也是正常的,当我们看到屏幕上模拟器那一页几十个五颜六色的按钮,各个按钮可以各种组合点击加上满屏的滚动日志输出,不出一分钟就迷失了。来,上张局部图给大家感受下:



当我们提出来能不能简单测试下 MQTT 连接,先不管数据上报,客户却提出来需要 “花点时间 “配置好模拟设备。配置设备这当然是合理的步骤,可以理解。但是我们却低估了 “花点时间 “的含义... 没想到这一花点时间,三天就过去了... 之中各种错误,包括 Linux 基本命令缺失 (wget 命令标准格式无法使用),SIM 卡网络连接不成功(APN 受限,默认 APN 只能连接到指定的服务器,临时又申请了新的 APN),好不容易网络通了,telnet 也能成功,MQTT connection string 配置好了,可是连接死活无法建立。


好了,啰里吧嗦这么几大段,终于进入了连接环节了,也正式拉开了接下来三周不幸的调试序幕。



第一回合,

陷入证书的死循环


由于模拟器和服务端走的 MQTT 的双向 TLS 认证,因此客户端和服务端都需要证书。一般连接建立不成功,90% 的问题都是在证书问题,要么客户端校验服务端证书不通过,要么服务端校验客户端证书不通过,这都是日常集成中常见问题,也就是打怪途中时不时跳出来的那些个小 BOSS,大家也习以为常。


当然,在这里,还是需要给大家科普下双向 TLS 认证。毕竟是个技术相关的帖子,不能当成小说写。


首先简单解释下 TLS,这个相信大多数人或多或少都听说过,传输层安全协议 (Transport Layer Security),它的前身是安全套接层 SSL(Secure Sockets Layer)协议。顾名思义,传输层安全协议是通过一套加密和签名算法,用来在两个应用程序之间透过网络创建起安全的连线,防止在交换数据时受到窃听以及篡改。可以简单理解在两个应用之间构建了一个加密管道,这个管道里面流动的东西对于应用双方以外的第三方是安全未知的。


TLS 分了几种类型,单向认证,双向认证,也可以不认证。其中单项认证是指的只有一个对象校验对端的证书合法性,通常都是客户端校验服务器的合法性。我们经常用的浏览器访问 https 站点就是这种类型。双向认证指的是相互校验,服务器需要校验每个客户端的证书,客户端也需要校验服务器证书,这种在 IOT 物联网的场景里面非常常见,因为客户端也即设备端接入的网络平台是特定的,设备需要由指定的供应商提供,因此需要有机制能校验设备是不是具备接入特定网络平台的权限,在我们这个场景里面也是需要用到双向认证。不认证最简单,指的是不相互校验证书,但仍然使用 TLS 连接。


在这里就不再进一步解释 TLS 连接里面用到的证书,密钥,公钥,根证书等这些基本概念了,假设读者能有基本的理解。如果大家有兴趣,大家不妨读一读这本书《图解密码技术 第三版》,讲得很透彻。


下图把双向 TLS 认证的大致流程画了出来(摘取自《图解密码技术 第三版》),方便大家理解,后面的大部分环节,也是针对第 (7) 步出现的问题进行描述和解决。



言归正传,调试照例首先是查找日志,当然,查找日志也不是一帆风顺。这里不得不提到客户的模拟器。模拟器虽然滚动输出日志,但并不会把所有日志都缓存住,如果要调取某一时间段的日志,需要在模拟器的命令窗口执行一段命令,而这个命令,客户并不知道,在等待了几天之后,通过模拟器供应商之手,终于把这个命令执行了出来。我们也终于看到了想要的日志记录,是的,小 BOSS 登场了,日志显示的是 “Server CA unknown“。果不其然,客户的模拟器并不认我们根证书签发的服务端的证书,尽管我们的根证书是 public authority 颁发的。


这个问题容易解决,我们需要客户从模拟器导入服务端的根证书成为受信任的根证书即可。本来以为简单的导入操作,在客户模拟器上执行居然又花了几个小时。这个过程依然是说多了都是泪,客户需要手动从命令行执行导入命令。没错,你前面看到的模拟器图形界面,并不支持证书的导入!由于安全原因,在导入之前还需要手动从客户本地将证书上传到客户跳板机,再从跳板机拷贝到客户某台内网服务器,再从服务器导入到模拟器,这一系列的操作可能因为网络超时,分分钟重来,加上客户也不太熟悉命令,经常因为大小写的原因敲错文件名和命令名,导致导入不成功,再加上导入完成之后需要重启模拟器花费若干时间,总之,就是在漫漫等待当中经过不断强化训练,我们竟然不知不觉也掌握了客户模拟器操作命令……  并且能够在客户操作的过程当中及时地指出来各种小错误……


终于,模拟器不再报 “Server CA Unknown“了,但是,连接依然没有建立。这个时候服务端发现了错误,客户端的证书为非授信证书。于是赶紧问,果然客户模拟器的证书是非授信证书机构签发的,简单点说就是私签证书。好吧,于是赶紧让客户把自己的根证书以及二级证书(这里需要注意的是,对于有二级证书的自签证书,需要将根证书和二级证书都导入到 BigIP 才能生效)分享给我们,导入到我们的服务器。


再次尝试,然而,连接依然没有建立,服务端也没有报任何错误。这个时候大家一脸懵逼,发生了什么???两边都没有报错,连接却不能建立,这时候我们开始嗅到了一丝诡异的气息,凭着猿们的直觉,这不是一个简单的小 BOSS。


于是,大家开始迅速讨论,改变战术。问题显然仍然出在 TLS 的双向认证,会不会模拟器本身有问题?这个思路符合 90% 猿们的思路,问题发生了第一判断那必然是对方的问题... 当然,对方是客户,我们不能直接跟客户说你们模拟器不行啊... 于是,委婉的劝了客户,既然模拟器有命令行,看上去也是个 linux 的内核,不如用 openssl 命令在模拟器试试,至少看看 TLS 连接有没有问题?


客户相当配合,这个思路不错,只是…… openssl 命令咋玩?没尝试过…… 额,好吧,甲方 “爸爸” 不能伤,作为曾经在甲方当过 “爸爸” 的我也表示这种情况非常理解,毕竟都是给供应商惯坏了。不多废话,赶紧准备好命令:


openssl s_client -connect <Server_Address>:8883 -key <Client_PrivateKey > -cert <Client_Cert > -CAfile <Server_Root_CA_cert> -showcerts


然后看着客户一行行敲进命令行,再回车,没问题…… 是的,没错,居然连接通了,在我们的服务器端也很明显看到了连接。幸福来的这么迅雷不及掩耳盗铃但是转瞬即逝,唯一让在场的猿们坚定的就是客户模拟器有问题…… 你看,openssl 连接的好好的呀!


当然,依然不能跟甲方 “爸爸” 说,你们模拟器有问题呀,因为 “爸爸” 们需要理解 openssl 成功了,模拟器不成功,这二者之间意味着啥。在 “爸爸” 们的脑袋里面,openssl 就是 openssl,模拟器是模拟器,你凭啥说 openssl 成功了,模拟器就有问题呢?我们模拟器又不是 openssl。就像你不能因为苹果好吃就觉得苹果梨也应该好吃,苹果梨也可以不好吃。当然,这一段是脑补 “爸爸” 们的思路,在脑海的深处,我们分明感受到了这个暗藏在深处的大 BOSS 的那丝寒气。纳尼,作为一群猿们精英,岂能被这种问题干倒?继续冲!于是,这个时候只能使用杀手锏,上 TCPDUMP,看看连接的过程到底发生了啥,先在服务端 TCPDUMP, 发现在 TLS 握手的过程中,客户端证书没有送上来,然后客户端直接终止了握手。是的,客户模拟器在看到服务器返回的证书之后,华丽丽地送上一个 FIN 包 say 了拜拜,就像拜金女看到男友居然买了一个 A 版 LV 包翻了个白眼坐上别人的宝马一骑绝尘…… 再也不见…… 


下图展示了 TCPDUMP 文件的截图,很清晰的看到在 Server 返回了 Certificate Request 之后,客户端发送了 FIN 包终止了连接。读者可以根据本文之前的 TLS 双向连接流程图对应理解。




第二回合,

还是证书


所以,在这种时候,作为猿们的正常思路,会怀疑什么?对,没错,当然还是客户的问题。既然客户端没有发送证书,那是不是证书有问题?既然有问题,那能不能干脆来一套我们签发的证书,服务端 + 客户端都是我们的证书,这样在证书的层面就不会有问题了。


于是说干就干,开始还寄希望客户自己会生成 CSR 给我们用于客户端证书生成,后来一看还得教甲方 “爸爸” 怎么生成 CSR 呢,那算了,干脆一步到位,私钥我们生成,公钥我们生成,证书我们也生成然后客户只需要导入到他们的模拟器就大功告成了。


当然,导入的过程依然不省心,还出现了多次客户端证书跟证书私钥不一致的情况,也就是只导入了我们签发的客户端证书,但是没有成功导入私钥。是的,你不是明明都给客户了,为啥还不一致?你问的没错,可惜,我没有答案...... 只能告诉你,在大 BOSS 挂掉之前,你能看到各种魑魅魍魉出现......


于是再次指导客户先用 openssl 命令,在每次导入之前都检查下证书和私钥的一致性,在这里也分享一个命令检查证书是否一致,命令如下:


openssl rsa -noout -modulus -in certificate.key | openssl md5

openssl x509 -noout -modulus -in certificate.pem | openssl md5


然而,所有的这些都尝试完之后,还是老问题,TCPDUMP 文件显示客户端证书还是没有上报成功。这个时候连客户 “爸爸” 找来的模拟器供应商进来支持的工程师都是一筹莫展,所有的日志都打开了,也看不到更多的错误提示,为什么客户端不上传证书?这个时候已经不是 A 货 LV 了呀,明明都是正牌专卖店签售,难道是给的姿势不对?



第三回合,

我没办法证明你不行,那你能不能证明下你行


这个时候就比较尴尬了,猿们继续坚守直觉,客户模拟器肯定哪里出了问题。为什么大家有这个直觉,还有一个更重要的问题,在模拟器的日志里面,很明显看到握手阶段,模拟器收到了服务端证书之后,打印了写证书成功(写套接字,对应发送证书),但是紧接着来了一条 connection timeout,然后模拟器就主动中断了握手,然后重新再次连接,如此循环反复。一方面,日志里面明明有 connection timeout,可是模拟器的支持工程师却解释不出来原因,也提供不了更多证据为啥会有这个错误。另一方面,从模拟器端 TCPDUMP 到的文件,完全没有看到有任何 TCP 包往外发,为啥模拟器日志里面能输出写证书成功的日志呢?无事献殷勤非奸即盗啊!肯定有问题!


所以,既然所有证据都不能让客户信服模拟器有问题,那么,不如你来试试它没问题吧。


确定了这个思路之后,开始建议客户连接自己的服务器给我们看看,然后抓取 TCPDUMP 包进一步分析差异。


当然,这里需要赞扬下客户的配合度以及敬业度,其实写的这么潇洒,但是调试的过程是相当枯燥,客户也很有耐心配合提供各种信息,反馈甚至也推迟午餐时间延长测试。这些种种都能看出客户也想尽快把问题解决调通。


于是,这次客户选择了一个最简单的方式,重置模拟设备,然后直接连接自己的服务器。当然,这个里面也有很多小波澜,比如模拟器重置之后网络又出现问题,APN 设置异常等等反复了 N 次,也浪费了很多时间,这都不算问题了。


总之,在重置完,配置好之后,连接客户自己的服务器,华丽丽连通了...


这下又尴尬了,感觉又回到了原点。这不是说明模拟器没有问题么?或者说只有在连接我们服务器的时候有问题?是,很明显。但是不能这么直接给客户解释啊,这不是伸出脸给客户打么。



第四回合,

找差异


还好 TCPDUMP 文件在,猿们清洗一把绝望的情绪,继续上路。


对比了 TCPDUMP 文件,再次发现了几点线索,


1. 文件里面,成功的连接客户端,服务器端证书都是由客户自有的私有根证书签发的,在一套 PKI 体系下。我们之前尝试过客户的客户端证书,我们服务端证书,尝试过所有证书都是我们根证书签发的。但是没有尝试过客户签发的服务端和客户端证书。


2. 文件里面,当服务端响应证书的时候,在 client SSL profile 的 Client Authentication 部分,“Trusted Certificate Authorities” 和 “Advertised Certificate Authorities” 的配置值不一样。尤其是 “Advertised Certificate Authorities”,这个是服务端响应给客户端的,在客户端发送证书之前要求客户端检查自己是否拥有响应里面指定的证书类型,如果没有,那客户端确实可以不发送任何证书给服务端。


3.文件里面,成功的连接里面服务端响应回客户端要求客户端发送证书的包里面,Distinguished Names 是空置的但是失败的连接里面服务端响应回的 Distinguished Names 是包含了内容的。这个 DN 主要是用来指示服务端可接受的证书颁发者信息,一般用来描述 CA 证书或者从属证书。


下图展示了成功的连接,服务端在 Certificate Request 的请求包里面,Distinguished Names 域设置为空,也就是对客户端证书没有任何要求。



下图展示了失败的连接,服务端在 Certificate Request 的请求包里面,Distinguished Names 域设置非空,设置了对客户端证书的要求(具体信息因为项目敏感性在此不作展示)。



4.文件里面,成功的连接协商的是 SHA384 的加密套件,失败的连接试图协商的是 SHA256 的 cipher suite 加密套件。


下图展示了客户端在发起连接时候提供给服务端可选的九种 cipher suite 加密套件:



下图展示了失败的连接里面,服务端在响应回客户端时选择的 cipher suite 加密套件。在成功的连接里面,服务端选择上图标记出来的 SHA384 套件。



好了,这么多的差异,怎么办?每一个差异的处理都需要或多或少修改客户端或者服务端的配置,意味着会要花费大把时间,不可能一个一个来。


那么这个时候,最有效的方式是分析,先挑最有可能的。这个时候猿们内部出现了小小分歧。比如,某猿觉得 1 或者 4 最有可能,某猿觉得 1,2,3 最有可能......


OK,有分歧不怕,就怕没思路。先求同存异,既然大家都觉得 1 最有可能导致问题。那么让客户签发一套证书体系给我们,直接上!当然,这也是最耗时间的。客户的测试环境和生产环境是一套 PKI 体系,任何证书的签发都有严格的审批流程,于是又等了两天,证书才签发下来。然而,这里面又有插曲,客户签发了一个证书,但是用了依旧没有成功,我们一检查,发现不是跟客户自己的服务器使用的证书同一个根证书签发的!用了另一套私有根证书签发。纳尼?好吧,那一定是我们没有解释清楚!我们依然怀疑连接不通是这个问题导致的并且深信不疑,于是又让客户帮忙再重新签发,那必须又要多等一天...


当然,在这个间隙我们也没闲着,又验证了 2 和 3 影响不大,因为 2 和 3 都可以通过服务端的 BigIP 配置更改。至于 4,直觉是觉得有可能,但是大家内部讨论,觉得既然客户端已经发起请求到了服务端并且也获取了服务端的证书,没理由因为加密套件协商不成功而失败呀,最关键的是,如果协商失败,客户端按照标准流程,应该发出来一个 cipher_suite_mismatch 的错误出来,但是所有的日志里面毛线都没有!于是大家先决定不尝试了,结果就是因为这个决定,导致了我们离成功擦肩而过!


然而,一天之后,拿到了新的证书,依然还是没有成功建立连接,错误也是一模一样,客户端没有发送证书。那感觉好像就是在原地画圈圈!


绝望么?当然绝望,这打怪打到最后简直就是被大 BOSS 在调戏啊!这还了得!


猿们怎么能屈服?于是小宇宙纷纷爆发,各种奇葩思路随之而来,


猿 A:既然客户服务器可以,那我们不如 fake 客户服务器试试?

猿 B:嗯,可以试试,反正客户服务器的证书我们也有

猿 C:等等,客户端校验的时候域名对应不上咋整?哦,把客户 DNS 或者模拟器 etc/hosts 文件的服务器域名映射到我们的服务器就行了

猿 A:那试呗

猿 B:那试呗

猿 C: 那试呗... 等等,还差一个东西,客户服务器证书的私钥,这玩意得导入到我们服务器

集体沉默三秒钟

猿 A:日

猿 B:日

猿 C:……


问客户要私钥,这好比你初次相亲的时候问人姑娘能把你家门钥匙给我用用么?我保证在自己家里用,不开你家锁... 作为一名专业的猿,怎么会干这种事情,那必须是自己造一把姑娘家的钥匙呀... 开玩笑... 那必须是放弃。



第五回合,

终极 PK


机会往往在绝望中诞生。有经验的猿们都知道,当真正绝望来临的时候,希望也就不远了......


猿们再次回到了找差异的老路,现在证书也都一样了,服务端 certificate request 的各种属性项都一样了,难不成客户在模拟器做了特殊逻辑,只能连到指定的服务端?不至于吧,千万不能钻这种牛角尖,客户应该也没有这种奇葩嗜好吧,何况这都是在 TCP 层的问题,还没到应用层呢。


再想,那难道是我们服务器有问题了?嗯,应该是有问题,不然客户端为啥死活不发呢?啥问题呢?难道是 BigIP 做了端口映射?那应该不会,对外是标准 MQTT 端口呢,TCPDUMP 也都显示只连到了标准端口,内部端口映射应该问题不大。那能有啥?还是应该换个服务端试试,嗯,不如去掉 BigIP 直连 MQTT 服务器试试?嗯,死马当活马医了,试试,最怕就是没有思路,有想法就应该上手。


于是,当晚,新的 MQTT 服务器就直接起来了,直面客户连接... 没想到啊没想到,深夜收到猿们的信息,成功了...


虽然成功了,但是也很尴尬,为啥?虽然解决问题了,但是没有找到根因。幸福依然来的迅雷不及掩耳盗铃而又转瞬即逝。生产环境也不能这么玩啊!


咋整,那必须是继续 TCPDUMP 找差异。


嗯,这回发现成功的连接又是建立在 SHA384 的加密套件之上。这种信息怎么可能再次错过!直觉再次告诉猿们,一定是这里有问题,虽然不能 100% 确认,但是 90% 可能性有啊!关键是,如果不是这个原因,那真找不出辙了呀...


于是猿们集合了一个大会诊,针对客户端明明发送了 9 个加密套件选项给服务端选择,服务端明明从这 9 个加密套件选项里面选择了一个,但是客户端却强制要求使用 SHA384 加密套件并且没有遵循标准流程(抛出 cipher_suite_mismatch 异常)的变态行为进行了集体谴责,同时针对客户这种变态行为迅速做出了适配支持(感觉哪里好像不对劲?有点 “助纣为虐”?没办法,谁叫咱们灵活度高呢!),更改了 BigIP 的配置,只等客户最后一试了!


经过漫长的几个小时等待,客户集成姗姗开始。按照约定,客户重新检查了一遍配置,重启了模拟器,眼看着猿们盯着电脑屏幕充满忧郁和焦虑的眼神随着瞳孔的慢慢放大逐渐开始散发出星星的光芒,幸福就这么悄然而至了!成了!


一阵欢呼有如漫天焰火四射。三周的痛苦,煎熬,绝望伴随着激动,兴奋,喜悦在这一瞬间全部转化成了这绚丽的焰火,尽情绽放,当然,也转瞬即逝。剩下的,更多是欣慰,是冷静,是反思,是前行的勇气。


测试依然需要继续,但是猿们已然干掉了成功路上的最大绊脚石!


小结


本文通过演绎的方式描述了这次客户集成 troubleshooting 的整个过程,很多细节都做了艺术处理。真正调试的工作,过程往往都是枯燥乏味毫无乐趣可言的,只有在找到问题的那一刹那,才能让大家找寻到一丝丝的兴奋和满足。相信大部分的程序猿们都是奔着这一丝丝兴奋和满足,在枯燥与乏味中坚持前行。也正是这一丝丝的兴奋和满足,让程序猿们能不断积累成长!


每一场联调都有各自的状况,但是诊断问题的方法,应该还是有一些共性 ,这里给大家也总结下一些经验,希望可以有些帮助。


  1. 怀疑对方的错误是每个程序猿的本能,这个没有错。但是在怀疑对方之前,希望能找到足够的证据自证清白。这里说的证据不光是说在 “我的 “环境没有问题,你需要做的是,” 我的 “环境跟你的环境没有差别,我都按照你的设置配置了我的环境,还是有问题,我们一起看看是不是问题出在了另外的地方?

  2. 如果对方是客户,那么请忘掉第一点,老老实实对自己说,甲方 “爸爸” 怎么可能出问题呢?问题肯定在我这边,或者我应该帮助甲方找到问题。千万不要理直气壮地跟客户说你看我这没问题呀,到你那就不行了,肯定你那边有问题。这么说没有任何益处,因为甲方首先想到的肯定是谢谢你告诉我这边有问题,那你帮我解决问题吧,不然我找你做啥呢?所以跟甲方联调的过程,一定要抱有把黑盒测试当成白盒测的心态,通过各种推敲模拟,把对方逻辑摸清楚。

  3. 相信自己的直觉。程序猿的直觉往往都十有八九都是真的(申明,本条只适应于针对程序本身的直觉。尤其排除相亲和理财)。这种直觉可能是在开发过程当中对于某个技术点的把控隐隐觉得不妥但是又不知道会有啥,那一定想清楚了,因为这个十有八九就是上线之后的雷。又或者是在调试的过程当中觉得哪个地方不顺眼,可能是配置,可能是安装方式,甚至是敲键盘的姿势,那个地方一定可能就是最终问题所在。一旦有了这种直觉,那么按着你的直觉深究下去吧,一定能有所发现。

  4. 绝望不怕,最怕没有思路。所以联调的时候大家一定要集思广益,把想到的都说出来,摆在桌面上讨论,哪怕只是一点点线索,别人会帮你丰富起来的。这时候任何一点火花都能点亮夜空。当然,也别钻牛角尖,显而易见不可能的思路也没必要尝试。比如前面提到的客户是不是针对我们服务器做了特殊处理导致这次失败?这种火花还是赶紧掐灭。

  5. 在最 “绝望 “的时候往往是离希望最近的时候,这个时候最需要坚持。不要怕找别人帮忙,猿们都是互助的好朋友,尤其是在体现技术价值的时候。但是找人帮忙别直切主题,尽量把项目背景,前因后果,目前困境讲清楚,让大家有个清晰的轮廓,站在同一个位置,避免浪费时间尝试之前已经走过的弯路。


最后,针对这次联调,需要感谢一直坚守的猿们,有 Will,有 Jingbo,也有一直兢兢业业协调资源,汇总进度,汇报各级 stakeholder 同时准时催促大家点餐事无巨细一应俱全的优秀项目经理 Christina 同学,还有后来加入排查问题的 Thunder team 的同学们。当然也顺带厚着脸皮表扬下我自己能在这三周积极参加联调,充分讨论,提供直觉,并在最后两天拖着病体坚守岗位跟大伙一起排查问题最终共享喜悦并能抓住残留的记忆总结出来这篇将近一万字的长文!好长时间没有这种感觉了,就好像回到了当年!谢谢大家!


—— 完 ——


原创内容,转载请注明出处。

文章转载自Share and Fun喜来分,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论