RocketMQ在SpringBoot中的使用

RocketMQ 在SpringBoot中的使用

一、前言

在Springboot中使用RocketMQ基本有两种方式, 第一种是基于RocketMQ原生的API,第二种是采用Springboot对RocketMQ封装的写法,接下来分别介绍这两种方式的基本用法,各自的优缺点各位看官仁者见仁,智者见智。

二、RocketMQ原生API的使用方式

2.1生产者的使用

pom 依赖如下:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.5.1</version>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-common</artifactId>
    <version>4.5.1</version>
</dependency>

配置文件的设置:

# NameServer地址
apache.rocketmq.namesrvAddr=192.168.56.129:9876
# 生产者的组名
apache.rocketmq.producer.producerGroup=test_Producer
发送非事务消息

生产者初始化:

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class MsgProducer {

    @Value("${apache.rocketmq.producer.producerGroup}")
    private String producerGroup;

    @Value("${apache.rocketmq.namesrvAddr}")
    private String namesrvAddr;

    private DefaultMQProducer mqProducer;

	//调用方注入MsgProducer后 通过这个方法获取配置好的DefaultMQProducer
    public DefaultMQProducer getMqProducer(){
        return mqProducer;
    }

    @PostConstruct
    public void initMQ(){
        mqProducer=new DefaultMQProducer(producerGroup);
        mqProducer.setNamesrvAddr(namesrvAddr);
        mqProducer.setVipChannelEnabled(false);
        try {
            mqProducer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }

    @PreDestroy
    public void destory(){
        mqProducer.shutdown();
    }
}

生产者发送消息

发送同步、非顺序的消息

 	@Autowired
    private MsgProducer msgProducer;

    public void sendMsg() throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
        //消息体
        String msg="hello";
        //包装成消息
        Message message=new Message("test_topic_2","test",msg.getBytes());
        //调用配置好的DefaultMQProducer发送消息
        SendResult result=msgProducer.getMqProducer().send(message);
    }

发送同步、顺序的消息:

	@Autowired
    private MsgProducer msgProducer;

    public void sendMsg() throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
    	//发100条消息测试
        for(int i=1;i<=100;i++){
        	/**
        	 * 消息 
        	 * 因为默认自动创建的topic有四个队列 所以type表示队列id、value表示值
        	 * 如果按照队列读取 那么同一个type下的value一定按照接收的顺序从小到大
        	 */
            String msg="type:"+i%4+" value:"+i;
            Message message=new Message("test_topic_2","test",msg.getBytes());
            
            SendResult result=msgProducer.getMqProducer().send(message, new MessageQueueSelector() {
                        @Override
                        //args一般是唯一id 用send的第三个参数传入(这里指的i)
                        public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                        
                            int queueNum = Integer.valueOf(String.valueOf(arg)) % 4;
                            System.out.println("队列id:"+queueNum+" 消息:"+new String(msg.getBody()));
                            
                            return mqs.get(queueNum);
                        }
                    },i);
        }
    }

异步、非顺序

@Test
public void testAsyncProducer() throws Exception {
    // Instantiate with a producer group name.
    DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup");
    // Launch the instance.
    producer.start();
    producer.setRetryTimesWhenSendAsyncFailed(0);
    for (int i = 0; i < 100; i++) {
        final int index = i;
        // Create a message instance, specifying topic, tag and message body.
        Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
        producer.send(msg, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId());
            }

            @Override
            public void onException(Throwable e) {
                System.out.printf("%-10d Exception %s %n", index, e);
                e.printStackTrace();
            }
        });
    }
}
发送事务消息

(即消息先不发到broker的目的队列,而是包装一层放到中间队列,待提交之后再放到目的队列。)

第一步, 配置类:

@Component
public class TxMsgProducer {

    @Value("${apache.rocketmq.producer.producerGroup}")
    private String producerGroup;

    @Value("${apache.rocketmq.namesrvAddr}")
    private String namesrvAddr;

