【SpringBoot框架篇】22.集成RocketMQ实现消息发布和订阅

文章目录

  • 1.相关文章
  • 2..使用的依赖
  • 3.配置文件
  • 4.发送正常的消息
    • 4.1.消费者(订阅者)
    • 4.2.发布者(生产者)
    • 4.3.测试
  • 5.发布事务消息结合@Transactional一起使用
    • 5.1.消费者(订阅者)
    • 5.2.生产者 (发布者)
    • 5.3.测试
  • 6.项目配套代码

1.相关文章

RocketMQ简介请看这篇文章: RocketMQ入门笔记
RocketMQ安装请看这篇文章: 使用docker安装RocketMQ

2…使用的依赖

        
            org.apache.rocketmq
            rocketmq-spring-boot-starter
            2.1.0
        

        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            mysql
            mysql-connector-java
            8.0.11
        

3.配置文件

rocketmq:
  #namesrv访问地址
  name-server: 127.0.0.1:9876
  producer:
    #消息生产者组名
    group: test-group
    #发送消息的超时时间
    send-message-timeout: 3000
    #异步消息重试的次数
    retry-times-when-send-async-failed: 2
    #重试是否换一个server
    retry-next-server: true
    #同步消息重试的次数
    retry-times-when-send-failed: 2
    #消费消息认证用的ak
    access-key: Ak
    #消费消息认证用的sk
    secret-key: SK
    
topic:
  string: stringTopic
  order: orderTopic
  
spring:
  application:
    name: springboot-rocketMq
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=GMT%2b8&characterEncoding=utf8&connectTimeout=10000&socketTimeout=3000&autoReconnect=true
    username: root
    password: 123456
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      #自动创建或修改表结构
      ddl-auto: update
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl    

4.发送正常的消息

4.1.消费者(订阅者)

注意事项

  • 消息发布者设置topic 要和消费者的RocketMQMessageListener注解里的topic 一致。
  • RocketMQListener泛型类型要和生产者发送的消息的类型一致,如果生产者发布的类型是User,此处的String要改成User.
/**
 * @author Dominick Li
 * @CreateTime 2020/3/22 21:06
 * @description 如果consumerGroup的名称不同, 则会产生重复消费的情况,例如 consumer1和consumer2
 * 如果一个top下面有多个consumerGroup组,则消费者 消费消息会根据组的长度取模
 **/
@Service
@RocketMQMessageListener(topic = "${topic.string}", consumerGroup = "${spring.application.name}-${topic.string}-consumer1")
public class StringConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String msg) {
        System.out.println("StringConsumer1开始消费:"+ msg);
//        if(true){
//            //模拟出现异常,出现异常后这个任务还会被重试消费
//            System.out.println(1/0);
//        }
    }
}

4.2.发布者(生产者)

发送消息的三种方式

方法 描述 请求类型
void send(topic,msg) 底层调用的syncSend方法,但是不接受返回结果 异步
SendResult syncSend(topic,msg) 发送消息,等待发送消息返回的结果 同步
void asyncSend(topic,msg,callback) 发送消息,通过回调函数根据发送状态处理相对于逻辑 异步
参数 描述
topic 消息队列
msg 消息类型
callback 回调函数
SendResult 发送消息是否成功,SendResult.OK为发送成功,其它为失败。
    @Resource
    private RocketMQTemplate rocketMQTemplate;
    @Value("${topic.string}")
    private String stringTopic;
    @Test
    void contextLoads() {
        testBaseMethod();
    }
    public void testBaseMethod() {
        //1.发送异步消息,但是不会确认消息有没有被接收,日志可以这么发,如果是对数据一致性要去比较高的,建议使用下面的方法
        rocketMQTemplate.send(stringTopic, MessageBuilder.withPayload(String.format("test send method")).build());
        //2. 发送同步消息,等待发送消息返回的结果
        SendResult sendResult = rocketMQTemplate.syncSend(stringTopic, "test syncSend method");
        if (sendResult.getSendStatus() == SendStatus.SEND_OK) {
            System.out.println("发送成功...");
        }
        //3.发送异步消息,回调里处理发送成功或失败逻辑
        for (int j = 1; j < 3; j++) {
            rocketMQTemplate.asyncSend(stringTopic, "test asyncSend method", new SendCallback() {
                @Override
                public void onSuccess(SendResult sr) {
                    if (sr.getSendStatus() == SendStatus.SEND_OK) {
                        System.out.print("async onSucess  ok");
                    } else {
                        System.out.print("async onSucess  fail");
                    }
                }
                @Override
                public void onException(Throwable var1) {
                    System.out.printf("async onException Throwable=%s %n", var1);
                }
            });
            System.out.printf("发送第%d条消息\n", j);
        }
    }

4.3.测试

执行发布消息的函数,然后控制台输出如下
【SpringBoot框架篇】22.集成RocketMQ实现消息发布和订阅_第1张图片

5.发布事务消息结合@Transactional一起使用

rocketmq事务消息是发生在Producer和Broker之间,是二阶段提交。
【SpringBoot框架篇】22.集成RocketMQ实现消息发布和订阅_第2张图片
第一阶段是:步骤1,2,3。
第二阶段是:步骤4,5。

5.1.消费者(订阅者)

和上面定义的消费者的除了泛型和消费队列不一样,其它基本是一致的。

@Service
@RocketMQMessageListener(topic = "${topic.order}", consumerGroup = "${spring.application.name}-${topic.order}-consumer1")
public class OrderConsumer implements RocketMQListener<Orders> {
    @Override
    public void onMessage(Orders orders) {
        System.out.println("OrderConsumer1开始消费" + orders.getName() + "," + orders.getOrderId());
    }
}

