把区块链系统当成订单系统来做后,剩下的 10 个 Java 设计点
这篇不讲区块链原理,只讲一个更熟的实现视角:
把它当成一个高并发、长生命周期、会失败回退、还要频繁查询的订单系统。
这样拆,很多设计会一下子顺起来。看到的也就不只是区块、交易和 UTXO,而是恢复、索引、状态机、补偿、游标分页这些更常见的工程问题。
1. 启动时先恢复,不要假设自己是第一次跑
系统启动不会从“空白”开始,它会先把本地身份、主链 tip、运行状态找回来。订单系统也一样,服务重启后不能假设一切重来,得先恢复:
- 上次消费位点
- 未完成订单
- 锁库存但未支付的单
- 延迟任务游标
- 热点缓存
长生命周期系统,恢复不是补充题,是主流程的一部分。
2. 一笔业务事实通常会写很多地方
订单创建成功,不会只写一张 orders。它还会影响:
- 订单明细
- 用户订单索引
- 商家待发货列表
- 支付映射
- 状态日志
- 库存锁定视图
主事实表回答“发生了什么”,索引和投影表回答“以后怎么快查”。
3. 事务边界应该跟业务结果对齐
真正重要的不是 DAO 怎么拆,而是这笔业务事实能不能一次性落稳。要么一起成功,要么一起失败。订单创建时如果只写了一半,系统就会开始脏。
这类系统的事务边界,应该围着业务结果画,而不是围着代码层级画。
4. 所有改核心状态的输入,都要先过统一入口
支付回调、补单、用户操作、批处理修复,最后最好都走一条统一的状态变更入口。这样规则不会分叉,也不会出现不同入口各写一套校验的情况。
对系统来说,最怕的不是慢,而是同一件事被不同入口改出不同结果。
5. 事件不要只分成功和失败
复杂系统里,很多消息不是“到”或者“没到”这么简单。它可能先到、后到、缺父节点、需要重放、需要挂起。
这套系统里把这些状态分得很细,很像订单系统里的现实:
- 有些单可以直接处理
- 有些单要先挂起
- 有些单等依赖补齐再继续
- 有些单要在重试里慢慢收回来
如果状态机只有成功和失败,后面一定会补出很多临时逻辑。
6. 高频查询先想索引,再想 SQL
用户中心、客服后台、商家后台,都是高频查询。正确姿势通常不是先写一个大 SQL,而是先想:
- 这类查询有没有单独的索引
- 能不能先拿 ID,再回表
- 能不能用游标分页
这类系统里,分页和索引是一起设计的,不是最后补的。
7. 一种状态变化最好只有一个总入口
用户支付、人工补单、回调确认、MQ 消费、修复脚本,最后都应该收敛到一个状态变更路径。入口一多,状态规则就会散。
复杂系统想稳,入口一定要少。
8. 失败后先补偿,不要先解释
发货失败、确认失败、下游失败,第一步不是打日志给人看,而是把临时占位撤掉,把系统拉回还能继续工作的状态。
补偿做完了,再考虑怎么解释失败。
9. 读性能和写一致性本来就是拉扯
你想让列表快,就得维护更多索引。你想让写入稳,就得接受更复杂的事务和状态流转。这不是谁对谁错,就是 trade-off。
这类系统真正成熟的地方,不是把矛盾消灭了,而是知道该把复杂度放在哪一层。
10. 先落事实,再更新缓存、内存和通知
顺序很重要。数据库里先确认,再去改缓存、发消息、推通知。这个顺序错了,内存和持久化很容易说两套话。
订单系统和这类链式系统,都一样。
最后记住的一句话
如果把这套区块链系统翻成订单系统,它讲的其实就是一件很朴素的事:
先校验,再入队;先锁资源,再进入处理;成功才落事实,失败只回退临时状态。
这就是最后能留下来的 10 个设计点。