🙋🏻♀️ 编者按:本文作者是蚂蚁集团数据研发工程师惠勒,将马斯克五步工作法应用在了实际项目中,实现了支付宝商家账单的重构,希望本文对想要降低系统复杂度的同学或者项目有所帮助。
0. 概述
1. 重构背景
1.1 什么是商家账单
1.2 为什么要重构
1.3 为什么是现在
2. 重构目标
3. 应用五步工作法重构账单
3.1 质疑
3.2 删减
3.3 优化
3.4 加速
3.5 替换
4. 重构效果
5. 总结反思
0. 概述
支付宝中国数据团队在过去的一年里应用马斯克的五步工作法重构了有 10 年历史之久的支付宝商家账单,整体复杂度减少 60%,时效性提升 1 小时,计存成本降低 30%,理解和运维成本大幅下降。复杂度是很多问题的根源,既会增加运维的成本,又降低了支撑业务的效率。账单重构的经验表明,相当大比例的复杂度是没有必要的,我们应该致力于把复杂的事情变简单,而不是倒过来做“防御性编程”。希望本文对想要降低系统复杂度的同学或者项目有所帮助。
1. 重构背景
1.1 什么是商家账单
商家通过支付宝发生业务,我们对他们提供相应的流水单或者凭证,这就是商家账单。商户可以到 B 站下载账单和他们自己的业务记录及资金变动期望逐一比对,确认所有业务和资金都按正确的期望的方式完成了处置,这个过程称为商家对账。
支付宝目前提供了丰富账单类型,包括资金流水,交易订单,资产凭证,营销动账,费用账单以及一些列个性化定制账单。实现方式上则有在线实时账单以及基于 odps 的离线的日/月账单,其中在线账单主要用于业务查询,而离线账单则主要用于商家对账,本文所指商家账单主要指离线账单。
图 1:B 站里的商家账单
1.2 为什么要重构
一句话概括:历时 10 年,积重难返。
商家账单作为支付宝收单业务配套的基础产品,主要的服务对象是商家。和所有 To B 产品一样,其面临着“千人千面的个性化诉求和成本可控的快速支撑”的核心矛盾。在实现过程中,要么在原有逻辑上打个补丁,更多的时候是出于稳定性等因素考虑,不敢动原有的逻辑,于是就新起炉灶搞个新的字段。历时 10 年,资金流水账单搞出了上百个字段,很多字段的加工链路极其复杂。目前整个账单大概有几千个任务,近万的依赖关系,平均加工深度 20 多层,各种横向域之间的耦合,纵向层之间的调用层出不强,用一团乱麻来形容也不为过!
图 2:真实的账单血缘图
图 3:账单架构混乱的示意图
1.3 为什么是现在
主要是因为逻辑过于复杂,当前用于保障账单准确和出账时效的成本已经过于高昂。
离线账单是拿去对账的,这就像有上百万商家拿着放大镜在找问题一样,不仅像金额,时间等字段不能有问题,各种订单号,门店 ID 等字段也偏差不得。而当前账单过于复杂,经常出现变更了这里漏了那里,或者是改了上游影响了好几层以外的下游。目前每年流转到二线研发同学的咨询就有几百例,一线外包和马力等同学接到的账单类问题更是以万计。
时效性方面的压力则有过之而无不及。由于需要对标竞对 T+1 10 点的出账时效,支付宝目前对客承诺 T+1 9 点出账,扣减掉在线账单文件生成和预留的异常处理时间,基本上要求离线账单需要 T+1 5 点 30 产出,运维同学承担了很大的压力,从 2023 年 9 月- 12 月,运维同学在夜间共计响应 150+ 起电话告警,涉及天数 67 天,值班起夜比例为 67/122=54.9%。虽然引发起夜的因素有集群计算资源以及 odps 软件等外部因素,但根子上还是因为加工链路太长,给基线预留的余量不够。
为了彻底解决上述问题,我们决心重构支付宝商家账单,通过降低复杂度的方式,既提升用户体验又降低运维成本。
2. 重构目标
通过降低 50% 的复杂度,达到以下 5 点业务效果
- 准确的
每个字段的含义是明确的,账单数据内部是一致的
- 高时效的
账单产出提前 1 小时
- 好运维的
重大问题能够快速一键重跑的(72h 降低到 12h 以内),日常的异常情况能够快速处理(1h以内),代码结构是好理解的(模块化的分层架构)
- 易扩展的
可扩展性强,对于各种业务需求的响应速度较快,不需要对代码逻辑大幅改动。有灰度环境的全链路回归链路,减少变更风险
- 低成本的
在保证回刷要求的前提下尽可能降低存储成本(降低 1/3 的存储成本),减少任务数量,降低计算成本(降低 1/3 的计算成本)
3. 应用五步工作法重构账单
马斯克在特斯拉和 spaceX 的成功经验告诉我们,应用五步工作法可以把复杂的事情变得简单,把高昂的成本打下来。所谓五步工作法,主要是
- 质疑,推敲需求,不要有愚蠢的需求;
- 删减,简化流程,精简部件或工艺流程;
- 优化,在前面两步的基础上做优化;
- 加速,在前三步的基础上加快迭代时间;
- 替换,在完成前四步之后做自动化替换。
商家账单的重构工作也或多或少借鉴了五步工作法。
3.1 质疑
第一步是质疑:为什么有那么多字段,为什么每个字段有那么多逻辑,为什么加工链路需要那么长。
带着这几个为什么,我们开始做字段梳理工作,核心工作是两项
- 梳理这些字段哪些是有人用的,哪些是没人用的。有人用的话有多少人在用,都是哪些商户
- 从末端表的字段出发,自下而上的梳理加工链路,穿透到最上游,看字段最终来源于哪些领域。
以很多商户使用的资金流水账单为例,上百个字段中仅有不到三分之一是核心字段,一半左右是个性化字段(使用商户数 100 以下),剩余大几十个都是无人使用字段;字段来源方面则集中在账务,交易,支付,计收费,结算,充转提等几个领域。从这些数字中我们得出以下两个观点
- 不需要那么多字段,可以先集中攻克核心字段
- 我们可以分域处理信息,再拼起来集中加工使用
3.2 删减
带着第一步质疑的观点,我们开始做删减,以核心字段为目标,落地如下架构设计
图 4:重构的账单架构图
核心的工作有那么几项
- 把最终的一个对客账单字段,拆解为几个不同领域的字段加工。如账单字段商户订单号,可以简化为如下规则:如果有交易号的话,则取交易号,否则使用账务域的外部流水号兜底。这样一来,一个账单字段就拆解为了一个交易域的字段和一个账务域的字段。对其余核心账单字段如法炮制,最终可以归到账务,交易,支付,计收费,结算,充转提等 6-7 个领域中去。
- 每个领域按照面向领域建模的方式进行中间层的构建,把需要的领域内的字段提前加工处理好
- 把(2)的结果拼接起来变成账单因子层宽表,再根据每个出账字段的加工规则,清洗出最后的账单字段
- 清洗出账规则,过滤(3)的结果,产出最终的日明细账单。日汇总,月账单等都基于日明细账单加工。
特别需要注意的是,在这个阶段并不需要特别拘泥于细节,因为后面还会把删多的逻辑补回来,按照五步工作法的说法,如果最后没有补回来 10% 的逻辑,说明这个阶段删减的不够。
3.3 优化
在整个账单重构过程中,有几个难点我们专门提出来成立了专项进行优化
- 出账范围
商家账单中最基本的一个问题是给谁出账。老账单里起了好几十个任务在处理这个问题,经常会出现商户来问为什么没有出账,往往需要查半天才解释的清楚。重构时优化了该问题,明确了必须是签约了指定产品才能出账单,任务数相应减少到 10 个以下。
- 关联 jar 包
商家账单中经常出现一笔昨日的流水需要关联多日之前的商品信息或者交易信息的诉求。这种跨天关联在离线 odps 的批处理框架下是比较麻烦且性价比很低的,在需要关联多日数据时面临着需要申请大量资源导致任务无法调起的问题。商家账单当前是使用一种 jar 包的方案来实现的,它的本质还是离线跨天关联,只是优化了并发逻辑,把一个大任务拆分成多个子任务发到 odps 跑。
这次重构,我们提出离在线融合方案,利用在线可以笔笔查,性能好速度快的优势,使用 udf 调用在线 http 接口进行查询。考虑到在线查询可能会有失败的可能,对于失败的少数数据再用离线跨天关联的方式进行查漏补缺。
目前这个方案还在研发中,预计可以大幅降低相关任务的计算成本,进一步提高出账的时效性。
- 离线灰度方案
作为一个面客类产品,如何在离线实现像在线一样的变更三板斧从而减少对客影响,是一直困扰着所有离线同学的一个难题。本次重构中,我们做了一点尝试。我们的核心思想是:离线任务只跑一次但算两套值,并通过一个控制模块来控制哪些账号取原始值,哪些账号取灰度值。
如下图所示,我们跑一次任务,把线上值记录在因子宽表的强字段收入金额和指出金额中,把灰度值记录在灰度扩展字段中,不同的值经过相同或者不同的加工规则,产出两个账单对客值净收入金额和灰度扩展字段中的净收入金额,最后通过一个控制模块来决定哪些账号要用灰度值。
图 5:离线灰度方案
- 稳定性自愈方案
商家账单有几千个任务,但我们依赖的这套离线批处理的模式里又有很多不确定的因素:odps 的软件问题,底层计算集群的机器抖动,槽位占用,在线压制等,所以每个月总有那么几十个报错或者变慢的任务需要人工处理。在本次重构过程中,我们联合蚂蚁大数据部相关团队上线了报错自动重跑和变慢自愈恢复等两个稳定性自愈方案。
报错自动重跑方案的核心是系统自动识别运行日志中的关键词,除非是明确不可重跑的报错(如数据质量问题,权限问题等),都会由调度系统拉起来重跑,实际运行过程中还需要综合考虑基线余量等要素。目前报错自动重跑方案每个月可以减少商家账单几十次的报错处理,减少 4-5 天的起夜值班。
变慢自愈方案的核心思想是识别相关变慢任务并自动 copy 到双跑链路执行。若双跑任务执行期间原链路恢复,则不做处理;若双跑链路执行完成,原链路仍未完成,则杀死原链路任务,将双跑结果注入原表,将任务置成功。
图 6:变慢自愈方案
3.4 加速
在第二步删减模块我们只关注于核心字段的核心逻辑,一方面存在核心字段逻辑删多了的问题,另外一方面需要把其余个性化字段也补上。在第四步加速中,我们需要通过一定的方式开始回补,并且这种回补相比第二步而言要高效的多。
我们的办法是找一个牵引指标,小步快跑,快速迭代。当主逻辑搭建的差不多了以后尽快把脚本发布上线,并起一个新老账单对比任务,通过对比来发现新账单逻辑上存在的问题,对于缺失的部分快速补上。这里面的核心是要有一个牵引指标,在账单重构过程中,我们引入了商户可切流比例这样一个指标,计算公式如下
商户可切流比例=所有流水的所有字段都比对通过的商户数/总商户数*100%
以资金账单为例,商户可切流比例从第一版的 41% 经过大概 10 次迭代上升到了 85%,后又经过一段时间的精细化调整,目前为 99.3%。在这个过程中,我们一方面修复了代码中的 bug,另外也基本上把删多了的有用逻辑进行了回补。这种目标驱动的针对性查漏补缺的做法相比第二步的正向推进要高效的多。
图 7:资金账单的迭代次数和商户可切流比例
3.5 替换
五步工作法的最后一步是自动化替换人工,在账单场景中我们姑且取替换之意。在商户可切流比例高于 95%,且新账单的时效性等问题基本优化完全之后,我们就开启了新老账单的替换工作。优先切换核对通过且经常有下载使用的商户,这样做一方面可以得到商户的反馈,另一方面不因为长尾的逻辑优化影响切流。
4. 重构效果
重构的效果总的来说是满足预期的。以下为几个方面的效果
- 复杂度
我们用任务数来衡量复杂度,资金流水账单的复杂度下降了 60% 以上,交易订单的复杂度也下降了 47%
- 时效性
资金流水账单时效性较老账单提升 1.5 小时,交易订单提升 1 小时
- 成本
存储和计算成本较老账单下降 1/3 左右,每年节省上百万的计存成本
- 准确性
汇总,月,历史等账单均从日明细加工而得,不再会有内部不统一的问题
- 运维/理解成本
代码耦合度大幅降低,账单整体的理解成本从半年到 1 年下降到 1 个月左右
5. 总结反思
站在现在这个时间点,我们认为有那么几点是值得总结的
1) 复杂度是各种问题的源泉
商家账单的各种准确性,时效性以及高可用问题,归根到底是因为业务逻辑过多,架构腐化,导致整体复杂度急剧飙升,造成今日想改也改不动,想维持也维持不了的尴尬局面。我们必须在日常迭代中高度重视复杂度控制问题,不图一时之快,尽量不给后人留坑。
2) 很多复杂度是没有必要的
账单重构的经验表明,对于一个发展了多年的系统,很多复杂度是没有必要的。今日之所以可以降低复杂度,一方面是因为过去很多逻辑现在已经失效了,另一方面是因为后人对于业务的理解有了新的认知,可以用新的方法来复现来重做相关功能。
复杂度的降低带来的好处是方方面面的,就商家账单而言,直观的就是时效性的提升,从而带来基线稳定性的提升,降低起夜压力。其次复杂度的减少降低了运维和编码成本,可以进一步减少准确性问题。当然,用现在流行的话讲,这不是一种“防御性编程”,不再需要半年到 1 年的时间才能熟悉账单的工作,一个略有经验的同学,可能 1 个月左右就能上手账单业务。那是否要担心因此工作就会被替代呢?我们想是没有必要杞人忧天的,如果一个工作要通过刻意把事情变复杂才能形成壁垒和核心竞争力,那么这样的工作是不值得为之奉献的。
3) 拆解事情很重要,节奏感更重要
重构商家账单是一件有些复杂的事情,面对这样一团乱麻般的代码,从何处下手,要拆解出哪几件事情来,每件事件的交付物应该是什么,这样的拆解固然很重要。但更重要的是节奏感的把握,同样是做这些事情,哪个阶段先重点做什么工作,是细致做还是粗略做,这种节奏感的把握更为重要。因为如果节奏不对,就会在某个阶段发现事情无法快速推进下去,时间一久就会受到一线同学以及主管等质疑。要是节奏对了,每个阶段都能看到一些希望,那么一件复杂的事情便会推行的比较顺利。
4) 需要进一步降低重构这件事的成本
尽管如此,我们还是耗费了几百人日来做账单的重构。我们有那么多的场景值得重构,整个社会上也有非常多的公司有这种重构降本增效的诉求。是否有可能把重构的过程流程化,甚至是产品化,从而大幅降低重构这件事的成本?这是重构这件事给我们的遗留命题。
5) 数仓同学未来的两个发展方向
在转岗到支付宝数据部门的时候,有一位高年级的同学曾经问我如何看待数仓同学的上升空间/发展路径问题。经过这一年多在商家账单的一线工作,尤其是这大半年来专职投入重构工作的经历,我想可能有如下两个方向
a) 专业的数据技术专家
数据技术专家主要专注于通过代码优化,架构优化等方法,降低一套数仓任务的复杂度,获取时效,准确,计存成本等方面的收益。商家账单重构就是典型这类场景,这个发展方向的价值收益比较好衡量。未来的核心竞争力在于是否可以借助AI的力量把重构优化这件事的成本降低,提升效率。
b) 全局的数据架构师
全局的数据架构师更关注的是信息架构的问题,要解决的是数据生产者和数据消费者之间的信息差问题。我们常常可以听到业务同学抱怨数据不准,不好用,听到算法同学说没有优质的数据供给。殊不知其实要做一份好的数据资产其实是有比较高的门槛,需要对业务,架构,信息流转的方式等都有比较深入的理解,才有可能做出一份好的数据。但在实践中我们发现,造成这种门槛的一个重要原因是因为作为数据生产者的系统研发同学并不了解作为数据消费者的数仓同学的痛点,有些问题其实在数据生产者略作改动就可以给数据消费者减少极大的成本。数据架构师就需要致力于解决这样的问题。我们很难具象的衡量这样的改动带来的收益,但可以肯定的是这些细小的改动会润物细无声的降低做一份好的数据资产的门槛,进而真正的发挥数据要素的乘数效应,助力业务发展。