20250908debug
今天用黑马点评加rocketmq,想测试一下模拟大量用户下单
项目代码见CuSO41108/hmdp_init
遇到了经典的分布式事务数据不一致的问题:
我用ai写的测试用例,因为上次生成5000个token用jmeter压测失败了(暂未解决)
第一次测试:redis中stock为1000,数据库也为1000,THREAD_COUNT = 5000。
结果: 数据库最后剩下9,redis:stock剩下0 (这个时候忘记看voucher_order和seckill_voucher了)
起初是问了ai:
MQ重复消费 的可能性存在。必须保证handleVoucherOrder 和 createVoucherOrder 是 幂等的
已经有幂等校验:
1 | Long count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count(); |
// 在 handleVoucherOrder 中:
1 | boolean isLock = lock.tryLock(); |
对于 onMessage
方法的 try
块来说,这个调用没有抛出任何异常,它“成功”执行完了。
catch
块没有捕获到任何东西。
onMessage
方法正常结束。
RocketMQ 收到一个“成功”的 ACK,于是心满意足地将这条消息标记为“已消费”并删除。
往以下2个方面修了一下:
修复静默失败(启用 RocketMQ 重试机制)
配置死信队列 + 补偿 Redis
之后,进行第二次测试:
测试结果如下,数据库方面,voucher_order新增1073条,seckill_voucher库存从1000->19,redis方
面:seckill:stock:10从1000—>0,seckill:order:10新增1000.
92 个订单 = 1073 (总数) - 981 (正常订单)。这意味着有 92 次重复消费(Bug #1)导致了订单创建。这 92 次重复消费,由于事务隔离性的巧合,它们执行时 count()=0 通过了,但 update stock 失败了(因为库存早就是 0 了),但 save(order) 却成功了。
原因:
@Transactional 没能回滚 save(order) !
于是加了:
ALTER TABLE tb_voucher_order
ADD UNIQUE KEY UK_USER_VOUCHER
(user_id
, voucher_id
); //了解到联合唯一索引
加上这个索引后的效果:
当 RocketMQ 重试消息时,即使你的 count() 检查因为事务隔离级别而失效(读到了旧快照),当 save(order) 尝试插入一个重复的(user_id, voucher_id)组合时,数据库会立即抛出 DuplicateKeyException。这个异常会导致整个事务(包括 update stock****)回滚,然后这个异常会被 RocketMQ 捕获,消息会继续重试,直到最后进入死信队列 (DLQ)。
加了联合唯一索引后,解决了!