3、最终直到redis中的库存减到0,停止售卖次商品 。
/**负责createOrder中的减库存操作*/private void orderDecreaseStock(Integer itemId, Integer amount) throws BusinessException {boolean result = itemService.decreaseStock(itemId, amount);if (!result) {throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH);}}
@Override@Transactionalpublic boolean decreaseStock(Integer itemId, Integer amount) {Long result = redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue() * -1);if (result >= 0) { // > 变 >= 再分化return true;}else {//库存为负,此次交易失败,补回redis中库存redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue());return false;}}
好处:因为redis是内存级别的效率非常高
坏处:redis和数据库中的数据不同步,如果redis宕机会导致用户购买商品的数量信息全部丢失 。简单来说就是缓存中的数据未能和数据库中的数据产生任何一致性,这种方法是不可取的
二、引入 1.思考为什么要引入MQ
因为要同步redis和Mysql中的数据同步 。
而MQ的左右就是在扣除redis中的stock之后,发送消息给Mysql让数据库扣减库存,异步更新Mysql保证redis和Mysql的最终一致性 。
关于的一般使用和简单概念,可看我这篇博客:
但是每当引入了一个新的结构,系统变得复杂了,此时就需要考虑这个下单过程中会出现那些问题,采取什么样的结构 。
2.下单采取的结构
第一种结构采用同步异步更新数据库
采用同步方式发送更新数据库消息,必定等到成功发送消息给才能算订单成功,返回给前端 。
第二种结构采用的是前端轮询加后端假异步
采用异步发送方式,并不需要等待消息是否发送成功,但是前端不能直接返回给用户,说下单成功,前端采用长轮询方式不断的访问后端是都更新数据库成功了,只有后端都成功才会发给前端消息,所以也称之为假异步 。
本项目中我们采用的是第一种结构
2.MQ遇到的问题
MQ发送更新数据库扣减库存的消息,应该在校验环节和订单生成环节都完成之后,在进行MQ的发送 。因为中的@只能回滚Mysql并不能回滚MQ发送的消息 。如果MQ先发送消息,之后订单方法出错了,此时就会白白的在Mysql中扣除一个库存导致,订单少买
如何解决这个问题呢?
使用事务机制 。
【秒杀预约活动项目中如何高效的保证下单交易成功?保证redis】3.事务机制
事务之正常事务过程
可以采用中的事务发送,将校验和生成订单放在的本地事务来做,只有本地事务成功了,才会发送这条消息给,本地事务失败了,MQ就不会发送消息给,整个订单就会回滚 。
事务之正常补偿过程
假设在创建订单这个过程发生了很长的时间,创建订单在数据库压力比较大的情况下可能用了10s多 。这样MQ就发现本地事务一直没有说提交成功还是提交失败回滚,处于一个的状态于是就需要n方法回调判断是否下单是否是成功的 。
我们需要创建一个新的数据表流水日志表表 。
当前中的决定当前订单的状态,2代表成功,3代表回滚,1代表不知道 。
每次当订单生成之后改动当前这条流水的状态,决定是否补偿结果 。
代码
@Componentpublic class MqConsumer {private DefaultMQPushConsumer consumer;@Value("${mq.nameserver.addr}")private String nameAddr;@Value("${mq.topicname}")private String topicName;@Resourceprivate ItemStockDOMapper itemStockDOMapper;@PostConstructpublic void init() throws MQClientException {consumer = new DefaultMQPushConsumer("stock_consumer_group");consumer.setNamesrvAddr(nameAddr);//订阅所有topicName的消息consumer.subscribe(topicName,"*");//consumer处理过程consumer.registerMessageListener(new MessageListenerConcurrently() {@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List
- 2023华为产品测评官活动火热开启——发出属于你的开发者之声,赢取丰厚奖品!
- 51cto首次户外纪实
- 跑步准备活动
- 转载 UCenter Home如何做线下活动
- 9月流量扶持活动入选名单出来啦!
- 三国中的名将颜良为何会被关羽秒杀?
- 美容店通过什么活动能最快吸引顾客?
- 企业私域流量活动参与率怎么提高?
- 受众同步管理功能上线,让你的活动礼包发对人
- 实用干货 高中数学,高考压轴三角形难题秒杀技巧