一. 并发更新方案
采用在Redis中判断并更新库存(库存值可增可减),由Redis保证库存的正确性,由Kafka与MongodDB数据库事务保证最终的一致性。
二. Redis使用lua脚本来保证并发更新的正确性
1. Redis会将整个lua脚本作为一个整体执行,中间不会被其它命令插入,保证了原子性,线程安全,且不需要事务控制。
2. Redis中的值,假设正确范围在0 <= x <= max(通过脚本传参)
3. 脚本的作用是,检测key指定的值与increment之和counter是否在正确范围内;如果在正确范围内则更新为counter,更新成功返回1,更新失败返回0;如果不在正确范围内则返回0。
4. Redis执行脚本成功,不论后续发送消息成功如否,都要给业务调用方返回成功。如果执行脚本失败,直接给业务调用方返回失败,无后续动作。
三. Kafka配置与消息主要内容
生产者采用异步发送加回调的方式,若发送失败或出现异常,则必须在回调函数里保证消息一定要发送成功。
生产者的acks设置为all,保证不丢消息。
消息内容:{
key:long类型,kafka消息key,雪花算法唯一ID,
orderId:long类型,订单ID,雪花算法唯一ID,
skuId:long类型,库存商品ID,雪花算法唯一ID,
increment:int类型,增加或减少库存,
timestamp:long类型,时间戳
}
消费者配置手动提交ACK,消费消息的接口逻辑如下:
接口开启MongoDB事务(REQUIRED_NEW,rollbackFor=Exception.class)
1)插入防重表,MongoDB的集合id设置为消息的key或orderId都可以,保证唯一就行。
2)更新库存表,使用MongoDB的inc指令对库存值做原子操作。
3)返回更新结果。
因为MongoDB中的WriteConflict问题,上述接口需要重试直到保证MongoDB更新成功。消费接口调用成功后才能ACK。
重复消费的问题:
事务中需要插入防重表,重复的消息key会插入失败,MongoDB事务会回滚,保证了数据库中库存值的正确性。
四. 并发更新与补偿逻辑验证
假设库存值的正确范围在0至1000,即0 <= x <= 1000
时序 |
线程A+1 |
线程B-1 |
Redis值 |
DB值 |
备注 |
999 |
999 |
||||
执行lua脚本+1 |
1000 |
999 |
|||
发送+1消息成功 |
执行lua脚本-1 |
999 |
1000 |
||
发送-1消息成功 |
999 |
999 |
最终一致 |