buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

交易成功还要记 3 笔账?我们直接砍到 1 笔—— 网络红包记账的一次自顶向下业务层重构

交易成功还记 3 笔账?我们把它砍到 1 笔

—— 网络红包记账的一次自顶向下业务层重构

先给结论:性能优化,立足于业务层优化,收益最大。当一段代码的"保险"价值前提已经不存在时,它就不再是保护,而是纯粹的负债。我们把网络红包的记账从两阶段(冻结→解冻→扣减)改成直扣模式,RPC 请求量省掉一半,记账流水砍掉 ⅔。这不是炫技,是一次业务分析驱动的架构简化。


背景:为什么是现在

随着 j'j'l 系统交易量的增多,并发交易量也跟着增多(每秒高达 80 多笔),系统优化、尤其是性能优化,成了首要任务。

j'j'l 网络红包的交易涉及企业校验、风控&延迟风控、交易单&通道单落库、资金冻结记账、请求三方通道、结果落库、同步与异步查单等环节。

这个时候,必须自顶向下,从宏观角度分析业务——因为业务层的优化,往往收益最大。


一、先说结论

优化收益往往和改动层级成反比:越往底层抠(SQL、缓存、线程池),收益越薄;越往业务层看,空间越大。

本文要讲的,不是调优某个 SQL,也不是给某个接口加缓存。而是质疑一个"约定俗成"的记账模式本身——当业务前提变了,模式该不该跟着变。

答案是:该变。我们改了,效果立竿见影。


二、传统的两阶段记账:一套"防御式"设计

先看现状。j'j'l 网络红包发放交易的记账,沿用的是我司各产品线虚拟账户的通用做法——两阶段记账

阶段 动作 方法
交易发起 冻结账户余额 EnterpriseAccountingService#freeze
交易成功 解冻并扣减余额 EnterpriseAccountingService#transDeduction
交易失败 解冻还原余额 EnterpriseAccountingService#unfreeze

拆开看,一次成功的交易,实际产生了 3 笔记账流水

  1. 冻结(freeze)
  2. 解冻(transDeduction 内部的解冻动作)
  3. 扣减(transDeduction 内部的扣减动作)

为什么会有两阶段?

这套模式背后有一个明确的业务前提

交易要请求三方通道(微信红包 / 支付宝零钱),而通道返回结果是异步的、不确定的、甚至可能长时间不返回的

在这种前提下,资金必须有一个"中间态":

  • 扣早了——商户如果看到交易还没完成呢,账户余额却减少了,会质疑我们系统的安全性;
  • 不扣——万一通道成功,商户余额不足,就产生了资损。

于是引入"冻结"作为中间态:先把钱锁住(既没划走,也动不了),等通道结果明确了,再决定是"解冻+扣减"还是"解冻还原"。

这是一套为"结果不确定"设计的防御式记账,逻辑自洽,没有错。


三、关键一步:回到业务,重新审视前提

针对两阶段记账,我们能做什么优化?大胆假设,小心求证。我们的做法是自顶向下,先问业务

两阶段记账的前提——"交易结果不确定、可能长时间悬置"——在 j'j'l 网络红包这个场景下,还成立吗?

结论是:不成立了。 依据是两条硬数据:

1. 时效性快。
依赖的三方通道是微信红包、支付宝零钱接口,交易通常在 10 秒内完成。没有"挂起几小时等结果"的情况。

2. 成功率高。
交易成功率 98% 以上。也就是说,绝大多数交易走的是"成功"这条路。

把这两条放一起,两阶段记账的价值链条就断了:

  • 因为快,"中间态需要长时间保持"的理由消失了;
  • 因为成功率高,"失败需要频繁回滚"的理由也被大幅削弱。

于是问题浮出水面:为了那不到 2% 的失败场景,我们让 98% 的交易都多付了 2 笔本不该有的记账开销(一次 freeze + 一次解冻)。

这不是优化不够狠的问题,是模式本身开始与业务失配了。


四、新模式:直扣 + 失败补偿

既然结果几乎总是"成功",那就反过来设计:

默认当成会成功,直接扣减;真失败了,再补回来。

阶段 动作 方法
交易发起 直接扣减 EnterpriseAccountingService#deduct
交易成功 无需记账 ——
交易失败 解冻还原余额 EnterpriseAccountingService#deductRollback

对比一下就清楚了:

  • 旧模式:成功交易 = freeze + 解冻 + 扣减 = 3 笔流水
  • 新模式:成功交易 = deduct = 1 笔流水

失败的交易,回滚逻辑和原来一致(deductRollback),没有引入新的复杂度和风险。

这就是"直扣模式"——用"事后补偿"替代"事前防御"


五、收益量化

上线后,系统压力尤其是记账服务的压力,明显缓解。具体到数字:

指标 旧模式 新模式 降幅
RPC 接口请求量 基准 —— 省掉约 ½
成功交易的记账流水 3 笔 1 笔 减少约 ⅔

