RocketMq +Spring boot 使用示例

目录

 

背景

准备工作

加依赖

添配置

生产者

消费者

事务消息

消息发送

事务消息 处理

总结


背景

上面几篇博文,从RocketMq 的概念,特性,架构等方面详细描述了RocketMq 的基础知识和架构原理;下面我们研究下工作中在什么样的场景应该使用RocketMq什么样的属性;

开发环境: jdk1.8 + RocketMq 4.8.0 + springboot 2.3.2.release

准备工作

加依赖

        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>

添配置

# rocketmq 配置
rocketmq:
  name-server: 127.0.0.1:9876
  producer:
    # 注意:必须制定group
    group: test-group

生产者

package com.springcloud.alibaba.controller;

import com.alibaba.fastjson.JSONObject;
import com.springcloud.alibaba.domain.message.MqMsgDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.messaging.support.MessageBuilder;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @Author corn
 * @Date 2021/2/27 20:31
 */
@SpringBootTest
@Slf4j
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class MqSendTest {

    private final RocketMQTemplate rocketMQTemplate;


    /**
     *@描述 消息同步发送,常用作比较重要的消息发送,比如邮件短信的发送 ;优点: 保证消息的同步传输 缺点: 并发比较高时,性能下降
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/2/28
     */
    @Test
    public void SyncSend() throws UnsupportedEncodingException {
        MqMsgDto ayncSendMsg = MqMsgDto.builder()
                .msgId(new Random().nextInt())
                .msgVal("同步发送")
                .build();

        // 入参: topic 和message 
        SendResult sendResult = rocketMQTemplate.syncSend("add-mq", ayncSendMsg);

        log.info(JSONObject.toJSONString(sendResult));

    }

    /**
     *@描述 消息异步发送,通常用作业务对时间比较敏感的操作,比如注册成功后,发送邮件通知
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/2/28
     */
    @Test
    public void asyncSend(){
        MqMsgDto asyncSend = MqMsgDto.builder()
                .msgVal("异步发送")
                .msgId(new Random().nextInt())
                .build();
        
        // 入参: topic ,message ,SendCallback 回调事件
        rocketMQTemplate.asyncSend("add-mq", asyncSend, new SendCallback() {

            // 消息发送成功后的回调事件
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("异步消息发送成功,返回result:{}",JSONObject.toJSONString(sendResult));
            }

            // 消息发送异常的消息回调事件,可以打印出异常的原因
            @Override
            public void onException(Throwable throwable) {
                log.info("异步消息发送失败,异常状态:{}",throwable.getMessage());
            }
        });

    }

    /**
     *@描述 单项发送,无需等待broker  的响应,通常用作对数据可靠性要求不高的场景。比如日志的收集等
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/2/28
     */
    @Test
    public void sendOneway(){
        MqMsgDto sendoneway = MqMsgDto.builder()
                .msgVal("单项发送")
                .msgId(new Random().nextInt())
                .build();

        // 入参: topic ,message
        rocketMQTemplate.sendOneWay("add-mq",sendoneway);

        log.info("消息单项发送");


    }

    /**
     *@描述 消息有序发送,通常用作对消息顺序有严格要求的场景,比如订单系统等。
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/2/28
     */
    @Test
    public void sendOrder(){
        List<MqMsgDto> orderbyObject = getOrderbyObject();
        for (MqMsgDto mqMsgDto : orderbyObject) {
            // 参数: topic 、message 、唯一id,通过改id 可以将相同id的消息分配到一个consumerQueue 中,从而来保证消息的有序性
            rocketMQTemplate.syncSendOrderly("add-order-mq",mqMsgDto,String.valueOf(mqMsgDto.getMsgId()));
        }
    }

    public List<MqMsgDto> getOrderbyObject(){
        List<MqMsgDto> list = new ArrayList<>();

        // 创建订单
        list.add(MqMsgDto.builder()
                .msgId(10001)
                .msgVal("创建订单")
                .build());
        list.add(MqMsgDto.builder()
                .msgId(10002)
                .msgVal("创建订单2")
        .build());

        list.add(MqMsgDto.builder()
        .msgId(10001)
        .msgVal("付款")
        .build());

        list.add(MqMsgDto.builder()
        .msgId(10002)
        .msgVal("付款2")
        .build());

        list.add(MqMsgDto.builder()
        .msgId(10001)
        .msgVal("完成")
        .build());

        list.add(MqMsgDto.builder()
                .msgId(10002)
                .msgVal("完成2")
                .build());
        return list;
    }


    /**
     *@描述 延时发送消息
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/2/28
     */
    @Test
    public void delayTimeSend(){
        MqMsgDto delayTime = MqMsgDto.builder().msgId(101)
                .msgVal("延时消息发送")
                .build();
        // 参数: topic 、message、消息发送等待时间、延时级别,延时级别分为18个级别,数字分别对应相关的延迟级别,具体的对应关系可以查找rocketMq 官网
        rocketMQTemplate.syncSend("add-mq", MessageBuilder.withPayload(delayTime).build(),2000,10);
        log.info("延时消息发送成功");
    }

    /**
     *@描述 消息筛选,RocketMq 消息筛选支持tag 消息筛选,和sql92 的方式
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/2/28
     */
    @Test
    public void searchTag(){
        // 通过RocketMQ 源码得知,设置tag的方法是 在topic:tag 的方式设置的,及topic 冒号后即为tag 
        SendResult tag = rocketMQTemplate.syncSend("add-mq:tag", MqMsgDto.builder().msgId(1).msgVal("标签为tag 的消息").build());

        SendResult tag2 = rocketMQTemplate.syncSend("add-mq:tag1", MqMsgDto.builder().msgId(2).msgVal("标签为tag1 的消息").build());

        // MessageBuilder.withPayload(T) 是将相关泛型转换成RocketMq 的MessageBuilder 消息体。并且可以在header 属性中设置相关的参数信息,用来做参数的传递或者是sql92 方式的筛选
        rocketMQTemplate.syncSend("add-mq",MessageBuilder.withPayload(MqMsgDto.builder().msgId(3).msgVal("设置属性num=2").build()).setHeader("num",2).build());

        rocketMQTemplate.syncSend("add-mq",MessageBuilder.withPayload(MqMsgDto.builder().msgId(4).msgVal("设置属性num=6").build()).setHeader("num",6).build());

        rocketMQTemplate.syncSend("add-mq",MessageBuilder.withPayload(MqMsgDto.builder().msgId(5).msgVal("设置属性name=test").build()).setHeader("name","test").build());

        rocketMQTemplate.syncSend("add-mq",MessageBuilder.withPayload(MqMsgDto.builder().msgId(5).msgVal("设置属性name=test11").build()).setHeader("name","test111").build());


    }

}

