秒杀 预约活动项目中如何高效的保证下单交易成功?保证redis( 六 )

msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) {//实现库存真正到数据库内扣减的逻辑Message msg = msgs.get(0);String jsonString = new String(msg.getBody());Map map = JSON.parseObject(jsonString, Map.class);Integer itemId = (Integer) map.get("itemId");Integer amount = (Integer) map.get("amount");System.out.println(10/0);itemStockDOMapper.decreaseStock(itemId,amount);return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();}
因10/0出错,所以就会不断重试重试,所以ACK机制就是保证端消费成功 。
三、redis完善系统
系统还可以有两个优化 。
1.实现redis中Stock数据一致性
@标签同样不能回滚redis中的stock数据,在进行redis扣减库存之后,如果插入订单失败了,此是redis也没回滚相当于少买了 。
方法可以采用的ion方法里面实现事务适配器中的方法 。这样就在事务回滚的同时保证了redis的一致性 。
代码
@Transactionalpublic void toAsynIncreaseRedisStock(Integer itemId, Integer amount) throws BusinessException{TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {@Overridepublic void afterCompletion(int status) {//参考连接:https://www.cnblogs.com/ciel717/p/16190723.html/**status 0:外部方法事务提交后执行的业务逻辑*1:外部方法事务回滚后执行的业务逻辑*2:外部方法事务异常时执行的业务逻辑*/if(status!=0){itemService.asynchronousIncreaseStock(itemId,amount);}}});}
@TransactionalpromoId非空表示已预约活动价格下单public OrderModel createOrder(Integer userId, Integer itemId, Integer promoId,Integer amount,String stockLogId) throws BusinessException {/*** 1.校验下单状态下单商品是否存在,用户是否合法,购买数量是否正确* 2.采取落单减库存(一般电商分为两种一种是落单减库存,支付减库存)* 落单减库存就是用户先落单了,此时系统就去锁库存了 。也就是再支付之前用户就相当于拥有了这个商品 。* 而支付减库存,就是落单了,系统检测还有库存,但是用户在支付完后,再次系统去锁库存,发现已经没有* 库存了只能给用户退款了 。* 3.订单入库* 4.返回前端*///1.校验下单状态,商品是否存在,用户是否合法,购买数量是否正确verifyingUserInformation(itemId,userId,amount,promoId);//2.落单减库存,支付减库存orderDecreaseStock(itemId, amount);//应对订单出现错误的时候,回滚第二步redis扣减的订单,防止噪声少买//注意必须放在订单前面订单失败才能回滚toAsynIncreaseRedisStock(itemId,amount);//3.订单入库OrderModel orderModel = orderintoMysql(itemId, userId, amount, promoId);stockLogToSuccess(stockLogId);//5.返回前端return orderModel;}
2.redis实现售空机制
假如说我们当前商品只有100个,但我们有1w个人点击了在中 。设置插入数据库,此时就会造成生成了1w个流水信息,产生了极大的浪费 。所以我们希望,当卖完这一百个之后,就不再产生订单直接从后台提示前端商品全部销售完毕 。
做法就是在redis中设置一个标志位“alid_"+如果是false就是还没销售空,如果是true就是销售空了 。
代码
//订单创建@PostMapping(value = "http://www.kingceram.com/order/createOrder", consumes = {CONTENT_TYPE_FORMED})public CommonReturnType createItem(@RequestParam(name = "itemId")Integer itemId,@RequestParam(name = "amount")Integer amount,@RequestParam(name = "promoId",required = false)Integer promoId) throws BusinessException {//判断是否登录Boolean isLogin = (Boolean) httpServletRequest.getSession().getAttribute("IS_LOGIN");if(isLogin == null || !isLogin){throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "请登录后再进行预约");}//获取用户的登录信息UserModel userModel = (UserModel) httpServletRequest.getSession().getAttribute("LOGIN_USER");//为了根据checkLocalTransaction确定消息的状态,需要引入操作流水(操作型数据:log data)//他的作用就是先插入一条异步流水,根据这个status用来追踪异步扣减库存这个消息 。//就是根据你这个流水的状态来确定MQ返回是成功还是失败还是不知道//若库存不足直接返回下单失败//(2)问题又来了,假如说我们当前商品只有100个,但我们有1w个人点击了在OrderController中//设置插入数据库,此时就会造成生成了1w个流水信息,产生了极大的浪费if(redisTemplate.hasKey("promo_item_stock_invalid_"+itemId)){throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH);}String stockLogId = itemService.initStockLog(itemId, amount);//OrderModel orderModel = orderService.createOrder(userModel.getId(), itemId, promoId, amount);//因为需要保证MQ中信息发送必须成功,所以采用rocketMQ事务boolean result = mqProducer.transactionAsyncReduceStock(userModel.getId(), itemId,promoId, amount,stockLogId);if(!result){throw new BusinessException(EmBusinessError.UNKNOW_ERROR,"下单失败");}return CommonReturnType.create(null);}