微服务架构-分布式消息中间件-086:RabbitMQ解决分布式事务问题

1 RPC分布式事务产生的背景

课程内容:

  1. 30分钟未支付,系统自动超时关闭订单实现方案
  2. 消息中间件自动补偿触发原理
  3. 消息中间件如何合理选择重试机制
  4. 消息中间件如何解决消费者幂等性
  5. SpringBoot整合消息确认机制

业务场景:使用公众号下单邮寄包裹,下单后会调用派单接口,快递员上门取件
在这里插入图片描述
分布式事务产生的背景:

  1. 在rpc通讯中,每个服务都有自己独立的数据源,每个数据源都互不影响;
  2. 在单个项目存在多个不同jdbc连接(多数据源)

每个jdbc连接都有自己独立的事务数据管理。

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

基于RabbitMQ解决分布式事务的思路采用最终一致性的方案
在这里插入图片描述
强一致性:线程B读取userName一定为mayikt;
解决方案:数据库A非常迅速的将数据同步给数据库B,或者数据库A没有同步完成之前数据库B不能够读取userName。
弱一致性:允许读取数据为老的数据,允许读取的结果不一致性。

在分布式系统中,数据的同步走网络通讯,多多少少可能会因为网络的延迟、数据抖动产生延迟,这是正常现象,因此不能保证强一致性。但是最终结果一定要同步,这就是最终一致性,涉及补偿和人工补偿。

3 基于RabbitMQ解决分布式事务思路

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

  1. 确保生产者往MQ投递消息一定要成功(生产者消息确认机制confirm),失败重试;
  2. 确保消费者能够消费成功(手动的ack机制)如果消费失败的情况下,MQ自动帮消费者重试,如果多次重试失败,表记录人工补偿;
  3. 确保生产者第一事务先执行成功,如果执行失败采用补单队列;(补单队列和派单队列订阅同一个路由键,补单队列查询订单创建是否成功,如果没有创建成功,补偿创建;订阅的消息内容包含下单的完整信息和派单信息。)

4 代码实现基于RabbitMQ解决分布式事务1

订单系统mayikt_order_producer
maven依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.1.1</version>
    </dependency>
    <!-- mysql 依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- 阿里巴巴数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.14</version>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 添加springboot对amqp的支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    <!--fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.49</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

数据库表

CREATE TABLE `order_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `orderCreatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `orderMoney` decimal(10,0) DEFAULT NULL,
  `orderState` int(60) DEFAULT NULL,
  `commodityId` int(60) DEFAULT NULL,
  `orderId` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

application.yml

spring:
  rabbitmq:
    ####连接地址
    host: 127.0.0.1
    ####端口号
    port: 5672
    ####账号
    username: guest
    ####密码
    password: guest
    ### 地址
    virtual-host: /mayikt_rabbitmq
    ###开启消息确认机制 confirms
    publisher-confirms: true
    publisher-returns: true
    listener:
      simple:
        retry:
          ####开启消费者(程序出现异常的情况下会)进行重试
          enabled: true
          ####最大重试次数
          max-attempts: 5
          ####重试间隔次数
          initial-interval: 3000
          ###开启ack模式
        acknowledge-mode: manual
  datasource:
    url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

server:
  port: 8080

核心代码

@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");
    }
}
@Data
public class OrderEntity {

    private Long id;
    // 订单名称
    private String name;
    // 订单时间
    private Date orderCreatetime;
    // 下单金额
    private Double orderMoney;
    // 订单状态
    private int orderState;
    // 商品id
    private Long commodityId;

    // 订单id
    private String orderId;

    public OrderEntity(Long id, String name, Date orderCreatetime, Double orderMoney, int orderState, Long commodityId, String orderId) {
        this.id = id;
        this.name = name;
        this.orderCreatetime = orderCreatetime;
        this.orderMoney = orderMoney;
        this.orderState = orderState;
        this.commodityId = commodityId;
        this.orderId = orderId;
    }

    public OrderEntity() {

    }
}
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);
}
@Component
@Slf4j
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class OrderProducer implements RabbitTemplate.ConfirmCallback {

    @Resource
    private OrderMapper orderMapper;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 基于MQ发送投递订单消息
     */
    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 jsonString = JSONObject.toJSONString(orderEntity);
        sendMsg(jsonString);
        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("每特教育第六期平均就业薪资破10万");
        orderEntity.setOrderCreatetime(new Date());
        // 价格是300元
        orderEntity.setOrderMoney(300d);
        // 状态为 未支付
        orderEntity.setOrderState(0);
        Long commodityId = 30L;
        // 商品id
        orderEntity.setCommodityId(commodityId);
        orderEntity.setOrderId(orderId);
        return orderEntity;
    }

    /**
     * @param correlationData 投递失败回调的消息
     * @param ack             true,投递成功,否则false
     * @param s
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String s) {
        String msg = correlationData.getId();
        if (!ack) {
            log.info("往mq中投递消息失败>>" + msg);
            // 采用递归算法重试 可以加上限制重试次数,使用threadLocal
            sendMsg(msg);
            return;
        }
        log.info("往mq中投递消息成功>>" + msg);
        // 生产者投递多次还是失败,记录到表中后期人工补偿
    }
}
@RestController
public class OrderController {

    @Autowired
    private OrderProducer orderProducer;

    @RequestMapping("/sendOrder")
    public String sendOrder() {
        return orderProducer.sendOrder();
    }
}
@SpringBootApplication
@MapperScan("com.mayikt.mapper")
@EnableAsync
public class AppProducer {

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

5 代码实现基于RabbitMQ解决分布式事务2

派单系统mayikt_dl_consumer
数据库表

CREATE TABLE `platoon` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `orderId` varchar(255) DEFAULT NULL,
  `userId` int(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

application.yml

spring:
  rabbitmq:
    ####连接地址
    host: 127.0.0.1
    ####端口号
    port: 5672
    ####账号
    username: guest
    ####密码
    password: guest
    ### 地址
    virtual-host: /mayikt_rabbitmq
    ###开启消息确认机制 confirms
    publisher-confirms: true
    publisher-returns: true
  datasource:
    url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

server:
  port: 8081

核心代码

@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;
    }
}
public interface DispatchMapper {

    /**
     * 新增派单任务
     */
    @Insert("INSERT into platoon values (null,#{orderId},#{userId});")
    int insertDistribute(DispatchEntity distributeEntity);
}
@Component
public class DispatchConsumer {
    @Resource
    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");
        // 幂等性 主动根据orderId查询是否派单过,如果已经派单情况下,直接返回
        // 计算分配的快递员id
        DispatchEntity dispatchEntity = new DispatchEntity(orderId, 123L);
        // 插入数据库
        int result = dispatchMapper.insertDistribute(dispatchEntity);
        if (result > 0) {
            // 手动ack 删除消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }
}
@SpringBootApplication
@MapperScan("com.mayikt.mapper")
public class AppConsumer {
    public static void main(String[] args) {
        SpringApplication.run(AppConsumer.class);
    }
}

运行结果:
在这里插入图片描述

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

补单消费者代码

@Component
public class CreateOrderConsumer {
    @Resource
    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);
        }
    }
}

测试结果:
在这里插入图片描述
注意:
补单的消费不应该和订单生产者放到一个服务器节点;
补单消费者如果不存在的情况下 队列缓存补单消息;
补偿分布式事务解决框架 思想最终一致性;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值