语言栈转型经验谈

背景介绍

近一年都在做语言栈的转型,也注意到周围很多公司都在做相似的事情,大概的路径是 Python -> Go -> Java,转型的起因也是有诸多的因素,像 Python 这种开发速度快,执行相对慢的语言更适合中小型项目,加上国内语言生态不够成熟,项目做大了会发现大家一刀切的转到其它语言上,当然这些说的是在做 web 后端方向上,Python 在数据分析和人工智能方向上还是势头很猛的。Go 可能还是因为它能承载的并发更高,性能更好而逐渐流行起来。在并发模型上 Java 原生 API 使用上确实做得不好驾驭,Go 则要相对好用很多。还有在某些垂直领域上,Java 的生态已经很成熟,其它语言栈上则需要自己造轮子,相应对于开发人员的水平要求就会低很多了。

在当前互联网领域,后端研发做 web 主要谈的还是通过抽象和建模来提高项目的可迭代性与可维护性,另一方面谈的是工程实现上的优化和性能上的优化。在这些后面依赖的则是中台来保证的基础服务综合稳定性。

在语言栈转型中也踩过一些坑,遇到过一些小问题,当然这些也得益于一个相对靠谱的方案来保证迁移的安全,基于这些经验总结一下,在以后的迁移中使问题可预见和避免采坑。

转型流程

首先要明确转型的三个开发流程 MRO (Migration, Reconstruction, Optimization)

  • 迁移 就是把原语言代码照着抄一遍到新语言项目上,按照新语言的工程实现风格来做就可以。
  • 重构 的目的是来提高项目代码的可维护性和可迭代性,让代码更加优雅和好读懂,可以放到迁移完成来做。
  • 优化 则可以是在模块依赖、调用关系、接口字段等方面调整来降低项目的复杂性和提高合理性。

然后看我们人力和时间是否充足,我想大部分情况下是不充足的,按照最短时间交付的原则,我们应该只做迁移流程,也就是说先对原有代码进行语言上的迁移,这样我们可以快速实现交付。在人力充沛的情况下可以配备两个小组,一个维护现有系统,一个主力开发新系统,或者说锁定需求全力开发新系统。在对快速交付更看中的行业里前一个方案更合适一些。

交付流程

在交付过程中的验证流程 单测验证 -> 测试环境功能验证 -> QA生产回测 -> 灰度验证 -> 完全上线
只有功能和单测代码都迁移完才能算代码部分完成,需要优先保证单测行数覆盖率再去保证分支覆盖率,测试环境的功能验证需要覆盖所有 case 来保证大部分问题都被发现,然后进入小范围的灰度验证,之后逐步提高灰度比率直至完全上线。如果是纯读接口则可以直接进行异步校验,就是请求两遍,然后对比差异来不断的发现和修复 bug,直至问题收敛完全解决。
如果明确只做迁移,那么期间如果有发现旧逻辑的 bug 也不要管,这样才好去对比验证,验证通过上线后再去修复。只有通过明确目的和流程并且遵循这个流程做,才能更快的去交付。

验证方案

针对新代码的验证方案分为别为读写接口做不同的验证方案:

  • 读接口:异步请求到新接口做接口响应值校验,打印出差异数据,然后不断修正逻辑。这样可以避免在线上灰度造成数据的不一致。
  • 写接口:测试用例覆盖,然后测试环境验证,灰度回测,灰度验证,修复问题,继续灰度验证。

平稳交付

在整个交付的过程中,转型前后对 SLA 要提供一致的保证,可以看看下面的几个衡量标准:

服务可用性级别 服务正常运行时间 年宕机时间 日宕机时间
1 90% 36.5day 2.4hour
2 99% 3.65day 14min
3 99.9% 8.76hour 86sec
4 99.99% 52.6min 8.6sec
5 99.999% 5.26min 0.86sec
6 99.9999% 31.5sec 8.6msec

在线 MD 表格生成

一般 3 个 9 的可用性全年宕机时间约为 8.76 小时,针对不同系统不同用户规模对于系统可用性的要求不一样,边缘业务的要求可能会低一些,但是对于核心支付链路场景 TPS 可能不高,但是必须要求保证高可用级别。如何保证或者提升服务的 SLA 是我们接下来要探讨的目标,一般有下面两个影响因素:

  • MTBF (Mean Time Between Failures) 系统服务平均故障时间间隔
  • MTTR (Mean Time To Recover) 系统服务平均故障恢复时长

也就是说我们系统要尽可能的降低故障频率以及出现故障时能尽快的恢复。基于这两点我们在做系统平稳过渡时,要充分测试所有 case ,并且进行内部灰度方案和异步重试对比,发现异常立即回滚查找结局问题后再重新灰度。这里需要做到一键开关,数据可监控和追溯。

持续监控,感知系统稳定性的第一步就是监控,通过监控和系统日志来排查问题和及时响应处理。监控有两个层面,一个是基础设施提供的机器监控以及接口级别的响应稳定性监控,另一个是业务数据层面的多维度监控。系统日志按照等级大致分为 INFO 日志以及 ERROR 日志。

快速交付

关于快速交付,可以了解 下敏捷开发,及早和持续不断的交付有价值的软件。关于 Scrum 开发的介绍可以看: 什么是敏捷

现状及未来

基于公司现状考虑 nginx 不支持长时间和自定义灰度,所以 http 接口层没做改动,只是在内部逻辑上通过 rpc 服务转到新的系统中。基于以上要点可以做到功能的快速交付。截止此文撰写时间,语言栈转型已经将系统核心接口逻辑 100% 迁移到新的系统上,对于日常系统需求已经可以做到在新系统开发和接入了。后面要做的有以下几点:

  1. 将系统外围逻辑迁移到新系统;
  2. 不断监控降噪,细化监控粒度,继续提高服务的稳定性;
  3. 当前对于Python的花式“魔法” 硬翻译还需要不断重构和优化。
  4. 完善监控大盘,通过数据驱动来运营优化我们的流程;
  5. 项目复盘总结以及业务普及宣讲,提升人员对于业务细节的认知。

转型痛点

迁移后再做重构和优化过程。在迁移过程中有一个痛点是新需求过来了,要么锁定需求只做迁移,要么写两遍。基于人力情况可以选择一个小组同时写新旧系统或者一个小组维护新的一个小组维护旧的。
在转型过程中新需求过来有时要写两边,或者要把旧系统流量打到新系统接口上,常常在排查问题时遇到流量忘记转移的情况,所以在迁移过程要尽可能的快速交付上线。

反思

  1. 对于每一位工程师来说语言栈的转型既是挑战也是机遇,只有保持开放学习心态,及时调整和提升才能更好应对,同时增强自身软素质。
  2. 当前互联网环境下分布式是必经之地,而系统绝非 100% 可靠,每一个环节可能的异常在上线后必定遇到,所以针对不同场景我们要在 AP 与 CP 之间做出选择。
  3. 对于支付交易核心链路,一条柱子肯定是不稳的,双链路也未必可靠,但至少更稳一些。曾经遇到过相隔几公里的两条光纤被施工队挖断的情况,双机房访问直接 gg 了,但总归是少见的。
  4. 提系统可用性要避免出问题,除了问题要快快快速响应恢复,有问题先回滚。