文章目录
1.前话
目前使用RocketMq
最主流的无非就三种方式:
- 直接使用
RocketMq
客户端jar包的Producer
和Consumer
; - 对接传统
Spring
容器,将Producer
和Consumer
交给Spring
容器进行管理; - 使用
RocketMq-Springboot-Starter
包快速配置集成到Springboot
项目。
对于第一种直接使用RocketMq
客户端的方式,这里便不做介绍了,直接跳转至RocketMq
的Github
文档观看官方给的样例即可。
这里主要介绍的是传统的Spring
项目和Springboot
项目集成RocketMq
的最佳实践,以及扩展性的简单说明一下Springboot
集成的原理。
2.传统Spring
集成
对于传统Spring
项目而言,要集成RocketMq
和直接使用客户端的Producer
和Consumer
是差不了太多的,无非是初始化了对象后交给Spring
容器进行管理,后续需要使用的时候直接从Spring
容器中取出来即可。
2.1 Maven
配置
RocketMq
客户端jar包配置如下:
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-common</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-logging</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-remoting</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-srvutil</artifactId>
<version>4.5.2</version>
</dependency>
2.2 Configuration
配置
将需要使用的Producer
和Consumer
统一放到Configuration
类中进行管理,代码如下:
@Configuration
public class RocketMqConfiguration {
@Value("${rocketMq.nameSrvAddr}")
private String nameSrvAddr;
@Value("${rocketMq.producerGroup}")
private String producerGroup;
/**
* 配置Producer交给Spring进行管理
*
* @return
*/
@Bean(initMethod = "start", destroyMethod = "shutdown")
public DefaultMQProducer defaultMqProducer() {
// 使用默认的MQ producerGroup分组
DefaultMQProducer producer = new DefaultMQProducer(producerGroup);
// 设置NameServer地址
producer.setNamesrvAddr(nameSrvAddr);
// 其它属性看需求
return producer;
}
@Value("${rocketMq.consumerGroup}")
private String consumerGroup;
@Value("${rocketMq.consumerTopic}")
private String consumerTopic;
@Value("${rocketMq.consumerTag}")
private String consumerTag;
/**
* 注册默认的Consumer
*
* @param messageListenerConcurrently
* @return
* @throws MQClientException
*/
@Bean(initMethod = "start", destroyMethod ="shutdown")
public DefaultMQPushConsumer defaultMqPushConsumer(
@Qualifier("generalConcurrentlyListener")MessageListenerConcurrently messageListenerConcurrently)
throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup);
// 设置NameServer地址
consumer.setNamesrvAddr(nameSrvAddr);
// 订阅topic和tag
consumer.subscribe(consumerTopic, consumerTag);
// 注册listener监听器
consumer.registerMessageListener(messageListenerConcurrently);
// 其它属性配置看需求
return consumer;
}
}
配置方式和直接使用初始化使用差不多,只是把初始化后的Producer
和Consumer
交给了Spring
容器进行管理,具体的RocketMq
属性再配置到Spring
容器中即可。
2.3 配置MessageListener
监听器
在Configuration
类中是引用了MessageListener
监听类,因此需要在Spring
容器中创建一个名为generalConcurrentlyListener
的Bean
,代码如下:
@Slf4j
@Component("generalConcurrentlyListener")
public class GeneralConcurrentlyListener implements MessageListenerConcurrently {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (Message m : msgs) {
log.info("timestamp:{} msg body:{} msg data:{}", System.currentTimeMillis(), new String(m.getBody()), m);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
一个很简单的实现,只需要确保这个Bean
交给Spring
容器管理即可,到这里就能保证Consumer
是可以正常消费Topic
和Tag
的消息了。
2.4 使用Producer
如果要发送消息只需要从Spring
容器中获取Producer
,再使用对象进行相应的发消息操作即可,这里是使用了最简单的方式,代码如下:
@Slf4j
@Service
public class SendMqService {
@Value("${rocketMq.consumerTopic}")
private String consumerTopic;
@Value("${rocketMq.consumerTag}")
private String consumerTag;
@Autowired
private DefaultMQProducer producer;
/**
* 发送mq消息
*
* @param messageBody
*/
public void sendMq(String messageBody) {
Message message = new Message(consumerTopic, consumerTag, messageBody.getBytes());
try {
// 具体发送什么类型的消息看需求
SendResult sendResult = producer.send(message);
log.info("sendResult:{}", sendResult);
} catch (Exception e) {
log.error("发送mq消息失败", e);
}
}
}
直接调用sendMq()
方法即可发送普通的RocketMq
消息,如果要自定义不同的消息只需要添加不同的方法即可。
Spring
简单集成RocketMq
只需要配置这几步,使用方式和RocketMq
的Github
使用文档一样。确保Spring
容器有如下几个属性:
rocketMq.nameSrvAddr=http://***
rocketMq.producerGroup=GID_DEV_SPRINGBOOT_DEMO
rocketMq.consumerGroup=GID_DEV_SPRINGBOOT_DEMO
rocketMq.consumerTopic=Hello-Test
rocketMq.consumerTag=TAG_DEV_HELLO_WORLD
3.SpringBoot
快速集成
一般而言,使用较广的框架都会针对Springboot
出一个Starter
包以方便开发快速集成,RocketMq
同理。
3.1 Maven
配置
Springboot
的Starter
包配置如下:
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
3.2 Yaml
配置
rocketmq:
name-server: http://***
producer:
group: GID_DEV_SPRINGBOOT_DEMO
3.3 MessageListener
配置
如果有消费者则需要配置MessageLisenter
监听器来监听队列消息,代码如下:
@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "GID_DEV_SPRINGBOOT_DEMO",
topic = "Hello-Test", selectorExpression = "TAG_DEV_HELLO_WORLD",
messageModel = MessageModel.CLUSTERING)
public class ClusterMqListener implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
log.info("clustering consume message: {}", message);
}
}
consumerGroup
、topic
和selectorExpression
都可以将属性配置在Spring
容器中再通过${}
通配符去获取。
注:实现类一定要实现RocketMQListener接口,否则会抛异常
3.4 使用RocketMQTemplate
模板类发送
RocketMq
的Springboot
包提供了发送消息模板类RocketMQTemplate
,这个类提供了如下发送方式的快捷方法:
syncSend()
:同步发送消息;syncSendOrderly()
:同步发送严格顺序消息,默认使用的Selector
是SelectMessageQueueByHash
;asyncSend()
:异步发送,但必须要填入SendCallboack
回调实现类;asyncSendOrderly
:异步发送严格顺序消息,同样需要填入回调实现类,默认使用的Selector
是SelectMessageQueueByHash
;sendOneWay()
:单方发送消息;sendOneWayOrderly()
:单方发送严格顺序消息,默认使用的Selector
是SelectMessageQueueByHash
。
通用的方法大概有这六大类,事务性的用得少便不写上去了,这六大类快捷发送消息方法将一些通用流程都封装到了方法中,如果没有特殊的需求直接使用这些模板方法可以节省一定的开发时间。使用代码如下:
@Slf4j
@Service
public class SendMqService {
@Value("${rocketMq.consumerTopicTag:Hello-Test:TAG_DEV_HELLO_WORLD}")
private String topicTag;
@Autowired
private RocketMQTemplate rocketMqTemplate;
/**
* 发送mq消息
*
* @param messageBody
*/
public void sendMq(String messageBody) {
// 具体发送什么类型的消息看需求
SendResult sendResult = rocketMqTemplate.syncSend(topicTag, content);
log.info("sendResult:{}", sendResult);
}
}
相较于直接使用DefaultMQProducer
类去自己封装消息体及确定各种对象,使用模板类发送更为简单,如有特殊需求同样可以参考传统Spring
那种使用方式,直接获取DefaultMQProducer
类,再自己封装消息体和发送方式。
这种配置使用方式个人是十分推荐的,快捷方便,简单直接,基本能覆盖大部分的使用场景,但这种使用方式并没有很常见,更多的还是传统Spring
的那种配置方式。
4.Springboot
自动集成的原理
4.1 RocketMq
的自动装填类
只要稍微熟悉Springboot
自动装填机制,就可以知道RocketMq
一定是写了个AutoConfiguration
类去嵌入Springboot
,从rocketmq-spring-boot
jar包中就很容易看到RocketMQAutoConfiguration
类,这个类就是自动集成的核心了。
这个类做的事也很简单,就是把要集成到Spring
框架的事情在这个类中帮我们做了,其源码如下:
@Configuration
public class RocketMQAutoConfiguration {
@Autowired
private Environment environment;
@Bean
public DefaultMQProducer defaultMQProducer(RocketMQProperties rocketMQProperties) {
// **配置略过,感兴趣的可以自行去看看
DefaultMQProducer producer;
// **配置略过,感兴趣的可以自行去看看
return producer;
}
@Bean(destroyMethod = "destroy")
public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer,
ObjectMapper rocketMQMessageObjectMapper,
RocketMQMessageConverter rocketMQMessageConverter) {
RocketMQTemplate rocketMQTemplate = new RocketMQTemplate();
// **配置略过,感兴趣的可以自行去看看
return rocketMQTemplate;
}
}
贴出来的代码省略了很多,但里面大致的关键要素代码就这几段,是不是和传统的Spring
配置方式相差不大?
4.2 Producer
的启动
看完上段代码是知道了自动装填类帮我们把DefaultMQProducer
和RocketMQTemplate
类注册到了Spring
容器中,但并没有看到调用Producer
的start()
方法,那么Producer
启动是在那里完成的呢?请看到RocketMQTemplate
类的关键源码:
public class RocketMQTemplate extends AbstractMessageSendingTemplate<String>
implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
if (producer != null) {
producer.start();
}
}
@Override
public void destroy() {
if (Objects.nonNull(producer)) {
producer.shutdown();
}
for (Map.Entry<String, TransactionMQProducer> kv : cache.entrySet()) {
if (Objects.nonNull(kv.getValue())) {
kv.getValue().shutdown();
}
}
cache.clear();
}
}
看到这个类实现了InitializingBean
和DisposableBean
接口是不是就豁然开朗了?原来Producer
是借助RocketMQTemplate
还魂了。
4.3 MessageListener
的读取解析
那么疑问又来了,Consumer
是在哪里创建监听消息的呢?这就需要看到RocketMQAutoConfiguration
类中的一段关键代码了:
@Import({ListenerContainerConfiguration.class})
public class RocketMQAutoConfiguration {
}
在RocketMQAutoConfiguration
类中引入了ListenerContainerConfiguration
类,在这个类中将会完成对@RocketMQMessageListener
注解的解析并生成对应的Consumer
,其关键源码如下:
@Configuration
public class ListenerContainerConfiguration implements
SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
// 获取Spring容器中的所有被@RocketMQMessageListener注解的bean
Map<String, Object> beans = this.applicationContext
.getBeansWithAnnotation(RocketMQMessageListener.class);
// 如果有则依次将bean传进registerContainer()方法执行
if (Objects.nonNull(beans)) {
beans.forEach(this::registerContainer);
}
}
private void registerContainer(String beanName, Object bean) {
// 不重要代码略过,感兴趣的可以自行去看看
// 从bean获取@RocketMQMessageListener注解信息
RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class);
// 使用placeholder类解析注解的属性,这就是为什么可以在注解中
// 使用${}通配符
String consumerGroup = this.environment
.resolvePlaceholders(annotation.consumerGroup());
String topic = this.environment
.resolvePlaceholders(annotation.topic());
// 不重要代码略过,感兴趣的可以自行去看看
// createRocketMQListenerContainer()方法会根据注解的其它属性创建
// DefaultRocketMQListenerContainer类,有兴趣可以自行观看
genericApplicationContext.registerBean(containerBeanName, DefaultRocketMQListenerContainer.class,
() -> createRocketMQListenerContainer(containerBeanName, bean, annotation));
// 这里直接使用Spring上下文类的getBean()方法,此时将会在容器内
// 实例化刚刚创建的DefaultRocketMQListenerContainer类
DefaultRocketMQListenerContainer container = genericApplicationContext.getBean(containerBeanName,
DefaultRocketMQListenerContainer.class);
if (!container.isRunning()) {
try {
// 如果容器尚未执行则调用start()方法,这里面将会调用
// Consumer的start()方法启动消费者
container.start();
} catch (Exception e) {
// 不重要代码略过,感兴趣的可以自行去看看
}
}
// 不重要代码略过,感兴趣的可以自行去看看
}
}
简要说明一下这个类的执行流程和作用:
- 当
RocketMQAutoConfiguration
被自动装填到Spring
容器后,将会使用@Import
注解引入ListenerContainerConfiguration
; - 当被引入的
ListenerContainerConfiguration
类被Spring
容器实例化后将会执行afterSingletonsInstantiated
方法; - 在该方法中将会从
Spring
容器中取出所有被RocketMQMessageListener
注解过的Bean
; - 将
Bean
用DefaultRocketMQListenerContainer
类封装,并注册到Spring
容器中实例化; - 调用
DefaultRocketMQListenerContainer
的start()
方法,启动消费者。
总而言之就是实现了RocketMQListener
接口且被RocketMQMessageListener
注解的Bean
,最终会被封装成DefaultRocketMQListenerContainer
类,在这里面的Consumer
消费消息,并经过一系列处理(诸如转换消息内容)最终调用到Bean
的接口方法实现中。
这样是不是就清晰多了?如果感兴趣推荐自行去看源码,很多疑惑将会在源码中给出答案。