分布式事务-异步方案

分布式事务解决方案主要有如下4种:

  • 不同的解决方案应对不同的业务场景~

    

 

此处主要讨论异步处理场景

~~一个外卖的业务场景~~

 

  • 此场景中有独立的2个业务系统:1、订单中心 2运单中心;业务逻辑为:用户下单,订单中心生成订单,并把订单传递到运单中心,由运单中心生成配送单,并分配给外卖小哥配送~那么2个系统怎样保障数据一致性呢?!

 

  • 正确的设计思路,通过可靠的消息中间件来保障

 

  • 此处需要保障2个点:1、可靠生产,保证消息一定发送到mq服务;2、可靠消费,保证消息取出一定被正确消费;最终多方数据达到一致。

 

 具体实现步骤~:

  1. 可靠消息生产 - 记录消息发送,增加本地消息表,记录消息的发送状态
  2. 可靠消息生产 - 修改消息发送状(开启发布确认机制,mq准确受理消息会返回回执)

     

  3. 可靠消息处理 - 正常处理 注意:1、开启手动ack模式;2、幂等性。 
  4. 可靠消息处理 - 消息重发(出现异常会重发几次,由消费者自己记录重发次数,并进行次数控制)
  5. 可靠消息处理 - 消息丢弃(消息处理失败,丢弃或者转移死信队列-DLQ)

     

示例代码实现: 

  • 订单中心发送消息
    /**
     * 这是一个发送MQ消息,修改消息表的地方
     *
     */
    @Service
    @Transactional(rollbackFor = Exception.class)
    public class MQService {
        private final Logger logger = LoggerFactory.getLogger(MQService.class);
    
        @Autowired
        JdbcTemplate jdbcTemplate;
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        @PostConstruct
        public void setup() {
            // 消息发送完毕后,则回调此方法 ack代表发送是否成功
            rabbitTemplate.setConfirmCallback(new ConfirmCallback() {
                @Override
                public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                    // ack为true,代表MQ已经准确收到消息
                    if (!ack) {
                        return;
                    }
    
                    try {
                        // 2. 修改本地消息表的状态为“已发送”。删除、修改状态
                        String sql = "update tb_distributed_message set msg_status=1 where unique_id=?";
                        int count = jdbcTemplate.update(sql, correlationData.getId());
    
                        if (count != 1) {
                            logger.warn("警告:本地消息表的状态修改不成功");
                        }
                    } catch (Exception e) {
                        logger.warn("警告:修改本地消息表的状态时出现异常", e);
                    }
    
                }
            });
        }
    
        /**
         * 发送MQ消息,修改本地消息表的状态
         * 
         * @throws Exception
         */
        public void sendMsg(JSONObject orderInfo) throws Exception {
            // 1. 发送消息到MQ
            // CorrelationData 当收到消息回执时,会附带上这个参数
            rabbitTemplate.convertAndSend("createOrderExchange", "", orderInfo.toJSONString(),
                    new CorrelationData(orderInfo.getString("orderId")));
        }
    }

     

rabbitmq:
    # 重要!  开启消息发送确认机制
    publisher-confirms: true

 

  • 运单中心消费消息
    /**
     * 消费者,取调度队列
     *
     */
    @Component
    public class OrderDispatchConsumer {
        private final Logger logger = LoggerFactory.getLogger(OrderDispatchConsumer.class);
    
        @Autowired
        DispatchService dispatchService;
    
        @RabbitListener(queues = "orderDispatchQueue")
        public void messageConsumer(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag)
                throws Exception {
            try {
                // mq里面的数据转为json对象
                JSONObject orderInfo = JSONObject.parseObject(message);
                logger.warn("收到MQ里面的消息:" + orderInfo.toJSONString());
                Thread.sleep(5000L);
    
                // 执行业务操作,同一个数据不能处理两次,根据业务情况去重,保证幂等性。 (拓展:redis记录处理情况)
                String orderId = orderInfo.getString("orderId");
                // 这里就是一个分配外卖小哥...
                dispatchService.dispatch(orderId);
                // ack - 告诉MQ,我已经收到啦
                channel.basicAck(tag, false);
            } catch (Exception e) {
                // 异常情况 :根据需要去: 重发/ 丢弃
                // 重发一定次数后, 丢弃, 日志告警
                channel.basicNack(tag, false, false);
                // 系统 关键数据,永远是有人工干预
            }
            // 如果不给回复,就等这个consumer断开链接后,mq-server会继续推送
    
        }
    }

     

rabbitmq: 
# 重要,开启手动ack,控制消息在MQ中的删除、重发...
listener:
simple:
acknowledge-mode: MANUAL

 

   总结

通过mq处理分布式事务

  • 优点:
  • 缺点:

 

最后~~~

 

posted @ 2019-10-23 17:16  编号9527L  阅读(566)  评论(0编辑  收藏  举报