    private TransactionMQProducer mqProducer;

    public DefaultMQProducer getMqProducer(){
        return mqProducer;
    }

    @PostConstruct
    public void initMQ(){
        mqProducer=new TransactionMQProducer(producerGroup);
        mqProducer.setNamesrvAddr(namesrvAddr);
        mqProducer.setVipChannelEnabled(false);
        mqProducer.setNamesrvAddr(namesrvAddr);
        //下面两个时新增的
        mqProducer.setExecutorService(getExecutorService());
        mqProducer.setTransactionListener(new TransactionListenerImpl());

        try {
            mqProducer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }

    @PreDestroy//在程序运行结束时执行
    public void destory(){
        mqProducer.shutdown();
    }


    /**
     * 事务监听
     */
    class TransactionListenerImpl implements TransactionListener {
        /**
         * 第一次判断是否提交或回滚
         *
         * @param message
         * @param arg
         * @return
         */
        @Override
        public LocalTransactionState executeLocalTransaction(Message message, Object arg){

            //message就是那个半发送的消息 arg是在transcationProducter.send(Message,Object)时的另外一个携带参数)

            //执行本地事务或调用其他为服务

            if(true) return LocalTransactionState.COMMIT_MESSAGE;

            if(true) return LocalTransactionState.ROLLBACK_MESSAGE;

            //如果在检查事务时数据库出现宕机可以让broker过一段时间回查 和return null 效果相同
            return LocalTransactionState.UNKNOW;
        }

        /**
         * 返回UNKOWN时回查!
         * @param messageExt
         * @return
         */
        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
            //只去返回commit或者rollback
            return LocalTransactionState.COMMIT_MESSAGE;
        }


    }

    /**
     * 定义一个线程池 让broker用来执行回调和回查
     * 
     * @return
     */
    public ExecutorService getExecutorService(){
        return new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000));
    }
    
}

第二步,发送消息

@Autowired
    private TxMsgProducer msgProducer;

    public void sendMsg() throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
        String msg="hello";
        Message message=new Message("test_topic_2","test",msg.getBytes());
        //调用配置好的TxMsgProducer 发送消息
        SendResult result=msgProducer.getMqProducer().send(message);
    }

2.2 消费者的使用

pom依赖

	<dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client</artifactId>
        <version>4.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-common</artifactId>
        <version>4.5.1</version>
    </dependency>

配置文件的设置:

# NameServer地址
apache.rocketmq.namesrvAddr=192.168.56.129:9876
# 消费者的组名
apache.rocketmq.consumer.PushConsumer=test_Consumer

消费消息:

@Component
public class MsgConsumer {

    @Value("${apache.rocketmq.consumer.PushConsumer}")
    private String consumerGroup;

    @Value("${apache.rocketmq.namesrvAddr}")
    private String namesrvAddr;

    private DefaultMQPushConsumer consumer;

