6_86.RabbitMQ解决分布式事务问题

一、RPC分布式事务产生的背景

分布式事务产生的背景:

  1. 在rpc通讯中,每个服务都有自己独立的数据源,每个数据源都互不影响;
  2. 在单个项目存在多个不同jdbc连接(多数据源)
    6_86.RabbitMQ解决分布式事务问题_第1张图片

二、如何理解分布式事务最终一致性

6_86.RabbitMQ解决分布式事务问题_第2张图片

强一致性:

也就是我们的线程b读取到的结果一定是为mayikt而不是yushengjun;

强一致性解决方案:

要么数据库A非常迅速的速度将数据同步给数据库B,
或者数据库A没有同步完成之数据库B不能够读取userName。

弱一致性:

允许读取数据是为老的数据 允许读取的结果不一致性。

最终一致性的概念:

因为在分布式系统,数据的同步走网络通讯、多多少少可能会收到网络的延迟、抖动数据会产生延迟,这是非常正常现象。
所以呢,短暂的数据延迟是允许的,但是最终结果一定要同步
最终一致性 补偿和人工补偿就是这么来的。

三、基于RabbitMQ解决分布式事务思路一

6_86.RabbitMQ解决分布式事务问题_第3张图片

四、基于RabbitMQ解决分布式事务思路二

如何基于我们的MQ解决我们的分布式事务的问题 (最终一致性概念)

  1. 确保我们的生产者往我们的MQ投递消息一定要成功(生产消息确认confirm机制) 实现重试。
  2. 确保我们的消费者能够消费成功 (手动的ack机制) 如果消费失败的情况下, MQ自动帮消费者重试
  3. 确保我们的生产者第一事务先执行成功,如果执行失败采用补单队列。

五、订单系统代码实现

1.OrderMapper

public interface OrderMapper {

    @Insert(value = "INSERT INTO `order_info` VALUES (#{id}, " +
            "#{name}, #{orderCreatetime}, #{orderMoney}, #{orderState}, #{commodityId},#{orderId})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    public int addOrder(OrderEntity orderEntity);


    @Select("SELECT * from order_info where orderId=#{orderId};")
    public OrderEntity findOrderId(@Param("orderId") String orderId);

}

2.OrderRabbitMQConfig

@Component
public class OrderRabbitMQConfig {

    /**
     * 派单队列
     */
    public static final String ORDER_DIC_QUEUE = "order_dic_queue";
    /**
     * 补单对接
     */
    public static final String ORDER_CREATE_QUEUE = "order_create_queue";
    /**
     * 派单交换机
     */
    private static final String ORDER_EXCHANGE_NAME = "order_exchange_name";

    /**
     * 定义派单队列
     *
     * @return
     */
    @Bean
    public Queue directOrderDicQueue() {
        return new Queue(ORDER_DIC_QUEUE);
    }

    /**
     * 定义补派单队列
     *
     * @return
     */
    @Bean
    public Queue directCreateOrderQueue() {
        return new Queue(ORDER_CREATE_QUEUE);
    }


    /**
     * 定义订单交换机
     *
     * @return
     */
    @Bean
    DirectExchange directOrderExchange() {
        return new DirectExchange(ORDER_EXCHANGE_NAME);
    }


    /**
     * 派单队列与交换机绑定
     *
     * @return
     */
    @Bean
    Binding bindingExchangeOrderDicQueue() {
        return BindingBuilder.bind(directOrderDicQueue()).to(directOrderExchange()).with("orderRoutingKey");
    }

    /**
     * 补单队列与交换机绑定
     *
     * @return
     */
    @Bean
    Binding bindingExchangeCreateOrder() {
        return BindingBuilder.bind(directCreateOrderQueue()).to(directOrderExchange()).with("orderRoutingKey");
    }


}

3.生产者

@Component
@Slf4j
public class OrderProducer implements RabbitTemplate.ConfirmCallback {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 基于MQ发送投递订单消息
     */
    @Transactional
    public String sendOrder() {
        // 1.先创建我们订单信息
        String orderId = System.currentTimeMillis() + "";
        OrderEntity orderEntity = createOrder(orderId);
        // 2.添加到我们的数据库中
        int result = orderMapper.addOrder(orderEntity);
        if (result <= 0) {
            return null;
        }
        // 3.订单数据库插入成功的情况下, 使用MQ异步发送派单信息
        String msgJson = JSONObject.toJSONString(orderEntity);
        sendMsg(msgJson);
        return orderId;
    }


