参考官方文档,大部分功能的示例代码都可以从这个地址找到
https://github.com/apache/rocketmq/blob/master/docs/cn/RocketMQ_Example.md
就是官网稍稍复杂些,并且是脱离springboot的,对任务繁重的开发们可能不太友好,而我的示例注释比较多,可以直接复制就用
消费者部分代码
MyConsumerConfig.java
package com.lbx.ms.mq.bill2crm;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class MyConsumerConfig {
public final Logger log = LoggerFactory.getLogger(MyConsumerConfig.class);
//消费组名称
@Value("${yourCompany.rocketMq.groupName}")
private String consumerGroup = null;
//rockmq集群namesrver的地址
@Value("${yourCompany.rocketMq.namesrvAddr}")
private String namesrvAddr = null;
//主题
@Value("${yourCompany.rocketMq.topic}")
private String topic = null;
//tag,用于筛选主题下指定tag的信息
@Value("${yourCompany.rocketMq.tags}")
private String tags = null;
//最大线程数
@Value("${yourCompany.rocketMq.consumeThreadMax}")
private Integer consumeThreadMax = null;
//每次拉取消息最多拉取多少条
@Value("${yourCompany.rocketMq.consumeMessageBatchMaxSize}")
private Integer consumeMessageBatchMaxSize = null;
@Bean("MyConsumer")
public DefaultMQPushConsumer MyConsumer() throws Exception {
DefaultMQPushConsumer consumer = getDefaultMQPushConsumer();
consumer.start();
log.info("Consumer started..............");
return consumer;
}
/**
* MQ消费者-配置封装
*
* @return
* @throws Exception
*/
public DefaultMQPushConsumer getDefaultMQPushConsumer() throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup);
consumer.setNamesrvAddr(namesrvAddr);
consumer.subscribe(topic, StringUtils.isBlank(tags) ? null : tags);
consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize);
consumer.setConsumeThreadMax(consumeThreadMax);
consumer.registerMessageListener(getMessageListenerConcurrently());
//默认配置:CONSUME_FROM_LAST_OFFSET
//消费者客户从以前停止的地方开始。如果它是一个新启动的消费者客户端,根据消费者群体的年龄,有两个,
// 如果消费者组是最近创建的,因此订阅的最早消息尚未过期,这意味着消费者组代表最近启动的业务,则消费将从头开始;
//consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
//如果订阅的最早消息已过期,则消费将从最新消息开始,这意味着在启动时间戳之前生成的消息将被忽略
return consumer;
}
/**
* 消费者-消费逻辑
*
* @return
* @throws Exception
*/
//返回类型为MessageListenerConcurrently:用于并发消费
//返回类型为MessageListenerOrderly: 用于按顺序接收异步传递的消息。一个队列,一个线程
public MessageListenerConcurrently getMessageListenerConcurrently() {
return (List<MessageExt> msgs, ConsumeConcurrentlyContext context) -> {
for (MessageExt msg : msgs) {
String currentMsgBody = new String(msg.getBody());
currentMsgBody = currentMsgBody.replaceAll("\n", "").replaceAll("\t", "");
log.info("消费者:接收到mq消息:msg.getBody():{} ; msg.getMsgId():{}", currentMsgBody, msg.getMsgId());
}
//如果返回这个标识,接下来的一段时间会有消息重复发送过来,默认发16次,并且重发间隔会越来越长
//return ConsumeConcurrentlyStatus.RECONSUME_LATER;
//返回成功的标识
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
};
}
}
/**
* 重复消费场景:
* 1.消息队列服务器发信息时遇到网络问题,发送中断,于是重新发送
* 解决办法:(1)由于同一条消息一般只会出现在一个消费者上,为单线程,业务上判断是否已经消费过,消费过就无需再次消费
* (2)对key增加分布式锁,锁内判断是否重复消费
* (3)消费端改成幂等代码
*
* 2.消息队列服务器没有收到ack信息,或者收到消费失败的ack标识,于是重新发送
* (1)解决代码报错问题
*
*
* 消息丢失场景:
* 1.异步刷盘导致消息丢失
* 2.自动ack导致消息丢失
*
*
*
* 消息顺序消费的方法
* 方法1.将所有消息放入同一个分区
* 方法2.将消息分类,需要顺序消费的消息放入同一个分区,可实现有序。不需要顺序消费的消息放入不同分区,提升整体消息消费速度
*
*
*
*/
application.yml
spring:
application:
name: rocketmqDemo
profiles:
active: dev
yourCompany:
rocketMq:
namesrvAddr: 127.0.0.1:9876
groupName: testtopicConcurrencyGroup_dev
tags:
topic: testtopicConcurrency
consumeThreadMax: 20
consumeMessageBatchMaxSize: 1
server:
port: 8067
logging:
level:
com.lbx.ms.mq: info
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lbx.ms.realtime</groupId>
<artifactId>rocketmqDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.3.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
启动类
Application.java
package com.lbx.ms.mq.bill2crm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
生产者代码
MyProducerConfig.java
package com.lbx.ms.mq.bill2crm;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyProducerConfig {
public final Logger log = LoggerFactory.getLogger(MyProducerConfig.class);
//组名称
@Value("${yourCompany.rocketMq.groupName}")
private String groupName = null;
//rockmq集群namesrver的地址
@Value("${yourCompany.rocketMq.namesrvAddr}")
private String namesrvAddr = null;
@Bean("MyProducer")
public DefaultMQProducer MyProducer() throws MQClientException {
// 实例化消息生产者Producer
//生产者组名称在概念上聚合了完全相同角色的所有生产者实例,这在涉及事务消息时尤为重要
//涉及事务消息时,需要注意的是事务消息的生产组名称 ProducerGroupName不能随意设置。事务消息有回查机制,回查时Broker端如果发现原始生产者已经崩溃,则会联系同一生产者组的其他生产者实例回查本地事务执行情况以Commit或Rollback半事务消息。
//对于非事务性消息,只要生产者组名称在每个进程中是唯一的,就无关紧要,在4.x版本的rocketmq客户端中,相同的producerGroupname会引起报错
//Apache RocketMQ 服务端5.x版本开始,生产者是匿名的,无需管理生产者分组
DefaultMQProducer producer = new DefaultMQProducer("producer" + System.currentTimeMillis() / 1000);
// 设置NameServer的地址
producer.setNamesrvAddr(namesrvAddr);
// 启动Producer实例
producer.start();
log.info("producer start....");
return producer;
}
}
测试类代码
DemoController.java
package com.lbx.ms.mq.bill2crm;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 发送消息测试类
*/
@RestController
public class DemoController {
public final Logger log = LoggerFactory.getLogger(DemoController.class);
@Autowired
DefaultMQProducer myProducer;
//消费组名称
@Value("${yourCompany.rocketMq.topic}")
private String topic = null;
/**
* 发送消息
*/
@GetMapping("/send")
public SendResult send() throws Exception {
Message msg = new Message(topic, "你好,RocketMQ ".getBytes(RemotingHelper.DEFAULT_CHARSET));
log.info("即将发送消息{}", msg);
SendResult sendResult = myProducer.send(msg);
log.info("{}", sendResult);
return sendResult;
}
}
验证下代码能否正常运行
启动rocketmq服务器,启动该springboot项目
在浏览器发送请求
得到如下结果
控制台打印如下