一 RocketMQ介绍
rocketmq是阿里巴巴开源的一款分布式的消息中间件,他源于jms规范但是不遵守jms规范。对于分布式只一点,如果你了用过其他mq并且了解过rocketmq,就知道rocketmq天生就是分布式的,可以说是broker、provider、consumer等各种分布式。
二 RocketMQ优点:
1 rmq去除对zk的依赖
2 rmq支持异步和同步两种方式刷磁盘
3 rmq单机支持的队列或者topic数量是5w
4 rmq支持消息重试
5 rmq支持严格按照一定的顺序发送消息
6 rmq支持定时发送消息
7 rmq支持根据消息ID来进行查询
8 rmq支持根据某个时间点进行消息的回溯
9 rmq支持对消息服务端的过滤
10 rmq消费并行度:顺序消费 取决于queue数量,乱序消费 取决于consumer数量
三RocketMQ的安装(windows)
首先去官网下载编译之后的版本,然后解压到本地目录,比如如下截图所示
然后按照如下步骤来操作:
1 配置ROCKETMQ_HOME到系统环境变量中,因为启动脚本会读取这个变量
2 进入bin目录,用编辑器打开红色标注的脚本
3 查看内容,发现每个脚本会调用另外一个脚本,最终要修改如下的脚本
打开之后找到这一行,修改成红色标注的一样
其实就是把2g改为1g,防止内存设置过大而导致的其他问题
4 分别进入bin目录下 启动如下脚本:
& 启动namesrv
& 启动brokerserver
看到如下就代表两个服务都成功启动了,接下来通过代码来模拟发送消息和消费消息
四RocketMQ发送消息和消费消息
看一下pom.xml的文件内容
4.0.0
com.suning.mq
rocketmq
1.0-SNAPSHOT
org.apache.rocketmq
rocketmq-client
4.2.0
com.alibaba
fastjson
1.2.44
先定义一个消息保存的载体:
package producer;
import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import java.io.Serializable;
/**
* @Author 18011618
* @Date 10:41 2018/7/17
* @Function 消息生产者
*/
public class Producer {
public static void main(String[] args) throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer("test-group");
producer.setNamesrvAddr("localhost:9876");
producer.setInstanceName("rmq-instance");
producer.start();
try {
for (int i=0;i<100;i++){
User user = new User();
user.setLoginName("abc"+i);
user.setPwd(String.valueOf(i));
Message message = new Message("log-topic", "user-tag",JSON.toJSONString(user).getBytes());
System.out.println("生产者发送消息:"+JSON.toJSONString(user));
producer.send(message);
}
} catch (Exception e) {
e.printStackTrace();
}
producer.shutdown();
}
/**
* 发送用户消息
*/
static class User implements Serializable{
private String loginName;
private String pwd;
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
定义消息的发送者:
package com.springboot.rocketmq.one.producer;
import com.alibaba.fastjson.JSON;
import com.springboot.rocketmq.content.UserContent;
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.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @Author 18011618
* @Date 10:31 2018/7/18
* @Function 模拟用户消息发送
*/
@Component
public class UserProducer {
/**
* 生产者的组名
*/
@Value("${suning.rocketmq.producerGroup}")
private String producerGroup;
/**
* NameServer 地址
*/
@Value("${suning.rocketmq.namesrvaddr}")
private String namesrvAddr;
@PostConstruct
public void produder() {
DefaultMQProducer producer = new DefaultMQProducer(producerGroup);
producer.setNamesrvAddr(namesrvAddr);
try {
producer.start();
for (int i = 0; i < 100; i++) {
UserContent userContent = new UserContent(String.valueOf(i),"abc"+i);
String jsonstr = JSON.toJSONString(userContent);
System.out.println("发送消息:"+jsonstr);
Message message = new Message("user-topic", "user-tag", jsonstr.getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult result = producer.send(message);
System.err.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
producer.shutdown();
}
}
}
定义消息的消费者:
package consumer;
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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class Consumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test-group");
consumer.setNamesrvAddr("localhost:9876");
consumer.setInstanceName("rmq-instance");
consumer.subscribe("log-topic", "user-tag");
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(
List msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("消费者消费数据:"+new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
}
}
接下来先启动消费者,然后再启动生产者,看一下效果
生产者控制台发送消息:
消费者控制台消费消息
且能发现消费消息的机制 默认是乱序的
五 RocketMQ Web启动
上面虽然成功了发送了消息和消费了消息,可惜都是通过控制台查看的,其实rocketmq有扩展的组件,其中有一个组件就是支持web界面查看消息相关的功能,下面就简单介绍一下如何使用这个功能:访问 https://github.com/apache/rocketmq-externals/ 可以看到如下界面,选择红色标注的模块,下载到本地:
然后进行部署,它是一个完整的springboot项目,所以可以用IDEA打开
然后需要修改application.properties的相关内容
server.contextPath=
server.port=8080
#spring.application.index=true
spring.application.name=rocketmq-console
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
logging.config=classpath:logback.xml
#if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
rocketmq.config.namesrvAddr=localhost:9876
#if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
rocketmq.config.isVIPChannel=
#rocketmq-console's data path:dashboard/monitor
rocketmq.config.dataPath=/tmp/rocketmq-console/data
#set it false if you don't want use dashboard.default true
rocketmq.config.enableDashBoardCollect=true
其实主要就是这个地址,然后启动App这个类,这个时候在浏览器端访问 http://localhost:8080/ 会出现如下界面
点击Dashboard可以查看相关消息主题的信息
还可以查看具体的发送消息和消费消息:点击Message
点击Detail 能看查看消息具体的消息
这个控制台的功能还是很强大的,都可以去摸索一下如何使用.
六SpringBoot和RocketMQ整合
整合方式有很多种,但是可以总结为两大类,一类是基于高度封装(使用起来非常简单,通用性高),一类是基于原始开发(没啥可通用性),在这里把这两个类别都讲解一下.
& 先讲一种,基于原始API开发,不要啥封装,只需要引入一个配置文件即可
看一下整体项目结构,主要实现的部分是红色标注的,其它类是第二种封装使用的,
看一下pom.xml的文件内容
spring_boot
com.suning.springboot
1.0-SNAPSHOT
4.0.0
spring_boot_rocketmq
org.projectlombok
lombok
provided
org.apache.rocketmq
rocketmq-client
4.1.0-incubating
在这里为了简化javabean的操作,引入了lombok组件.定义一个javabean用来承载消息
package com.springboot.rocketmq.content;
import lombok.*;
import lombok.experimental.Accessors;
/**
* @Author 18011618
* @Date 19:28 2018/7/17
* @Function 发送消息体
*/
@ToString
@AllArgsConstructor
@EqualsAndHashCode
@Accessors(chain = true)
@Getter
@Setter
public class UserContent {
private String username;
private String pwd;
}
关于这里的注解参数可以具体参考:lombok的常用注解含义
定义生产者:
package com.springboot.rocketmq.producer;
import com.alibaba.fastjson.JSON;
import com.springboot.rocketmq.content.UserContent;
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.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import javax.annotation.PostConstruct;
/**
* @Author 18011618
* @Date 10:31 2018/7/18
* @Function 模拟用户消息发送
*/
@Component
public class UserProducer {
/**
* 生产者的组名
*/
@Value("${suning.rocketmq.producerGroup}")
private String producerGroup;
/**
* NameServer 地址
*/
@Value("${suning.rocketmq.namesrvaddr}")
private String namesrvAddr;
@PostConstruct
public void produder() {
DefaultMQProducer producer = new DefaultMQProducer(producerGroup);
producer.setNamesrvAddr(namesrvAddr);
try {
producer.start();
for (int i = 0; i < 100; i++) {
UserContent userContent = new UserContent(String.valueOf(i),"abc"+i);
String jsonstr = JSON.toJSONString(userContent);
System.out.println("发送消息:"+jsonstr);
Message message = new Message("user-topic", "user-tag", jsonstr.getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult result = producer.send(message);
System.err.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
producer.shutdown();
}
}
}
这里使用了两个新注解:
@PostConstruct:它的作用是相当于servlet的Init功能,在对象的构造器执行完了,就会立马调用该方法
@Value:注入classpath下面的配置文件中的值到变量中
定义消费者:
package com.springboot.rocketmq.consumer;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @Author 18011618
* @Date 10:31 2018/7/18
* @Function 模拟用户消息消费
*/
@Component
public class UserConsumer {
/**
* 消费者的组名
*/
@Value("${suning.rocketmq.conumerGroup}")
private String consumerGroup;
/**
* NameServer 地址
*/
@Value("${suning.rocketmq.namesrvaddr}")
private String namesrvAddr;
@PostConstruct
public void consumer() {
System.err.println("init defaultMQPushConsumer");
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup);
consumer.setNamesrvAddr(namesrvAddr);
try {
consumer.subscribe("user-topic", "user-tag");
consumer.registerMessageListener((MessageListenerConcurrently) (list, context) -> {
try {
for (MessageExt messageExt : list) {
System.err.println("消费消息: " + new String(messageExt.getBody()));//输出消息内容
}
} catch (Exception e) {
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER; //稍后再试
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消费成功
});
consumer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后再看一下配置文件:
server.port=8080
# 生产者的组名
suning.rocketmq.producerGroup=user-group
# 消费者的组名
suning.rocketmq.conumerGroup=user-group
# NameServer地址
suning.rocketmq.namesrvaddr=localhost:9876
写一个启动类
package com.springboot.rocketmq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Author 18011618
* @Description
* @Date 11:02 2018/7/17
* @Modify By
*/
@SpringBootApplication
public class RocketmqApplication {
public static void main(String[] args) {
SpringApplication.run(RocketmqApplication.class,args);
}
}
这个时候直接启动该应用就能看到消息的发送和消费的情况:
其实这样就实现了和springboot的整合,其实也很简单,但是有的人会感觉代码有点啰嗦,而且没啥通用性,如果业务中有多个消息队列的应用,那么就会出现很多重复代码,针对这种情况,可以按照下面这种方式来进行简化,如果不想自己动手的话,可以去找开源的代码,看有没有人实现了类似功能,拿过来直接用,还有就是在熟悉原理之后,也可以自己进行二次封装.
& 使用第三方组件整合springboot
首先看一下整体项目结构:以红色标注
接下来具体分析要封装的步骤:
1 定义rocketmq相关参数的配置文件
参数可以自己根据实际业务进行选择,因为本身rocketmq中很多的参数都是有默认值的,所以可以不需要配置所有的参数,按需即可,存储位置,网上很多的例子都是配置在默认的application.yaml中(不建议这样做,因为这个是全局的,建议发在一个非全局的文件,然后动态注入进来,达到隔离)
2 定义保存配置文件的实体和对应的配置文件加载解析类
& 保存配置文件的类
package com.springboot.rocketmq.two.config;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* @Author 18011618
* @Date 19:31 2018/7/18
* @Function 读取配置文件信息
*/
@PropertySource("classpath:config/rocketmq.properties")
@ConfigurationProperties(prefix = "suning.rocketmq")
@Configuration
@Setter
@Getter
@ToString
@Accessors(chain = true)
public class RocketMQProperties {
private String namesrvAddr;
private String producerGroupName;
private String transactionProducerGroupName;
private String consumerGroupName;
private String producerInstanceName;
private String consumerInstanceName;
private String producerTranInstanceName;
private int consumerBatchMaxSize;
private boolean consumerBroadcasting;
private boolean enableHistoryConsumer;
private boolean enableOrderConsumer;
private List subscribe = new ArrayList();
}
@PropertySource:指定要加载的配置文件路径,其实默认是可以不写的,如果是加载classpath下面的配置文件,因为它会自己去寻找,但是有时候不同的版本,不写的话又会出现错误,所以为了不出现错误,通常建议配置一下,这样肯定不会有错的
@ConfigurationProperties:指定读取配置文件的规则,比如前缀是什么,不存在的字段是否可以忽略等
@Configuration:相当于xml的配置标签,这样
& 加载配置文件的参数,实例化相关的bean
package com.springboot.rocketmq.two.config;
import javax.annotation.PostConstruct;
import com.springboot.rocketmq.two.bean.MessageEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
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.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author 18011618
* @Date 19:36 2018/7/18
* @Function 通过使用指定的文件读取类 来加载配置文件到字段中
*/
@Configuration
@EnableConfigurationProperties(RocketMQProperties.class)
@Slf4j
public class RocketMQConfiguration {
@Autowired
private RocketMQProperties rocketMQProperties;
//事件监听
@Autowired
private ApplicationEventPublisher publisher = null;
private static boolean isFirstSub = true;
private static long startTime = System.currentTimeMillis();
/**
* 容器初始化的时候 打印参数
*/
@PostConstruct
public void init() {
System.err.println(rocketMQProperties.getNamesrvAddr());
System.err.println(rocketMQProperties.getProducerGroupName());
System.err.println(rocketMQProperties.getConsumerBatchMaxSize());
System.err.println(rocketMQProperties.getConsumerGroupName());
System.err.println(rocketMQProperties.getConsumerInstanceName());
System.err.println(rocketMQProperties.getProducerInstanceName());
System.err.println(rocketMQProperties.getProducerTranInstanceName());
System.err.println(rocketMQProperties.getTransactionProducerGroupName());
System.err.println(rocketMQProperties.isConsumerBroadcasting());
System.err.println(rocketMQProperties.isEnableHistoryConsumer());
System.err.println(rocketMQProperties.isEnableOrderConsumer());
System.out.println(rocketMQProperties.getSubscribe().get(0));
}
/**
* 创建普通消息发送者实例
* @return
* @throws MQClientException
*/
@Bean
public DefaultMQProducer defaultProducer() throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer(
rocketMQProperties.getProducerGroupName());
producer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
producer.setInstanceName(rocketMQProperties.getProducerInstanceName());
producer.setVipChannelEnabled(false);
producer.setRetryTimesWhenSendAsyncFailed(10);
producer.start();
log.info("rocketmq producer server is starting....");
return producer;
}
/**
* 创建支持消息事务发送的实例
* @return
* @throws MQClientException
*/
@Bean
public TransactionMQProducer transactionProducer() throws MQClientException {
TransactionMQProducer producer = new TransactionMQProducer(
rocketMQProperties.getTransactionProducerGroupName());
producer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
producer.setInstanceName(rocketMQProperties
.getProducerTranInstanceName());
producer.setRetryTimesWhenSendAsyncFailed(10);
// 事务回查最小并发数
producer.setCheckThreadPoolMinSize(2);
// 事务回查最大并发数
producer.setCheckThreadPoolMaxSize(2);
// 队列数
producer.setCheckRequestHoldMax(2000);
producer.start();
log.info("rocketmq transaction producer server is starting....");
return producer;
}
/**
* 创建消息消费的实例
* @return
* @throws MQClientException
*/
@Bean
public DefaultMQPushConsumer pushConsumer() throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(
rocketMQProperties.getConsumerGroupName());
consumer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
consumer.setInstanceName(rocketMQProperties.getConsumerInstanceName());
//判断是否是广播模式
if (rocketMQProperties.isConsumerBroadcasting()) {
consumer.setMessageModel(MessageModel.BROADCASTING);
}
//设置批量消费
consumer.setConsumeMessageBatchMaxSize(rocketMQProperties
.getConsumerBatchMaxSize() == 0 ? 1 : rocketMQProperties
.getConsumerBatchMaxSize());
//获取topic和tag
List subscribeList = rocketMQProperties.getSubscribe();
for (String sunscribe : subscribeList) {
consumer.subscribe(sunscribe.split(":")[0], sunscribe.split(":")[1]);
}
// 顺序消费
if (rocketMQProperties.isEnableOrderConsumer()) {
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(
List msgs, ConsumeOrderlyContext context) {
try {
context.setAutoCommit(true);
msgs = filterMessage(msgs);
if (msgs.size() == 0)
return ConsumeOrderlyStatus.SUCCESS;
publisher.publishEvent(new MessageEvent(msgs, consumer));
} catch (Exception e) {
e.printStackTrace();
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
}
// 并发消费
else {
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List msgs,
ConsumeConcurrentlyContext context) {
try {
//过滤消息
msgs = filterMessage(msgs);
if (msgs.size() == 0)
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
publisher.publishEvent(new MessageEvent(msgs, consumer));
} catch (Exception e) {
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
}
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
try {
consumer.start();
} catch (Exception e) {
e.printStackTrace();
}
log.info("rocketmq consumer server is starting....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return consumer;
}
/**
* 消息过滤
* @param msgs
* @return
*/
private List filterMessage(List msgs) {
if (isFirstSub && !rocketMQProperties.isEnableHistoryConsumer()) {
msgs = msgs.stream()
.filter(item -> startTime - item.getBornTimestamp() < 0)
.collect(Collectors.toList());
}
if (isFirstSub && msgs.size() > 0) {
isFirstSub = false;
}
return msgs;
}
}
这个类主要加载配置文件里面参数的值,然后初始化生成producer,事务producer,consumer等实例。
@EnableConfigurationProperties:启动自动配置文件属性的获取,通过指定的类
看一下配置文件的内容
# 指定namesrv地址
suning.rocketmq.namesrvAddr=localhost:9876
#生产者group名称
suning.rocketmq.producerGroupName=user_group
#事务生产者group名称
suning.rocketmq.transactionProducerGroupName=order_transaction
#消费者group名称
suning.rocketmq.consumerGroupName=user_consumer_group
#生产者实例名称
suning.rocketmq.producerInstanceName=user_producer_instance
#消费者实例名称
suning.rocketmq.consumerInstanceName=user_consumer_instance
#事务生产者实例名称
suning.rocketmq.producerTranInstanceName=user_producer_transacition
#一次最大消费多少数量消息
suning.rocketmq.consumerBatchMaxSize=1
#广播消费
suning.rocketmq.consumerBroadcasting=false
#消费的topic:tag
suning.rocketmq.subscribe[0]=user-topic:white
#启动的时候是否消费历史记录
suning.rocketmq.enableHistoryConsumer=false
#启动顺序消费
suning.rocketmq.enableOrderConsumer=false
3 创建消息的发送
package com.springboot.rocketmq.two.producer;
import com.springboot.rocketmq.two.bean.User;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSON;
import java.util.List;
@RestController
public class ProducerController {
@Autowired
private DefaultMQProducer defaultProducer;
@Autowired
private TransactionMQProducer transactionProducer;
/**
* 发送普通消息
*/
@GetMapping("/sendMessage")
public void sendMsg() {
for(int i=0;i<100;i++){
User user = new User();
user.setId(String.valueOf(i));
user.setUsername("jhp"+i);
String json = JSON.toJSONString(user);
Message msg = new Message("user-topic","white",json.getBytes());
try {
SendResult result = defaultProducer.send(msg);
System.out.println("消息id:"+result.getMsgId()+":"+","+"发送状态:"+result.getSendStatus());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 发送事务消息
* @return
*/
@GetMapping("/sendTransactionMess")
public String sendTransactionMsg() {
SendResult sendResult = null;
try {
// a,b,c三个值对应三个不同的状态
String ms = "c";
Message msg = new Message("user-topic","white",ms.getBytes());
// 发送事务消息
sendResult = transactionProducer.sendMessageInTransaction(msg, (Message msg1, Object arg) -> {
String value = "";
if (arg instanceof String) {
value = (String) arg;
}
if (value == "") {
throw new RuntimeException("发送消息不能为空...");
} else if (value =="a") {
return LocalTransactionState.ROLLBACK_MESSAGE;
} else if (value =="b") {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.ROLLBACK_MESSAGE;
}, 4);
System.out.println(sendResult);
} catch (Exception e) {
e.printStackTrace();
}
return sendResult.toString();
}
/**
* 支持顺序发送消息
*/
@GetMapping("/sendMessOrder")
public void sendMsgOrder() {
for(int i=0;i<100;i++) {
User user = new User();
user.setId(String.valueOf(i));
user.setUsername("jhp" + i);
String json = JSON.toJSONString(user);
Message msg = new Message("user-topic", "white", json.getBytes());
try{
defaultProducer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List mqs, Message msg, Object arg) {
int index = ((Integer) arg) % mqs.size();
return mqs.get(index);
}
},i);
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
创建消息的消费:
package com.springboot.rocketmq.two.consumer;
import java.util.List;
import com.springboot.rocketmq.two.bean.MessageEvent;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 监听消息进行消费
*/
@Component
public class ConsumerService {
@EventListener(condition = "#event.msgs[0].topic=='user-topic' && #event.msgs[0].tags=='white'")
public void rocketmqMsgListener(MessageEvent event) {
try {
List msgs = event.getMsgs();
for (MessageExt msg : msgs) {
System.err.println("消费消息:"+new String(msg.getBody()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
为了方便监听消费,增加了一个事件监听功能:
package com.springboot.rocketmq.two.bean;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.context.ApplicationEvent;
import java.io.UnsupportedEncodingException;
import java.util.List;
/**
* 监听对象
* @author 18011618
*
*/
public class MessageEvent extends ApplicationEvent {
private static final long serialVersionUID = -4468405250074063206L;
private DefaultMQPushConsumer consumer;
private List msgs;
public MessageEvent(List msgs, DefaultMQPushConsumer consumer) throws Exception {
super(msgs);
this.consumer = consumer;
this.setMsgs(msgs);
}
public DefaultMQPushConsumer getConsumer() {
return consumer;
}
public void setConsumer(DefaultMQPushConsumer consumer) {
this.consumer = consumer;
}
public List getMsgs() {
return msgs;
}
public void setMsgs(List msgs) {
this.msgs = msgs;
}
}
写一个启动应用类:
package com.springboot.rocketmq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* @Author 18011618
* @Description
* @Date 11:02 2018/7/17
* @Modify By
*/
@SpringBootApplication
//@ComponentScan(basePackages = "com.springboot.rocketmq.one")
@ComponentScan(basePackages = "com.springboot.rocketmq.two")
public class RocketmqApplication {
public static void main(String[] args) {
SpringApplication.run(RocketmqApplication.class,args);
}
}
这里要区别于第一种方式,所以只扫描第二种方式的包
然后访问浏览器端 http://localhost:8080/sendMessage 效果如下截图所示
至此生产者和消息这就可以进行正常的发送和消费了.
扩展:根据条件动态创建bean
场景:通常在项目中我们会有一些开关或者一些标识值,来做不同的操作,以前的话大家可能都是通过在代码中加入大量的if判断,这样虽然实现了功能,但是维护和可读性都是比较差,所以spring在3.x版本之后开始加入了@Conditonal条件注解:很简单就是开发者根据一些条件来决定是否创建bean或者动态来创建bean,这就比较简单了,而springboot为了方便开发,又对上面这个注解进行了一些列的扩展,让使用起来更加的简单:,下面就列举常用的条件注解:
@ConditionalOnClass:当前上下文存存在个类,才会创建对应的bean实例
解释:如果写入Hello.class,那么就代表在当前的类路径下面肯定又要这样的一个类存在
@ConditionalOnBean:当前上下文存在某个类的实例,才会创建对应的bean实例
解释:所谓实例,就是已经被spring实例化过了,比如一般需要在类上加一些注解@Component
@Service @Configuration @Respositry …..
@ConditionalOnMissingBean:和上面整好相反,不在对应的实例,创建当前bean的实例
@ConditionalOnMissingClass:不存在某个类,就创建当前bean的实例
@ConditionalOnProperty:根据配置文件中的属性条件,来创建当前bean的实例
解释:这个参数组合有好几种,下面一一列举
1 prefix and value
prefix:参数的前缀
value:参数前缀后面的字段名称
2 name and havingValue
name:完整的字段名称
havingValue:字段名称对应的值
3 value and matchIfMissing
value:配置文件里面参数的值 是否为true
看一下这个注解的源码:
@ConditionalOnExpression:根据表达式的值,来创建当前bean的实例
解释:目前该值只能是boolean类型,也就是说是true或者false,在熟悉这些知识之后,优化一下上面的那个配置代码:
package com.springboot.rocketmq.two.config;
import javax.annotation.PostConstruct;
import com.springboot.rocketmq.two.bean.MessageEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
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.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author 18011618
* @Date 19:36 2018/7/18
* @Function 通过使用指定的文件读取类 来加载配置文件到字段中
*/
@Configuration
@EnableConfigurationProperties(RocketMQProperties.class)
@Slf4j
@ConditionalOnProperty(prefix = "suning.rocketmq",value = "namesrvAddr")
public class RocketMQConfiguration {
@Autowired
private RocketMQProperties rocketMQProperties;
//事件监听
@Autowired
private ApplicationEventPublisher publisher = null;
private static boolean isFirstSub = true;
private static long startTime = System.currentTimeMillis();
/**
* 容器初始化的时候 打印参数
*/
@PostConstruct
public void init() {
System.out.println("配置信息:"+rocketMQProperties);
}
/**
* 创建普通消息发送者实例
* @return
* @throws MQClientException
*/
@Bean
@ConditionalOnClass(DefaultMQProducer.class)
@ConditionalOnMissingBean(DefaultMQProducer.class)
@ConditionalOnProperty(prefix = "suning.rocketmq",value = "namesrvAddr")
public DefaultMQProducer defaultProducer() throws MQClientException {
System.err.println("create defaultProducer....");
DefaultMQProducer producer = new DefaultMQProducer(
rocketMQProperties.getProducerGroupName());
producer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
producer.setInstanceName(rocketMQProperties.getProducerInstanceName());
producer.setVipChannelEnabled(false);
producer.setRetryTimesWhenSendAsyncFailed(10);
producer.start();
log.info("rocketmq producer server is starting....");
return producer;
}
/**
* 创建支持消息事务发送的实例
* @return
* @throws MQClientException
*/
@Bean
@ConditionalOnProperty(prefix = "suning.rocketmq",value = "transactionProducerGroupName")
@ConditionalOnClass(TransactionMQProducer.class)
@ConditionalOnMissingBean(TransactionMQProducer.class)
public TransactionMQProducer transactionProducer() throws MQClientException {
System.err.println("create transactionProducer....");
TransactionMQProducer producer = new TransactionMQProducer(
rocketMQProperties.getTransactionProducerGroupName());
producer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
producer.setInstanceName(rocketMQProperties
.getProducerTranInstanceName());
producer.setRetryTimesWhenSendAsyncFailed(10);
// 事务回查最小并发数
producer.setCheckThreadPoolMinSize(2);
// 事务回查最大并发数
producer.setCheckThreadPoolMaxSize(2);
// 队列数
producer.setCheckRequestHoldMax(2000);
producer.start();
log.info("rocketmq transaction producer server is starting....");
return producer;
}
/**
* 创建消息消费的实例
* @return
* @throws MQClientException
*/
@Bean
@ConditionalOnProperty(prefix = "suning.rocketmq",value = "consumerGroupName")
@ConditionalOnClass(DefaultMQPushConsumer.class)
@ConditionalOnMissingBean(DefaultMQPushConsumer.class)
public DefaultMQPushConsumer pushConsumer() throws MQClientException {
System.err.println("create pushConsumer....");
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(
rocketMQProperties.getConsumerGroupName());
consumer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
consumer.setInstanceName(rocketMQProperties.getConsumerInstanceName());
//判断是否是广播模式
if (rocketMQProperties.isConsumerBroadcasting()) {
consumer.setMessageModel(MessageModel.BROADCASTING);
}
//设置批量消费
consumer.setConsumeMessageBatchMaxSize(rocketMQProperties
.getConsumerBatchMaxSize() == 0 ? 1 : rocketMQProperties
.getConsumerBatchMaxSize());
//获取topic和tag
List subscribeList = rocketMQProperties.getSubscribe();
for (String sunscribe : subscribeList) {
consumer.subscribe(sunscribe.split(":")[0], sunscribe.split(":")[1]);
}
// 顺序消费
if (rocketMQProperties.isEnableOrderConsumer()) {
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(
List msgs, ConsumeOrderlyContext context) {
try {
context.setAutoCommit(true);
msgs = filterMessage(msgs);
if (msgs.size() == 0)
return ConsumeOrderlyStatus.SUCCESS;
publisher.publishEvent(new MessageEvent(msgs, consumer));
} catch (Exception e) {
e.printStackTrace();
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
}
// 并发消费
else {
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List msgs,
ConsumeConcurrentlyContext context) {
try {
//过滤消息
msgs = filterMessage(msgs);
if (msgs.size() == 0)
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
publisher.publishEvent(new MessageEvent(msgs, consumer));
} catch (Exception e) {
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
}
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
try {
consumer.start();
} catch (Exception e) {
e.printStackTrace();
}
log.info("rocketmq consumer server is starting....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return consumer;
}
/**
* 消息过滤
* @param msgs
* @return
*/
private List filterMessage(List msgs) {
if (isFirstSub && !rocketMQProperties.isEnableHistoryConsumer()) {
msgs = msgs.stream()
.filter(item -> startTime - item.getBornTimestamp() < 0)
.collect(Collectors.toList());
}
if (isFirstSub && msgs.size() > 0) {
isFirstSub = false;
}
return msgs;
}
}
ok 到此为止本篇文章就讲解完了,谢谢!