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


//订单创建@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");boolean result = mqProducer.transactionAsyncReduceStock(userModel.getId(), itemId,promoId, amount,stockLogId);if(!result){throw new BusinessException(EmBusinessError.UNKNOW_ERROR,"下单失败");}return CommonReturnType.create(null);}
@TransactionalpromoId非空表示已预约活动价格下单public OrderModel createOrder(Integer userId, Integer itemId, Integer promoId,Integer amount,String stockLogId) throws BusinessException {//1.校验下单状态,商品是否存在,用户是否合法,购买数量是否正确verifyingUserInformation(itemId,userId,amount,promoId);//2.落单减库存,支付减库存orderDecreaseStock(itemId, amount);//自己添加,应对订单出现错误的时候,回滚第二步redis扣减的订单,防止噪声少买//注意必须放在订单前面订单失败才能回滚toAsynIncreaseRedisStock(itemId,amount);//3.订单入库OrderModel orderModel = orderintoMysql(itemId, userId, amount, promoId);//4.因为采用了MQ事务发送机制,而这个订单成功的时间是不确定的,使用MQ事务有事务补偿机制//所以在询问本地事务的时候返回是需要通过流水stock_log的status来判断的,当来到这里的时候说明订单都已经完成了//所以此时我们应该将stock_log中的status设置为成功状态//(1)问题又来了,当前不是又插入数据库了岂不是并发性又下降了?//这种插入数据库是根据stockLogId进行插入的每一笔订单都有自己的stockLogId,而会在stockLogId上添加行锁//这个行锁不存在并发关系,而在stock表中大家订单都需要取更改库存这个操作就是并发的,所以相当于将并发改数据库//变成了非并发更改流水表中的字段//(2)问题又来了,假如说我们当前商品只有100个,但我们有1w个人点击了在OrderController中//设置插入数据库,此时就会造成生成了1w个流水信息,产生了极大的浪费stockLogToSuccess(stockLogId);//5.返回前端return orderModel;}private void stockLogToSuccess(String stockLogId) throws BusinessException {StockLogDO stockLogDO = stockLogDOMapper.selectByPrimaryKey(stockLogId);if(stockLogDO == null){throw new BusinessException(EmBusinessError.UNKNOW_ERROR);}//如果能拿到这个订单表的字段就将status更改为2stockLogDO.setStatus(2);stockLogDOMapper.updateByPrimaryKeySelective(stockLogDO);}
4.的ACK机制
目前已经解决了,和之间的发送消息事务问题 。保证了订单生成成功后,MQ才进行发送消息给Mysql消息 。但是目前是否已经能消费成功这也是一个系统问题 。假如说,订单生成没问题,redis减库存,然后MQ就发送消息,返回给前端已经下单成功了 。但是此时消费出现了错误此时MQ因为ACK机制,会有消息重试机制 。默认重试16次,如果16次尝试都失败了 。这条消息就进入了死信队列 。在死信队列中出现消息,就需要人工介入处理了 。
如果想体验插入数据库中的例子可以参考如下代码
代码
@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