面临的课题
订单在线处理系统需要增加一个计算器功能,统计某个商品的销量,然后根据商品的渠道销量设置当前这笔订单的佣金。
订单关键字段:tradeId,itemId,quantity
难点分析:
累加和计算这2个步骤都有可能抛异常,这两步操作整体要保持一致性,计算需要依赖于当前累加的结果。
如何解决
初步思路:
累加和计算必须保证原子性,要么一起成功,要么一起失败。即:如果累加成功,计算异常了,需要一起回滚。
再仔细思考一下,这样不仅仅是2个步骤要包装成一个分布式事务,下面很多后续步骤都需要整合在一起(因为他们都可能发生异常)。分布式事务的范围肯定越小越好,因此考虑是否可以只把累加进行事务的处理。
再思考:
如果累加成功,计算异常了,认为累加“占坑”成功,不回滚;
然后需要保证后续计算的重试一定可以成功(超过重试次数,记录日志人工进行处理)。
存储结构:
表1:itemId,tradeId,curr_count(用来作为是否处理过得依据、以及重试时要拿到之前的count)
表2:itemId,sum_count(全局的数量)
note:利用db的事务机制(实现时使用注解@Transaction),保证这2张表的一致性。
解法:
事项 | 加锁 | 备注 |
begin |
| |
1、查询表1获取当前的总数 select curr_count from table1 | 幂等,防止并发问题, 即如果已经存在,则直接返回当前的count。 | |
2、查询表2获取当前的总数 select sum_count from table2 | ||
3、更新当前表2的数据 update table2 set sum_count=sum_count+n where sum_count=xxx | update 加锁 | mysql 事务乐观锁机制 |
4、插入当前表1的数据 insert into table1 (itemId, tbTradeId, sum_count) | insert加锁 | |
commit | 释放锁 |
note:
如果并发(两条相同消息同时到达)导致幻读,insert会抛异常,这个时候上游触发重试即可。
也可以捕获主键冲突的异常,进行额外的处理。
理论支撑
整体db层面的行级锁由db的隔离机制来保证,
mysql的事务默认隔离级别是 Repeatable Read(可重读)。
详细可见:深入理解Mysql的四种隔离级别