技术文章
全部来自我的零售 ERP SaaS 仓库的真实代码、真实参数与真实事故——它们同时是这个项目「代码可走读」承诺的入口。
项目实战复盘
9 篇两套 Outbox:把"至少一次"圈进可管理的边界里
单据审核通过的一瞬间要发生太多事:扣库存、记财务账、更新统计。最早我把这些塞在审核事务里同步做——事务越拉越长,任何一个下游抖一下,用户点"审核"就转圈失败;后来拆成 MQ 异
一段 Lua 扣四类 Key:促销防超卖的原子设计
促销上线第一周就被运营找上门:限时购活动设了 100 件库存,卖完后台一看,成交记录加起来是 104 件。复盘日志,两笔订单几乎同时进来,各自读到 stock=1,各自判断"够
库存不是数,是流水:不可变事件账本的设计
做零售 ERP 的人迟早会面对一次"账对不上":库存表显示还有 3 件,货架上没有;或者月底盘点,成本金额和库存变动对不出。当年我靠 UPDATE quantity = qua
多租户系统的 ThreadLocal 上下文:写进去只是第一步
一个真实 bug 带来这篇文字:测试环境里,A 租户的运营反馈看到了 B 租户的门店数据。查了一圈,SQL 没写错,拦截器在位,最后定位到那条请求走的是 @Async——wor
用 JSqlParser 改写 SQL:自研数据权限引擎管住 137 张表
错误日志里有一句我亲手写不出来也删不掉的 SQL:
当天第一单的流水号是错的:MySQL 序列表取号的三个坑
商户报:每天早上第一张单的流水号不是 1,是一个不知从哪来的大数。
单号超长,收银全挂:一次由"预留号段"引发的线上事故
收银台扫码,主扫、被扫,全部失败。渠道只回一句:数据不符合约束要求。
从一次重复记账的风险说起:支付回调幂等要落在存储层
对账巡检那天,同一笔渠道交易在账面上出现了两次。
线上查不到数据,但没有任何报错:一次租户上下文"静默查空"的排查
没有报错,就是查不到数据。
支付与交易
2 篇消息与分布式事务
3 篇f04-distributed-tx-choices
单据审核成功后,下游的记账、库存、打印要跟着动。这些下游动作部分经由 RabbitMQ 通知,于是有一个绕不开的问题:审核落库和"通知下游"这两件事,怎么保证要么都发生、要么都
f02-ttl-dlx-delay-retry
门店的小票打印机偶尔离线。最早的实现很"自然":listener 消费失败,Thread.sleep(1000) 睡一秒再重试。结果某天三台打印机同时掉线,listener 容
f01-outbox-pattern-implementation
单据审核通过了,打印没出、库存台账没动。翻日志,下游模块一脸无辜:没收到消息。再翻一遍代码——审核逻辑最后一步发 RabbitMQ,前面那段事务里有个校验抛了异常回滚。消息发出
MySQL
3 篇MySQL 没有 ADD COLUMN IF NOT EXISTS:414 个迁移脚本的幂等范式
部署窗口里,一个 ALTER 语句把整个发版卡死了:ERROR 1064 (42000),语法错误。脚本在我的库上跑得好好的,到生产 MySQL 直接报错。定位到的那行 SQL
防超卖:一条 UPDATE 条件语句凭什么比 SELECT FOR UPDATE 强
门店盘点后对不上账:系统里的可卖数量比货架上的实物多 3 件。查了两个小时的变更流水,发现是两个收银员同一秒各卖了一瓶同一批次的酒,两次扣减都"成功"了——库存基数被并发改穿。
幂等的最后一道门,永远在数据库里
一次联调,渠道把同一笔支付通知发了三遍(应答超时触发重试),我的服务第一遍记了账、第二三遍被 Redis 分布式锁挡住了。看起来防线工作正常——直到有人问:如果服务在持锁期间重