5.2.生产者 (发布者)

执行逻辑描述

  • 1.发送事务消息后TransactionListener 会监听到事务消息,并执行MQ本地事务executeLocalTransaction方法
  • 2.在executeLocalTransaction方法中执行添加数据到数据库的操作,被执行的方法需要添加**@Transactional注解**让数据库事务可以回滚。
  • 3.在checkLocalTransaction方法中会检查本地事务是否提交成功,根据消息生产者传递的事务Id(订单Id)去查询数据库里面是否存在这条记录,如果记录存在提交MQ事务,如果不存在则表示数据库事务没有正常提交,MQ事务也进行回滚。

发送端代码

    /**
     * 发送事务消息
     */
    public void testTransactionMethod() {
        Long orderId = System.currentTimeMillis();
        Orders order = new Orders();
        order.setOrderId(orderId);
        order.setName("酒水订单");
        order.setCreateDate(new Date());
        rocketMQTemplate.sendMessageInTransaction(orderTopic,
                MessageBuilder.withPayload(
                        order)
                        .setHeader(RocketMQHeaders.TRANSACTION_ID, orderId)
                        .build()
                , order);
    }

执行MQ事务代码

@RocketMQTransactionListener
public class TransactionListener implements RocketMQLocalTransactionListener {

    @Autowired
    OrdersService ordersService;

    /**
     * 执行本地事务
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
        System.out.printf("#### executeLocalTransaction  transactionId=%s %n",
                transId);
        //执行本地事务,并记录日志
        ordersService.save((Orders) arg);
        //执行成功,可以提交事务
        return RocketMQLocalTransactionState.COMMIT;

    }

    /**
     * 检查本地事务是否成功
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
        System.out.printf("#### checkLocalTransaction transactionId=%s %n", transId);
        Optional<Orders> ordersOptional = ordersService.findById(Long.parseLong(transId));
        if (ordersOptional.isPresent()) {
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

}

数据库操作相关代码

@Component
public class OrdersService {

    @Autowired
    OrdersRepository ordersRepository;

    @Transactional
    public Orders save(Orders orders) {
        Orders save = ordersRepository.save(orders);
        if (false) {
            //测试抛出RuntimeException让mysql事务回滚
            System.out.println(1 / 0);
        }
        return save;
    }
    
    public Optional<Orders> findById(Long id) {
        return ordersRepository.findById(id);
    }

}

@Entity
@Table(name = "orders")
public class Orders implements Serializable {
    @Id
    private Long orderId;
    private String name;
    private Date createDate; 
    //省略get,set方法
}
public interface OrdersRepository extends JpaRepository<Orders,Long> {
}

5.3.测试

正常测试
此操作不模拟异常,正常发送事务消息,可以看到插入数据库的事务提交和MQ的事务提交都同步提交了。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
回滚mysql事务测试
在执行数据库操作处把if(false)改成if(true),模拟数据库异常让事务回滚,然后发送事务消息。
【SpringBoot框架篇】22.集成RocketMQ实现消息发布和订阅_第3张图片
执行后的结果如下,可以看到控制台并没有打印消费消息的信息
在这里插入图片描述
查看数据库也可以发现没有orderId为1628439222274的记录。
【SpringBoot框架篇】22.集成RocketMQ实现消息发布和订阅_第4张图片

6.项目配套代码

gitee代码地址

创作不易,要是觉得我写的对你有点帮助的话,麻烦在gitee上帮我点下 Star

【SpringBoot框架篇】其它文章如下,后续会继续更新。

  • 1.搭建第一个springboot项目
  • 2.Thymeleaf模板引擎实战
  • 3.优化代码,让代码更简洁高效
  • 4.集成jta-atomikos实现分布式事务
  • 5.分布式锁的实现方式
  • 6.docker部署,并挂载配置文件到宿主机上面
  • 7.项目发布到生产环境
  • 8.搭建自己的spring-boot-starter
  • 9.dubbo入门实战
  • 10.API接口限流实战
  • 11.Spring Data Jpa实战
  • 12.使用druid的monitor工具查看sql执行性能
  • 13.使用springboot admin对springboot应用进行监控
  • 14.mybatis-plus实战
  • 15.使用shiro对web应用进行权限认证
  • 16.security整合jwt实现对前后端分离的项目进行权限认证
  • 17.使用swagger2生成RESTful风格的接口文档
  • 18.使用Netty加websocket实现在线聊天功能
  • 19.使用spring-session加redis来实现session共享
  • 20.自定义@Configuration配置类启用开关
  • 21.对springboot框架编译后的jar文件瘦身
  • 22.集成RocketMQ实现消息发布和订阅
  • 23.集成smart-doc插件零侵入自动生成RESTful格式API文档
  • 24.集成FastDFS实现文件的分布式存储
  • 25.集成Minio实现文件的私有化对象存储
  • 26.集成spring-boot-starter-validation对接口参数校验
  • 27.集成mail实现邮件推送带网页样式的消息
  • 28.使用JdbcTemplate操作数据库
  • 29.Jpa+vue实现单模型的低代码平台
  • 30.使用sharding-jdbc实现读写分离和分库分表
  • 31.基于分布式锁或xxx-job实现分布式任务调度
  • 32.基于注解+redis实现表单防重复提交
  • 33.优雅集成i18n实现国际化信息返回
  • 34.使用Spring Retry完成任务的重试

你可能感兴趣的:(springBoot,java,spring,boot)