前言:
本文介绍了饿了么交付中心由python语言栈转换到java语言栈大致过程,一来是对前段时间的工作做下总结,另外也是想通过此次总结为其他应用服务转型提供些借鉴。写的不好,欢迎板砖。
背景
饿了么并入阿里集团,为了能高效与集团内部系统协同对接,同时方便利用集团优势技术资源,更好的融入阿里集团技术生态圈,饿了么交易中台在上半年启动了交易领域的四大应用语言栈转型项目,为阿里集团本地生活服务平台打好技术平台基础做准备。另外,随着业务量的激增,饿了么平台支持的品类不仅仅是最初的外卖单品,整个交易中台也需要一次相对大的重构来快速应对复杂多变的业务需求。而本文中的交付中心即是饿了么交易领域四大应用之一。
准备
在开展相关工作之前,首先必须得清楚我们是要将一个系统从什么样变成什么样,新的系统相较老的系统在哪些方面做的更好,同时必须保证新老系统的无缝切换,做到业务无感不影响交易系统的稳定性。
系统价值
外卖订单的业务特点是重交易,c端用户从下单选餐到骑手完成餐品交付过程,目前大部分都能在半小时左右完成,对即时配送实时性要求较高。整个订单交易过程大致划分为:1.添加购物车确认订单,2.订单生成及订单支付,3.接单及订单交付,4.可能的售后退单。而要更好的服务用户,对四个系统稳定的协同能力提出很高的要求。
如前文所述,我们知道履约环节对交易订单的价值是什么,即是将外卖订单对应的餐品交付到用户手中,技术层面上来讲,交付对交易订单屏蔽了一切其他履约细节,使订单更专注订单领域业务。
内核分析
接下来,我们再进一步详细的剖析下转型前交付系统所承载的功能。
如上图所示,原交付履约领域中包含了三个较大板块:
- 1.订单对接运力线模块
- 2.商户订单信息模块
- 3.部分金额计算模块
可以看出原系统所承载功能并非真正聚焦在订单交付过程。怎么办?转型重构是个契机。商户订单回归到订单,金额计算下沉至结算,这两部分的相关转型重构这里不再赘述,相关部分也已由兄弟团队完成。其实到这里交付履约应该关注的已经很明显:把外卖订单给运力线,使其完成交付履约。
基于刚才提取后的交付履约核心领域,我们对主要的场景用例进行了详细的梳理。如下图所示,可以看到:
-
参与交付过程的主要角色有四个:
- 1.商户
- 2.订单
- 3.履约运力线
- 4.用户
-
交付提供的主要支撑能力有:
- 1.乎单交付能力
- 2.运单状态同步能力
- 3.运单信息透出能力
- 4.履约方式切换能力
- 5.运单状态广播触达下游能力等。
- 部分运单取消或异常管理。
系统核心用例分析如下图所示:
更详细的系统用例梳理或系统上下游交互时序这里不再一一列举,需要说明一点,当大流量系统转型遇到重构,要想走的够远够稳,前期的仔细调研工作及功能对齐过程非常重要。特别是生产环境已经跑了好几年核心功能系统,实际业务情况错综复杂。例如要转型重构系统接口能力,接口含义,场景必须都要详细掌握。
设计
至此,我们的目标已经很明确:1.语言栈转型,2.过程中要将不属于交付的东西从原系统中分离出去,使得交付领域更干净。结合交付未来可能的发展方向,其中交付能力可复用的,履约运力线可扩展的。这对整个交付可快速迭代对接提出了更高要求。另外,我们也在领域建模设计思想指导下做了些实践尝试,于是有了以下框架结构。
系统设计
转型是把python方言转换到java方言,交付系统核心能力不会有变化。能力的复用是通过接口维度的抽象聚合产生,履约运力线能力可扩展通过利用业务框架扩展点特性满足。拆分后的业务框架应如下图所示:
系统架构
业务框架结合当前饿场基础组件支持我们不难给出以下系统内部模块划分:
简单对各组成模块做简要说明:
- api:处于业务框架中上游系统接入层,对外暴漏契约屏蔽细节,对内定义系统能力。
- starter:启动项目
- service:rpc服务暴漏
- domain:核心业务能力实现,聚合层
- infra:基础数据支撑能力层
- extension:履约能力扩展层
- tester:单元测试包
到此,我们可以按照设计与既定的计划撸代码了。
嗯,代码撸完了测完了,从调研到开发完成历时两个半月,代码仓库总行数约4.7w行,提交1000次左右,完成63个接口+16个消息消费入口转型迁移,共计测出117+bug,期间有喜有忧,奋战两点多是常态。职业生涯中浓重的一笔。此时尤记起与超哥@邹超周末代理联调于北14楼小黑屋之畔,和明龙@张明龙并肩作战对接服务接口,还有晓波@俞晓波凌晨一同压测观察总结问题第二天分析反馈提升优化,当然还有杰哥@汤良杰的用例设计全面不失细节、对bug常常究其根本,对代码review逻辑核对一丝不苟明察秋毫。凡此种种,历历在目。一起走来,真心感谢。
------ 不由自主的感概下 ≧◇≦
平稳过渡
代码撸完测完才做好转型工作的第一步,将流量稳步平滑过渡到新的服务中,做到上下游无感是转型过程中面临的另一大挑战。
说到这里,要达到系统的语言转型前后的平稳过渡其实是在说转型前后对我们系统的用户SLA(Service Level Agreement)提供一致性的保证。这里先简单聊聊系统服务的SLA。
服务可用性级别 | 服务正常运行时间百分比 | 年宕机时间 | 日宕机时间 |
---|---|---|---|
1 | 90% | 36.5day | 2.4 hour |
2 | 99% | 3.65day | 14 min |
3 | 99.9% | 8.76day | 86sec |
4 | 99.99% | 52.6min | 8.6sec |
5 | 99.999% | 5.25min | 0.86sec |
6 | 99.9999% | 31.5sec | 8.6msec |
上表格是业界服务高可用的几个级别的衡量标准,例如:服务可用性是3个9时,全年宕机时长约为8.76天的统计概率。另外,我们需要明确的是不同的系统,不同的场景以及不同的用户规模对系统可用性要求是不一样的。如:某些业务的支付链路场景可能tps不是很高,但是作为交易的核型链路必须得保证高级别的可用性。
怎么保证或者提升系统服务的SLA呢?在明确我们目标后,接下来就是拆解目标量化目标。
you cant measure it,you cant fix it and improve it.
一般来说,影响系统服务可用性有两个重要因子:
- MTBF:Mean Time Between Failures,系统服务平均故障时间间隔。
- MTTR:Mean Time To Recover,系统服务平均故障恢复时间时长。
所以大致可以简单归纳为这样的一个函数关系:
可能无法给到一个精确的数学描述,但是可以做定性的分析:恢复时长因子与系统可用度成正相关,故障间隔因子与系统可用度成逆相关。也即:_问题出现时恢复时长要尽可能的短,尽可能降低故障频率以增大故障间隔。
基于以上理论,我们在做系统平稳过渡无缝切换时,无论资源使用,业务内部逻辑实现及灰度方案,我们都需要着眼于这两个方面。接下来我们将从这两个点出发分析转型过程存在的挑战及我们的应对方式。
快速响应,降低恢复时长
要做到恢复时长尽可能的短,首先我们需要保证在前期出现问题时流量切换过程中流量规模尽可能的小,即需要一个相对的合理的灰度梯度方案。其次当问题出现后我门需要能立即回切到原系统,不至于反应过慢导致大量增量问题产生,即需要一个响应高效的开关回滚方案。由于转型过程中涉及的接口和消息消费入口较多,另外我们还需要考虑到后期问题排障和快速定位的可能耗时。
- 针对灰度梯度合理制定,根据业务特征,开始阶段我们选择了较冷门城市(订单量较低)进行了各个运力标品业务的逻辑验证。标品验证完后说明我们新迁移实现的逻辑和原系统具有一致性。随后我们拉取了当前订单城市分布,根据城市订单分布占比制定灰度梯度,由小到大逐步增加。
- 针对回切需要响应高效,我们应该有一个总的开关可以控制流量,回切应该是一键控制的。
- 针对排障快速定位,灰度单生命周期内的操作能在单应用内自闭合,不至于排障时还需要确认灰度单某个状态到底走的原系统服务还是新系统服务。另外我们还希望,写操作灰度和查询灰度能单独隔离开,等写数据完全一致的情况下我们再开启新数据新接口查的灰度,将风险控制到最低。
所以我们采用了如下的老系统服务代理方案:
如上图所示,订单系统创建订单时根据既定的灰度计划读取配置给订单打标,灰度计划分为前期分标品,门店维度验证,后期按城市订单分布维度配置。若新系统服务出现问题可一键切掉灰度流量,止血增量问题。接着,原交付老服务识别标记做代理转发,订单不存在迁移标记则原流程操作处理,否则转发到新交付中心新服务操作处理,相应的消息监听处理同样也是参照订单标记,这样保证了同一个订单的交付过程能在一个应用中完成,有利于后期处理流量上来后的快速问题定位。
另外,我们的接口灰度过程是写与查分离的,整个迁移过程大致分为三个阶段,如下图所示:
- 第一阶段 灰度写阶段,灰度策略:餐厅维度,城市维度小范围灰度,读流量以老服务为准,问题出现及时切掉灰度写流量,逻辑回滚至老服务。
- 第二阶段 灰度查询阶段,灰度标记流量以新服务为准,非灰度标记以老服务透出为准,灰度策略:各个查询接口按照百分比逐步增加灰度占比。
- 第三阶段 迁移阶段,完成读写灰度,原系统老服务只是代理的一层皮,接下来就是上游系统迁移到新服务,去掉对原原系统服务的依赖,迁移完成。
最大努力降低故障风险
平均故障间隔是一个后验时长数据,要做到间隔时长尽可能的长,日常里就需做好发布控制,风险巡检及持续监控等工作。
1.发布控制
转型期间新系统服务必须遵循发布sop,饿场发布sop已经形成共识,我们只需严格遵守,这里不再赘述。
2.风险巡检
- 系统逻辑核对,多人多次code review。
- 变更发布前主干场景自动化用例全通过。
- 周期性压测。
3.多层次持续监控
- 部署机器,缓存集群,消息队列,数据库表等基础资源的基准监控。
- 业务曲线成功率,日同比,周同比,曲线波动比,及主要接口入口流量到下游出口流量转换率监控,业务系统成熟后还应对各个服务响应时间指标做监控等。
- 系统中很多情况下重试都是以异常中断为依据,这必然会对系统异常点带来较大的噪音。为此我们还需要细化各个中断异常的打点,排除不必要的干扰。
一致性问题
转型过程中我们实际上同时做了数据模型优化迁移,对外为了保证新老系统行为属性的一致性,我们还面临以下几个挑战:
- 灰度数据需要双写新老交付系统库表,如何保证双侧底层数据的一致性?
- 底层数据一致不等于对外服务接口数据透出一致,双侧服务应用层面怎么保证数据的一致性?
- 订单阿波罗履约线和交付的上下游数据数据最终一致性怎么保证怎么验证?
一致性的保证,别无他法,只能通过比对来做。但在做比对的时候我们还需要考虑到比对本身只是我们验证迁移系统正确性及稳定性的一部分属旁路,并非生产环境的必须功能。即我们得考虑这部分功能对现有系统可能造成的压力。这部分压力应该是随着系统验证完毕是可开关的,压力大小应随系统的表现可随时调节。不至于因为验证拖垮了生产应用。所以我们对比对的基本要求是:能一键开关,可监控可追溯。除了这些共性,具体还应做到以下几点:
-
针对底层数据比对:
- 比对应是准实时的,如只比对30分钟前的数据。
- 比对数据是基于时间可分段采样的,如只比对10分钟以内产生的数据。
- 为了方便控制,根据情况以上策略又可以是及时调节的。即准实时偏移量可调节,分段采样窗口大小可调节。
具体实施方案如下:
-
针对应用层数据比对:
- 代理层接收请求后,比对应是异步的,不影响接口响应耗时。
- 比对粒度要小,应细化到接口。
- 识别灰度数据,只做有效比对。
具体实施方案如下:
无论数据层面还是接口层面出现比对不一致,应立刻去分析是什么原因导致不一致。解决根因逐步降噪,只至比对差异在我们认为可接受的范围内。
-
针对上下游数据最终一致性:
- 全量数据核对
- 主干链路最终一致性核对
经过数据准实时比对,接口实时异步比对,其实基本可以确认新老系统能力行为属性对等性。然而稳定大于一切,要百分百确定还需要t+1数据核验。即从全局数据角度看新老系统转换的影响。这里以主干链路呼单多日成功率为例做简要说明。如下图所示,多日乎单成功率基本在99.9977%左右,可以认为新老系统代理切换交付成功率基本表现稳定。
未来
截止此文攥写时间,饿了么交付中心已经完成了整个系统的语言转换,流量也已经100%切换代理到新系统,处于流量切换的第三阶段。结合日常系统维护与需求迭代实践,我们仍需要再以下几个方面进行更深入的思考:
- 转型过程中为了在易测,可核对同时与python的“魔法”姿势斗争中找平衡,部分逻辑是"纯翻译"的,后期维护过程很痛苦,所以我们需要几次不小的重构来达到代码层面的和谐。
- 不断监控降噪,持续细化监控粒度,监控是服务稳定基石。
- 交付中心数据大盘建设,从数据层面量化观测我们的交付质量。数据驱动,数字运营从数据化思维优化我们的流程,提高我们的服务。
方法论沉淀
凡此以上,服务系统转型迁移归结于开发者理解与认知,项目的稳定实施归结于开发者套路方法运用。可以简单进一步提炼为以下方法论:
- 详细调研,客观问题及满足业务的系统是复杂的,详细调研做好准备是必须的。
- 持续监控,感知系统的质量是服务质量度量的第一步,不断持续的监控才能走的更稳。
- 稳步过渡,互联网系统服务高可用稳定不容商量。
- 问题发现根因解决,小的问题可能隐藏大的bug,认真对待究其根本,复盘->总结->提升。
- 归纳总结业务再认知。
关于认知提升的几个小点:
- 对于每一位工程师,开发高并发大流量系统是挑战也是机遇。时刻保持进取学习心态,增强自身软素质。
- 分布式情况下,承认系统并非绝对100%可靠,每一个环节都要考虑“失败”了怎么办,不同的场景我们需要在AP和CP之间作出抉择。如双链路调用保证可靠,异步重试保证顺序最终一致等。
- 出了问题快速恢复是个永恒的话题,没有“怎么样“最快只有”这样”更快。精准定位立即恢复,如何将这个过程耗时降到更低值得深入思考。
作者信息:李杰,花名项庭,当前主要负责饿了么交付领域系统。