    @Async
    public void sendMsg(String msgJson) {
        // 设置生产者消息确认机制
        this.rabbitTemplate.setMandatory(true);
        this.rabbitTemplate.setConfirmCallback(this);
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId(msgJson);
        String orderExchange = "order_exchange_name";
        String orderRoutingKey = "orderRoutingKey";
        rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msgJson, correlationData);
    }


    public OrderEntity createOrder(String orderId) {
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setName("20200728rabbit分布式事物呀");
        orderEntity.setOrderCreatetime(new Date());
        // 价格是300元
        orderEntity.setOrderMoney(300d);
        // 状态为 未支付
        orderEntity.setOrderState(0);
        Long commodityId = 30L;
        // 商品id
        orderEntity.setCommodityId(commodityId);
        orderEntity.setOrderId(orderId);
        return orderEntity;
    }


    /**
     * correlationData 投递失败回调的消息
     *
     * @param correlationData
     * @param ack             true 投递到MQ成功 如果是为false情况下 消息投递失败
     * @param s
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String s) {
        String msg = correlationData.getId();
        if (!ack) {
            log.info("<<<往MQ投递消息失败>>>>:" + msg);
            // 采用递归算法重试
            sendMsg(msg);
            return;
        }
        log.info("<<<往MQ投递消息成功>>>>:" + msg);
        // 生产者投递多次还是is的情况下应该 人工记录
    }
}

六、派单系统代码实现

1.DispatchEntity


@Data
public class DispatchEntity {

    private Long id;
    // 订单号
    private String orderId;

    // 派单id
    private Long userId;

    public DispatchEntity(String orderId, Long userId) {
        this.orderId = orderId;
        this.userId = userId;
    }
}

2.DispatchConsumer派单消费者

@Component
public class DispatchConsumer {
    @Autowired
    private DispatchMapper dispatchMapper;

    @RabbitListener(queues = "order_dic_queue")
    public void dispatchConsumer(Message message, Channel channel) throws IOException {
        // 1.获取消息
        String msg = new String(message.getBody());
        // 2.转换json
        JSONObject jsonObject = JSONObject.parseObject(msg);
        String orderId = jsonObject.getString("orderId");
        // 计算分配的快递员id
        DispatchEntity dispatchEntity = new DispatchEntity(orderId, 1234L);
        // 3.插入我们的数据库
        int result = dispatchMapper.insertDistribute(dispatchEntity);
        if (result > 0) {
            // 手动将该消息删除
            // 手动ack 删除该消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }

    }
}

3.DispatchMapper

public interface DispatchMapper {

    /**
     * 新增派单任务
     */
    @Insert("INSERT into platoon values (null,#{orderId},#{userId});")
    int insertDistribute(DispatchEntity distributeEntity);


}

4.订单OrderController

@RestController
public class OrderController {
    @Autowired
    private OrderProducer orderProducer;

    @RequestMapping("/sendOrder")
    public String sendOrder() {
        return orderProducer.sendOrder();
    }
}

5.启动类

@SpringBootApplication
@MapperScan("com.mayikt.mapper")
//@EnableAsync
public class AppProducer {

    public static void main(String[] args) {
        SpringApplication.run(AppProducer.class);
    }
}

6. 订单配置文件

6_86.RabbitMQ解决分布式事务问题_第4张图片

7. 派单配置文件

修改端口号即可

11

七、分布式事务补单队列的实现

6_86.RabbitMQ解决分布式事务问题_第5张图片
因为有事物,订单插入数据会滚,但是派单数据库已经插入数据。
需要订单惊醒补单操作

补单消费者CreateOrderConsumer

@Component
public class CreateOrderConsumer {
    @Autowired
    private OrderMapper orderMapper;

    /**
     * 订阅补单队列
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(queues = "order_create_queue")
    public void createOrderConsumer(Message message, Channel channel) throws IOException {
        // 1.获取消息
        String msg = new String(message.getBody());
        // 2.获取order对象
        OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
        // 3.获取订单号码
        String orderId = orderEntity.getOrderId();
        // 4.根据订单号码查询是否存在
        OrderEntity dbOrderEntity = orderMapper.findOrderId(orderId);
        if (dbOrderEntity != null) {
            // 手动ack 删除该消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            return;

        }
        // 说明该订单不存在,创建该订单
        // 2.添加到我们的数据库中
        int result = orderMapper.addOrder(orderEntity);
        if (result > 0) {
            // 手动ack 删除该消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }

    /***
     * 补单的消费不应该和 订单生产者放到一个服务器节点
     * 补单消费者如果不存在的情况下 队列缓存补单消息
     * 补偿分布式事务解决框架 思想最终一致性
     */
}

在这里插入图片描述

你可能感兴趣的:(6期,rabbitmq)