每秒 80+ 笔的并发量下,这笔账是很可观的——记账服务往往是资金链路上的瓶颈,流水每少一笔,都是一次实打实的吞吐释放。


六、总结:比"改代码"更重要的是"改前提"

回头看,这次优化真正的价值不在那几行代码,而在于三件事:

  1. 自顶向下,业务先行。 先从宏观业务找空间,而不是一头扎进技术细节。业务层的一个模式调整,收益远超底层几十处微调。

  2. 质疑"约定俗成"。 两阶段记账是行业通用做法,但不代表在每个场景都最优。当"结果不确定"这个前提不成立时,防御式设计就成了纯开销。

  3. 用数据支撑决策。 "10 秒内完成"和"98% 成功率"这两条,是把"我觉得可以改"变成"必须改"的关键。没有数据,这只叫赌;有了数据,才叫重构。

一句话收尾:性能优化,先看业务对不对,再看代码快不快。业务模式没对齐,代码再快也是给错误的设计打补丁。










【公众号版本】 交易成功还要记 3 笔账?我们直接砍到 1 笔

事情是这样的。

我们有个系统叫 j'j'l,做网络红包发放的。随着交易量增多,并发也跟着涨,每秒高达 80 多笔 ————系统优化,尤其是性能优化,成了首要任务。

这笔交易的链路可不短:企业校验、风控和延迟风控、交易单和通道单落库、资金冻结记账、请求三方通道、结果落库、同步与异步查单…… 一串环节环环相扣。

这种时候,必须自顶向下,先从宏观角度分析业务。为什么?因为业务层的优化,往往收益最大。如果一上来就往下抠 SQL 慢查、加缓存、调线程池——那些有用,但收益薄。

顺着这个思路,我盯上了"记账"这块。

原来的记账,是两阶段的

先交代背景。我们发红包,需要给商户虚拟账户记账,钱不是直接扣的,中间有个"冻结"环节:

  • 交易发起 → 冻结余额
  • 三方通道(微信红包 / 支付宝零钱)返回发放成功 → 解冻 + 扣减
  • 返回发放失败 → 解冻,还原余额

这套逻辑,我司各产品线的虚拟账户都在用,是"约定俗成"的标准做法。

但注意:一笔成功的交易,背后其实记了 3 笔账——冻结、解冻、扣减。

这里先明确一个概念,免得后面看岔:文中的"扣减",指的是系统内虚拟账户的余额的减少(本地记账动作)。

为什么搞这么复杂?因为它有个前提:

三方通道的结果是异步的、不确定的,可能半天不回来。

所以钱不能直接扣,得先"锁"住,等结果明确了再决定扣还是还。

这逻辑没毛病,是给"结果不确定"做的防御式设计。

但我想问一句:这个前提,在 j'j'l系统 还成立吗?

j'j'l 交易存在两个显著的特征:

  • 第一,交易时效特别快。 微信红包、支付宝零钱,基本都是 10 秒内完成,根本不存在"挂几个小时等结果"的情况。
  • 第二,成功率高达 98% 以上。 绝大多数交易,走的是"成功"这条路。

这两条一摆出来,问题就暴露了:

为了那不到 2% 的失败场景,我们让 98% 的交易,都多付了 2 笔本不该有的记账开销。

这不叫优化不够狠,这叫模式本身开始和业务失配了。

那怎么办?反过来设计

既然结果几乎总是成功,那就别防御了,直接:

  • 交易发起 → 直接扣减账户余额
  • 交易成功 → 啥都不用做
  • 交易失败 → 再解冻还原(和原来一样)

对比一下就很直观:

  • 旧模式成功交易 = 冻结 + 解冻 + 扣减 = 3 笔流水
  • 新模式成功交易 = 扣减 = 1 笔流水

失败的回滚逻辑没动,没引入新复杂度。就是把"事前防御"换成了"事后补偿"。

收益呢?实打实的

  • RPC 请求量,省了将近一半
  • 成功交易的记账流水,从 3 笔降到 1 笔,砍掉 ⅔

每秒 80 多的并发下,记账服务本来就是资金链路的瓶颈。每少一笔流水,都是一次实打实的吞吐释放。上线后,压力明显缓解。

最后说点实在的

这次改动代码量不大,但我觉得真正的价值在三件事:

  1. 自顶向下,业务先行。 一个业务层的模式调整,顶得上底层几十处微调。
  2. 敢质疑"约定俗成"。 两阶段记账是行业通用做法,但前提变了,它就不再是保护,而是纯开销。
  3. 用数据说话。 "10 秒"和"98%",是把"我觉得可以改"变成"必须改"的关键。没数据那叫赌,有数据才叫重构。

一句话收尾:

性能优化,先看业务对不对,再看代码快不快。业务模式没对齐,代码写得再快,也是给错误的设计打补丁。

posted on 2026-09-10 10:09  buguge  阅读(10)  评论(0)    收藏  举报