上一篇文章介绍了RocketMQ双Master+双Slave集群+可视化控制台环境搭建,这篇主要介绍一下Producer和Consumer的简单开发示例
Producer
创建个SpringBoot项目,配置下maven依赖,用的是4.7.0的RocketMQ,所以Manve也用4.7.0(版本要对应上)
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-common</artifactId>
<version>4.7.0</version>
</dependency>
在application.yml配置文件下配置RocketMQ nameServer地址和Topic
apache:
rocketmq:
namesrvAddr: 192.168.135.73:9876
producer:
producerGroup: wxProducer
topic: logTopic
之后我们来编写个Producer的启动类,在项目启动时自动执行Producer的start初始化方法
@Component
public class RocketMQProducer {
private static Logger logger = LoggerFactory.getLogger(RocketMQProducer.class);
/**
* 生产者的组名
*/
@Value("${apache.rocketmq.producer.producerGroup}")
private String producerGroup;
/**
* NameServer 地址
*/
@Value("${apache.rocketmq.namesrvAddr}")
private String namesrvAddr;
public DefaultMQProducer producer = null;
/**
* @PostContruct是java框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
*/
@PostConstruct
public void defaultMQProducer() {
//生产者的组名
producer = new DefaultMQProducer(producerGroup);
//指定NameServer地址,多个地址以 ; 隔开
producer.setNamesrvAddr(namesrvAddr);
try {
/**
* Producer对象在使用之前必须要调用start初始化,初始化一次即可
* 注意:切记不可以在每次发送消息时,都调用start方法
*/
producer.start();
logger.info("defaultMQProducer start ");
} catch (Exception e) {
e.printStackTrace();
}
}
@PreDestroy
public void closeMQProduce(){
producer.shutdown();
logger.info("defaultMQProducer shutdown ");
}
}
准备工作已经完成,现在来编写message的底层发送方法
@Component
public class RocketSendEntranceToMessage {
private static Logger logger = LoggerFactory.getLogger(RocketSendEntranceToMessage.class);
/**
* MQ消息发送类
*/
@Autowired
private RocketSendMessage rocketSendMessage;
/**
* MQ向外暴漏的总入口
* @author wang xin.
* @date 2021/05/18.
* @param flag, commandRecords
* @return void.
*/
public void sendMqDisposeParam(String tag, String key, int flag, String context){
//封装参数到POJO,调用发送MQ方法
MQCommandRecord mqCommandRecord = new MQCommandRecord();
try {
mqCommandRecord.setTag(tag);//tag
mqCommandRecord.setKey(key);//类型
mqCommandRecord.setFlag(flag);//flag标识码
mqCommandRecord.setContext(context);//备注
sendMqToManage(mqCommandRecord);
}catch (Exception e) {
logger.error("",e);
}
}
/**
* MQ发送命令总入口
* @author wang xin.
* @date 2021/05/18.
* @param mqCommandRecord
* @return void.
*/
private void sendMqToManage(MQCommandRecord mqCommandRecord){
try {
//发送Mq到应用层
rocketSendMessage.sendMessage(mqCommandRecord.getTag(), mqCommandRecord.getKey(), mqCommandRecord.getFlag(), mqCommandRecord.getContext());
}catch (Exception e) {
logger.error("",e);
}
}
}
@Component
public class RocketSendMessage {
private static Logger logger = LoggerFactory.getLogger(RocketSendMessage.class);
@Autowired
RocketMQProducer rocketMQProvider;
/**
* topic标识
*/
@Value("${apache.rocketmq.producer.topic}")
private String topic;
/**
*
* @param Tag 类型
* @param key 存储数据库key
* @param context 存储记录的table
* @return 提交mq的结果值
*/
public boolean sendMessage(String Tag, String key, int flag, String context){
boolean res = false;
Message message;
try {
message = new Message(topic, Tag, key, flag, context.getBytes("UTF-8"), true);
SendResult result = rocketMQProvider.producer.send(message);
if(result.getSendStatus().compareTo(SendStatus.SEND_OK) == 0){
res = true;
}
logger.info("MQProducer send message time = {},Tag = {} key = {} context = {}",System.currentTimeMillis(), Tag, key, context);
}catch (Exception e){
//mq发送异常处理
logger.error("",e);
}
return res;
}
}
一些参数介绍:
- topic:表示消息要到的发送主题,必填
- tags:表示消息的标签,消费者在消费时,可以根据标签进行过滤,需要注意的是,一个生产者,只能指定一个tag
- keys:用于建立索引,之后可以通过命令工具/API/或者管理平台查询key,可以为一个消息设置多个key,用空格""进行分割
- flag:选填,消息的标记,完全由应用设置,RocketMQ不做任何处理,类似于memcached中flag的作用。
- body:消息的内容,这是一个字节数组,序列化方式由应用决定,例如你可以将一个json转为字节数组,也可以通过protol buffer、hessian编码转为字节数组。
- waitStoreMsgOK:表示发送消息后,是否需要等待消息同步刷新到磁盘上。如果broker配置为ASYNC_MASTER,那么只需要消息在master上刷新到磁盘即可;如果配置为SYNC_MASTER,那么还需要等待slave也刷新到磁盘。需要注意的是,waitStoreMsgOK默认为false,只有将设置为true的情况下,才会等待刷盘成功再返回。
我们提交一个请求测试下
@Autowired
private RocketSendEntranceToMessage rocketSendEntranceToMessage;
@GetMapping("/sendMQMessage")
public Map sendMQMessage() {
Map<String, Object> returnMap = new HashMap<>();
//声明发送的MQ消息
rocketSendEntranceToMessage.sendMqDisposeParam("123456789", "5", 1, JSON.toJSONString("我只是测试发送的MQ消息,嘿嘿"));
return returnMap;
}
控制台输出
测试的这条消息已经上传到RocketMQ集群下的broker-b下master和savle节点下,Today Consume Count为0,代表还未消费
用到的pojo类
public class MQCommandRecord implements Serializable{
@Getter
@Setter
private String tag;
@Getter
@Setter
private String key;
@Getter
@Setter
private int flag;
@Getter
@Setter
private String context;
@Override
public String toString() {
return "MQCommandRecord{" +
"tag='" + tag + '\'' +
", key='" + key + '\'' +
", flag=" + flag +
", context='" + context + '\'' +
'}';
}
}
至此,producer的示例代码已经全部完成
Consumer
和producer流程相似,创建个SpringBoot项目,配置下maven依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-common</artifactId>
<version>4.7.0</version>
</dependency>
在application.yml配置文件下配置RocketMQ nameServer地址和Topic
apache:
rocketmq:
namesrvAddr: 192.168.135.73:9876
consumer:
consumeGroup: wxConsume
topic: logTopic
之后编写个Consumer的启动类,设置要消费的Topic和Tags,并且设置是以push形式消费数据还是pull形式,本示例是以push形式(其实pull形式的底层还是以push为实现)
@Component
public class RocketMQConsumer {
private static Logger logger = LoggerFactory.getLogger(RocketMQConsumer.class);
/**
* 消费者的组名
*/
@Value("${apache.rocketmq.consumer.consumeGroup}")
private String consumerGroup;
/**
* NameServer 地址
*/
@Value("${apache.rocketmq.namesrvAddr}")
private String namesrvAddr;
/**
* topic标识
*/
@Value("${apache.rocketmq.consumer.topic}")
private String topic;
private DefaultMQPushConsumer consumer;
@Autowired
private ParseMQCommand parseMQCommand;
/**
* @PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
*/
@PostConstruct
public void defaultMQPushConsumer() {
//消费者的组名
consumer = new DefaultMQPushConsumer(consumerGroup);
//指定NameServer地址,多个地址以 ; 隔开
consumer.setNamesrvAddr(namesrvAddr);
try {
//订阅PushTopic下Tag为push的消息
consumer.subscribe(topic, "*");
//设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
//如果非第一次启动,那么按照上次消费的位置继续消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.registerMessageListener((MessageListenerConcurrently) (list, context) -> {
try {
for (MessageExt messageExt : list) {
String paramContext = new String(messageExt.getBody(), "UTF-8");
MQCommandRecord mqCommandRecord = new MQCommandRecord();
mqCommandRecord.setTag(messageExt.getTags());
mqCommandRecord.setKey(messageExt.getKeys());
mqCommandRecord.setFlag(messageExt.getFlag());
mqCommandRecord.setContext(paramContext);
parseMQCommand.addMQCommandRecord(mqCommandRecord);
logger.info("MQ consume receive time = {} ,消息体 {}",System.currentTimeMillis(), mqCommandRecord.getContext());
}
} catch (Exception e) {
logger.error("",e);
return ConsumeConcurrentlyStatus.RECONSUME_LATER; //稍后再试
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消费成功
});
consumer.start();
logger.info(" defaultMQPushConsumer start ");
} catch (Exception e) {
e.printStackTrace();
}
}
@PreDestroy
public void closeDefaultMQPushConsumer(){
consumer.shutdown();
logger.info(" defaultMQPushConsumer shutdown");
}
}
/**
* 公共方法
* 接受生产组发送的MQ消息
* @author wang xin.
* @version 1.0
* @date 2021/05/18 11:08.
* @Copyright:2021 汉王智远科技有限公司 All rights reserved.
*/
@Component
public class ParseMQCommand extends Thread {
private static Logger logger = LoggerFactory.getLogger(ParseMQCommand.class);
private ArrayDeque<MQCommandRecord> adqs = new ArrayDeque<>();
private volatile boolean isWait = false;
@Override
public void run() {
for(;;){
if(adqs.isEmpty()){
synchronized (ParseMQCommand.class){
try {
isWait = true;
ParseMQCommand.class.wait();
} catch (InterruptedException e) {
logger.error("",e);
}
}
}else{
try {
MQCommandRecord mqCommandRecord = adqs.poll();
//具体业务处理
} catch (Exception e) {
logger.error("",e);
}
}
}
}
public void addMQCommandRecord(MQCommandRecord mqCommandRecord){
adqs.addLast(mqCommandRecord);
if(isWait){
synchronized(ParseMQCommand.class){
ParseMQCommand.class.notifyAll();
}
}
}
}
用到的pojo类
public class MQCommandRecord implements Serializable{
@Getter
@Setter
private String tag;
@Getter
@Setter
private String key;
@Getter
@Setter
private int flag;
@Getter
@Setter
private String context;
@Override
public String toString() {
return "MQCommandRecord{" +
"tag='" + tag + '\'' +
", key='" + key + '\'' +
", flag=" + flag +
", context='" + context + '\'' +
'}';
}
}
消费者端也已经完成,因为之前的生产者已经生成了一条消息到RocketMQ的集群,所以我们在启动消费者端的时候就会消费这条消息,会在控制台打印出来这条消息
并且RocketMQ的可视化控制台也显示了此条消息已经消费成功
至此,生产者端和消费者端的demo已经完成,这只是一个非常简单的示例demo,实际项目远比此复杂的多,需要考虑到重复消费,消息丢失,消息积压,机器的扩容方式,消息的峰值处理等等问题