    @PostConstruct
    public void init() throws MQClientException {
        consumer=new DefaultMQPushConsumer(consumerGroup);
        consumer.setNamesrvAddr(namesrvAddr);

        //设置consumer所订阅的Topic和Tag,*代表全部的Tag
        consumer.subscribe("test_topic_2", "*");

        /**
         * CONSUME_FROM_LAST_OFFSET 默认策略,从该队列最尾开始消费,跳过历史消息
         * CONSUME_FROM_FIRST_OFFSET 从队列最开始开始消费,即历史消息(还储存在broker的)全部消费一遍
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
                try{
                    System.out.println("接受:"+new String(list.get(0).getBody()));
                }catch (Exception e){
                     //ACK机制,消费失败,触发RocketMQ 重发消息
                    return ConsumeConcurrentlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                }
                //ACK机制,消费成功
                return ConsumeConcurrentlyStatus.SUCCESS;
            }
        });
        consumer.start();
    }


    @PreDestroy
    public void destory(){
        consumer.shutdown();
    }
}

其他消费模式:

顺序消费
只用把

consumer.registerMessageListener(new MessageListenerConcurrently(){});

改成

consumer.registerMessageListener(new MessageListenerOrderly(){});

事务消费
不用改变消费者 如果事务的监听rollback了 消费者的消费结果会自动回滚

广播消费

consumer.setMessageMode(MessageMode.BROADCASTING);
consumer.setOffsetStore(OffsetStore.LocalFileOffsetStore);

三、RocketMQ在Springboot中封装的使用方式

3.1生产者的使用

pom依赖:

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

配置文件设置:

rocketmq:
  name-server: localhost:9876
  producer:
    group: my-group
server:
  port: 8081

发送消息:

这里还是挺简单的,直接实现CommandLineRunner这个接口,复写run方法即可,然后注册RocketMQTemplate,就可以生产消息了。

@SpringBootApplication
public class SpringBootRocketmqProducerApplication implements CommandLineRunner {
    //引入依赖模板
    @Resource
    private RocketMQTemplate rocketMQTemplate;
   
    //main函数,这里其实不需要 ....
    public static void main(String[] args) {
        SpringApplication.run(SpringBootRocketmqProducerApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        //发送消息
        rocketMQTemplate.convertAndSend("test-topic-1", "Hello, World!");
        rocketMQTemplate.convertAndSend("test-topic-2",
                new OrderPaidEvent("orderId-0001", 88));
    }
}

@Data
@AllArgsConstructor
class OrderPaidEvent implements Serializable {
    private String orderId;
    private Integer paidMoney;
}

3.2消费者的使用

pom 依赖:

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

配置文件设置:

rocketmq:
  name-server: localhost:9876
server:
  port: 8082

消费消息:

@SpringBootApplication
public class SpringBootRocketmqConsumerApplication {

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

}

@Slf4j
@Service
@RocketMQMessageListener(topic = "test-topic-1", consumerGroup = "my-consumer_test-topic-1")
class MyConsumer1 implements RocketMQListener<String> {

    /**
     *需要注意的是,onMessage()封装了ACK机制,消费者往外抛异常时,RocketMQ认为消费失败,重新发送该条消息,否则默认消费成功
    */
    @SneakyThrows
    @Override
    public void onMessage(Message message) {
        log.info("receivie message:topic={},body={}", message.getTopic(), new String(message.getBody()));
        if(消费成功){
            
        }else if(消费失败){
            throw new Exception;
        }
    }
}

@Data
@AllArgsConstructor
class OrderPaidEvent implements Serializable {

    private String orderId;

    private Integer paidMoney;
}

**注意:**需要注意的是,onMessage()封装了ACK机制,消费者往外抛异常时,RocketMQ认为消费失败,重新发送该条消息,否则默认消费成功。

常见问题:RocketMQ 消息 Body()乱码 怎么处理?

Body默认是byte[]类型, 需要转换为String类型然后进行反序列化

String res = "字符串" ; 
//String转换byte[]
byte [] body = res.getBytes();
//byte[]转换String
String newString = new String(body);

//或者使用Base64转换
import org.apache.commons.codec.binary.Base64;
public class UtilHelper {	
	//base64字符串转byte[]
	public static byte[] base64String2ByteFun(String base64Str){
		return Base64.decodeBase64(base64Str);
	}
	//byte[]转base64
	public static String byte2Base64StringFun(byte[] b){
		return Base64.encodeBase64String(b);
	}
}
————————————————
版权声明:本文为CSDN博主「九亿moc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/big1989wmf/article/details/70144803

参考博客:

RocketMQ入门原理:

https://juejin.cn/post/6844904018322391054#heading-21

原生API的用法:

https://blog.csdn.net/Muscleheng/article/details/117526165

https://blog.csdn.net/weixin_43934607/article/details/102756379

Springboot封装RocketMQ的用法:

https://blog.csdn.net/qq_34205356/article/details/86596241

你可能感兴趣的:(Spring,中间件,RocketMQ,Springboot)