springboot + rabbitmq 实现分布式事务(可靠性生产者)

分布式事务(可靠性生产者)

实现思路:采取消息冗余+定时器

实际上就是保证消息的成功投递

例子:订单(生产者)+派送(消费者) springboot+rabbitmq在Windows环境下实现

pom:

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

配置文件yml:

server:
  port: 8012
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/rabbit_test_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.jdbc.Driver
  rabbitmq:
#    username: guest
#    password: guest
#    host: 127.0.0.1
#    virtual-host: /
#    port: 5567
    publisher-confirm-type: correlated

由于是本地搭建的环境,只需要配置一下手动确认就可以了,账号密码用的也是默认的,如果修改了,可以配置一下。

一般有订单,肯定会有一个订单表,在此基础上,加上一个订单的消息表,来存储订单的消息投递情况,默认值为0,表示消息投递失败(定时器主要跑的就是这类订单);如果消息投递成功,修改订单状态为1;如果异常处理也失败了,修改单子的状态为2,这个可以人为排查。

order表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yRGSn2rJ-1624962176147)(C:\Users\ning\AppData\Roaming\Typora\typora-user-images\image-20210629175431375.png)]

order_message消息状态表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hQb2gulN-1624962176153)(C:\Users\ning\AppData\Roaming\Typora\typora-user-images\image-20210629175528469.png)]

也就是消息冗余的意思;两个表用order_id关联;order_id作为主键,避免出现幂等性问题;创建两个的model,不创建也可以,本例采用的jdbc;

构建交换机和队列

@Configuration
public class OrderConfiguration {

    @Bean
    public FanoutExchange orderExchange(){
        return new FanoutExchange("order_fanout_exchange",true,false);
    }

    @Bean
    public Queue orderQueue(){
        Map<String,Object> args = new HashMap<>();//绑定死信队列的交换机
        args.put("x-message-ttl",60000);//key是一定的,设置队列中消息的过期时间
        args.put("x-dead-letter-exchange","order_dead_exchange");//死信队列
        return new Queue("order_queue",true,false,false,args);
    }

    @Bean
    public Queue orderDeadQueue(){
        return new Queue("order_dead_queue",true,false,false);
    }

    @Bean
    public FanoutExchange orderDeadExchange(){
        return new FanoutExchange("order_dead_exchange",true,false);
    }

    @Bean
    public Binding orderBinding(){
        return BindingBuilder.bind(orderQueue()).to(orderExchange());
    }

    @Bean
    public Binding orderDeadBinding(){
        return BindingBuilder.bind(orderDeadQueue()).to(orderDeadExchange());
    }
}

生产者产出消息

/**
     * 开启事务保证订单操作一致性
     * 采用分布式事务确保各个服务之间的数据一致性
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public void makeOrder() throws Exception{
        String addOrderSql = "insert into `order`(order_id,order_name,description,user_id,create_time) values(?,?,?,?,?)";
        Order order = new Order();
        String orderId = UUID.randomUUID().toString();
        order.setOrderId(orderId);
        order.setOrderName("小浣熊");
        order.setDescription("太给力了叭");
        order.setUserId("123456789");
        order.setCreateTime(new Date());
        //记录订单信息
        int result = jdbcTemplate.update(addOrderSql,
                order.getOrderId(),
                order.getOrderName(),
                order.getDescription(),
                order.getUserId(),
                order.getCreateTime());
        if(result != 1){
            throw new Exception("订单创建失败,失败原因:数据库操作失败");
        }
        //记录订单的消息状态,初始为0,消息投递成功在回调方法中修改为1,若果异常处理中没有处理成功,状态修改为2
        String orderMessSql = "insert into order_message(order_id,status,create_time) values(?,?,?)";
        jdbcTemplate.update(orderMessSql,orderId,0,new Date());
        //发送消息
        rabbitTemplate.convertAndSend("order_fanout_exchange","", JSONObject.toJSONString(order),new CorrelationData(orderId));
    }

使用java的注解@PostConstruct实现回调

/**
     * 设置消息发送确认回调,修改发送成功消息的状态,确保消息投递成功
     */
    @PostConstruct
    public void regCallBack(){
        //@param correlationData 相关配置信息
        //@param ack 布尔类型,消息是否投递到交换机,是true否false
        //@param cause 字符串String,投递失败的原因
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            System.out.println(correlationData);
            System.out.println(ack);
            System.out.println(cause);
            String orderId = correlationData.getId();
            //如果ack为true,表名消息 已经收到
            if(!ack){
                System.out.println("队列应答失败");
                return;
            }
            try {
                //消息投递成功,修改消息状态
                String updateMessSql = "update order_message set `status` = ? ,modify_time = ? where order_id = ?";
                jdbcTemplate.update(updateMessSql,1,new Date(),orderId);
            }catch (Exception e){
                System.out.println("本地消息状态修改失败,出现异常:"+e.getMessage());
            }
        });
    }

该注解的方法在整个Bean初始化中的执行顺序:

Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

包不要搞错了!!!详解查看csdn,直接搜索。

import javax.annotation.PostConstruct;

注入

	@Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    JdbcTemplate jdbcTemplate;

测试类启动

	@Test
    void contextLoads() throws Exception {
        orderServiceImpl.makeOrder();
    }

消息投递成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6DAnpLpa-1624962176166)(C:\Users\ning\AppData\Roaming\Typora\typora-user-images\image-20210629181755685.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bn3M3wDo-1624962176173)(C:\Users\ning\AppData\Roaming\Typora\typora-user-images\image-20210629181934341.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3PyR26R-1624962176176)(C:\Users\ning\AppData\Roaming\Typora\typora-user-images\image-20210629181948252.png)]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值