分布式事务-异步方案
分布式事务解决方案主要有如下4种:
-
不同的解决方案应对不同的业务场景~
此处主要讨论异步处理场景
~~一个外卖的业务场景~~
-
此场景中有独立的2个业务系统:1、订单中心 2运单中心;业务逻辑为:用户下单,订单中心生成订单,并把订单传递到运单中心,由运单中心生成配送单,并分配给外卖小哥配送~那么2个系统怎样保障数据一致性呢?!
-
正确的设计思路,通过可靠的消息中间件来保障
-
此处需要保障2个点:1、可靠生产,保证消息一定发送到mq服务;2、可靠消费,保证消息取出一定被正确消费;最终多方数据达到一致。
具体实现步骤~:
- 可靠消息生产 - 记录消息发送,增加本地消息表,记录消息的发送状态
- 可靠消息生产 - 修改消息发送状(开启发布确认机制,mq准确受理消息会返回回执)
- 可靠消息处理 - 正常处理 注意:1、开启手动ack模式;2、幂等性。
- 可靠消息处理 - 消息重发(出现异常会重发几次,由消费者自己记录重发次数,并进行次数控制)
- 可靠消息处理 - 消息丢弃(消息处理失败,丢弃或者转移死信队列-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处理分布式事务
- 优点:
- 缺点:
最后~~~