消费者

package com.springcloud.alibaba.consumer.rocketMq;

import com.alibaba.fastjson.JSONObject;
import com.springcloud.alibaba.consumer.domain.message.MqMsgDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;

/**
 * 描述消费者之前,首先详细了解下@RocketMqMessageListener 注解中的常用属性及含义
 * consumerGroup: 消费组名称,具有相同角色的使用者、具有完全相同的订阅和consumerGroup,才能正确实现负载平衡。它是必需的,并且必须是全局唯一的。
 * topic: 订阅所要消费消息的主题
 * selectorType: 设置筛选消息的方式,默认是tag ,支持设置sql92
 * selectorExpression: 设置筛选消息的规则,并且支持多种规则,规则如下:
 * 数值比较,比如:>,>=,<,<=,BETWEEN,=;
 * 字符比较,比如:=,<>,IN;
 * IS NULL 或者 IS NOT NULL;
 * 逻辑符号 AND,OR,NOT;
 * 常量支持类型为:
 * 数值,比如:123,3.1415;
 * 字符,比如:'abc',必须用单引号包裹起来;
 * NULL,特殊的常量
 * 布尔值,TRUE 或 FALSE
 *consumeMode: 设置消费消息的模式,默认为同时消费,也可以设置成顺序消费
 * messageModel: 设置消息消费的模式,默认是集群模式,可以设置成广播模式
 * consumeThreadMax: 设置消费者最大消费线程数,默认64
 * consumeTimeout: 设置消费者最大的消费时间,默认是30s
 * 
 *
 * @Author corn
 * @Date 2021/2/24 22:04
 */
@Service
@Slf4j
@RocketMQMessageListener(consumerGroup = "consumerGroup",topic = "transaction-topic")
public class RocketMqTestConsumer implements RocketMQListener<MqMsgDto> {
    @Override
    public void onMessage(MqMsgDto mqMsgDto) {
        // 接收到mq 消息时执行的方法
        log.info("mq消费消息内容为:{}",JSONObject.toJSONString(mqMsgDto));
    }
}


/**
 * 根据tag 做消息筛选
 * @Author corn
 * @Date 2021/2/24 22:04
 */
@Service
@Slf4j
@RocketMQMessageListener(consumerGroup = "consumerGroup6",topic = "add-mq",selectorExpression = "tag1")
public class RocketMqTag2TestConsumer implements RocketMQListener<MqMsgDto> {
    @Override
    public void onMessage(MqMsgDto mqMsgDto) {
        // 接收到mq 消息时执行的方法
        log.info("mq消费消息内容为:{}",JSONObject.toJSONString(mqMsgDto));
    }
}


/**
 * 顺序消费
 * @Author corn
 * @Date 2021/2/24 22:04
 */
@Service
@Slf4j
@RocketMQMessageListener(consumerGroup = "consumerOrderGroup",topic = "add-order-mq",consumeMode = ConsumeMode.ORDERLY)
public class RocketMqOrderlyTestConsumer implements RocketMQListener<MqMsgDto> {
    @Override
    public void onMessage(MqMsgDto mqMsgDto) {
        // 接收到mq 消息时执行的方法
        log.info("mq消费消息内容为:{}",JSONObject.toJSONString(mqMsgDto));
    }
}

/**
 * 根据属性num 进行筛选,只有selectType = Sql92 时
 * @Author corn
 * @Date 2021/2/24 22:04
 */
@Service
@Slf4j
@RocketMQMessageListener(consumerGroup = "consumerGroup9",topic = "add-mq",selectorType = SelectorType.SQL92,selectorExpression = "num=2",messageModel = MessageModel.BROADCASTING)
public class RocketMqNum1TestConsumer implements RocketMQListener<MqMsgDto> {
    @Override
    public void onMessage(MqMsgDto mqMsgDto) {
        // 接收到mq 消息时执行的方法
        log.info("mq消费消息内容为:{}",JSONObject.toJSONString(mqMsgDto));
    }
}

事务消息

事务消息的原理在前面的设计中,已经详细阐述了RocketMq 事务消息的工作机制。事务消息的目的主要是保证本地事务和Mq消息发送的一致性,所以编码主要体现在生产者端;详细编码如下:

消息发送

// 发送mq
        this.rocketMQTemplate.sendMessageInTransaction(
                /**
                 *用于将回调事件与侦听器相关联的txProducerGroup,rocketMQTemplate必须发送带有声明的txProducerGroup的事务性消息;
                 * 需要注意事务回调txProduceGroup 要和此处一致
                  */
                "tx-add-bouns-group",
                // topic
                "transaction-topic",
                // 消息内容,setHeader 主要传递参数,供事务回调使用
                MessageBuilder.withPayload(msg)
                        .setHeader(RocketMQHeaders.TRANSACTION_ID,transactionId)
                        .setHeader("share_id",3)
                        .build(),
                // args 参数
                user

        );

事务消息 处理

package com.springcloud.alibaba.rocketmq;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.springcloud.alibaba.mapper.RocketmqTranscationLogMapper;
import com.springcloud.alibaba.model.CanalUser;
import com.springcloud.alibaba.model.RocketmqTranscationLog;
import com.springcloud.alibaba.service.CanalUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * 事务消息回调事件
 * @Author corn
 * @Date 2021/2/28 21:58
 */
@RocketMQTransactionListener(txProducerGroup = "tx-add-bouns-group")
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
@Slf4j
public class TransactionListener implements RocketMQLocalTransactionListener {

    private final CanalUserService canalUserService;

    private final RocketmqTranscationLogMapper transcationLogMapper;

    /**
     *@描述 执行本地事务方法
     *@参数 
     *@返回值 
     *@创建人  corn
     *@创建时间  2021/3/16
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        MessageHeaders headers = message.getHeaders();
        String transactionId = (String)headers.get(RocketMQHeaders.TRANSACTION_ID);
        try {
            // 调用本地方法事务逻辑,如果执行成功,则提交事务,反之回滚事务
            this.canalUserService.saveTransactionLog((CanalUser) o,transactionId);
            Thread.sleep(500000);
            return RocketMQLocalTransactionState.COMMIT;
        }catch (Exception e){
            log.error("异常:{}",e.getMessage());
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    /**
     *@描述  事务回调方法,发生在生产者没有告诉broker事务消息的执行状态,broker会调用回查方法查询本地事务执行状态;
     *       需要注意的是,broker 并非无休止的轮查,默认回查15次,如果还未查到消息的状态,那么就会回滚该条事务消息;
     *       注意: RocketMq 3.1.2之前的有事务消息回查功能的版本。消息回查功能基于文件系统,回查后得到的结果以及正常的处理结果Commit/Rollback都会修改CommitLog里PREPARED消息的状态
     *       ,这会导致内存中脏页过多,有隐患。在之后的版本移除了基于文件系统的状态修改机制,对事务消息的处理流程进行重做,移除了消息回查功能。
     *@参数 
     *@返回值 
     *@创建人  corn
     *@创建时间  2021/3/16
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        MessageHeaders headers = message.getHeaders();
        String transactionId =(String) headers.get(RocketMQHeaders.TRANSACTION_ID);
        QueryWrapper<RocketmqTranscationLog> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("transaction_id",transactionId);
        List<RocketmqTranscationLog> rocketmqTranscationLogs = transcationLogMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(rocketmqTranscationLogs)){
            return RocketMQLocalTransactionState.ROLLBACK;
        }else {
            return RocketMQLocalTransactionState.COMMIT;
        }
    }
}

总结

上述中的几种RocketMq 发送消息和消费消息的简单实现demo ,够满足日常工作中简单的一些场景。对于消息发送的流程、消息如何落盘的、以及消息是如何消费的详细的设计,还是需要阅读源码。mark 一下,可以参考芋道源码中的RocketMq 源码进行阅读,了解mq 的底层实现原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值