Springboot2.3.1整合ActiveMQ
本文中,将初步探索ActiveMQ
整合其 点对点模式、Topic模式、虚拟主题Topic三种模式
ActiveMQ 安装请查看docker-软件安装篇
一、依赖与Yml配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
Yml配置
spring:
activemq:
#ActiveMQ通信地址
broker-url: tcp://xxx:61616
#账户
user: xxxx
#密码
password: xxxx
这即是最简单的配置,,,咱们一步步踩坑
二、点对点模式
先定义一个公共层对象,用来消费者生产者间消息传输
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Driver implements Serializable {
private Long id;
private String name;
}
定义queue
@Configuration
public class EasyActiveConfig {
@Bean
public Queue easyQueue() {
return new ActiveMQQueue("active_easy_queue");
}
}
生产者
使用 JmsMessagingTemplate 进行消息发送
@Service
public class EasyProvider {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
public void send() {
for (int i = 0; i < 3; i++) {
jmsMessagingTemplate.convertAndSend("active_easy_queue","123123");
}
}
}
消费者
使用 JmsListener 监听队列
@Component
public class EasyConsumer {
@JmsListener(destination = "active_easy_queue")
public void aa(String aa) {
System.out.println("收到简单的消息" + aa);
}
}
测试发送Json字符串 --发现是OK的
咱们再来试试直接发送对象–改造咱们的生产者以及消费者
@Service
public class EasyProvider {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
public void send() {
for (int i = 0; i < 3; i++) {
jmsMessagingTemplate.convertAndSend("active_easy_queue", new Driver((long)i, "小司机"));
}
}
}
@Component
public class EasyConsumer {
@JmsListener(destination = "active_easy_queue")
public void aa(Driver aa) {
System.out.println("收到简单的消息" + aa);
}
}
测试一下:
报错:
Could not convert JMS message; nested exception is javax.jms.JMSException: Failed to build body from content
Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.leilei.common.Driver!
根据错误信息查看官网以及百度,发现ActiveMQ 其默认是不支持直接对象发送的,发送时 实质会将其序列化进行传输,但activemq默认发送对象的包是不安全的,所以,序列化会失败,导致消息发送失败
解决办法:设置包名为安全
OK,再测一波
刚才发送失败的消息,保存到了队列中,下一次队列发送消息时,将之前未被消费的又发了出来
三、Topic模式
topic 主题模式 与点对点模式最大的区别是 其可以进行一对多,多个消费者监听到topic,即可同时收到消息
配置
@Configuration
public class ActiveTopicConfig {
@Bean
public Topic topic() {
return new ActiveMQTopic("lei_topic") ;
}
/**
* springboot默认只配置queue类型消息,如果要使用topic类型的消息,则需要配置该bean
* @param connectionFactory factory.setPubSubDomain(true);
* @return
*/
@Bean
public JmsListenerContainerFactory leiTopicFactory(ConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//这里必须设置为true,false则表示是queue类型 虚拟主题(virtual则必须设为false)
factory.setPubSubDomain(true);
return factory;
}
}
生产者
与简单模式一样,仅仅将之前的queue的名字替换为了topic的名字
@Service
public class ActiveTopicProvider {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
public void sendMessage() {
for (int i = 0; i < 30; i++) {
jmsMessagingTemplate.convertAndSend("lei_topic",new Driver((long)i,"司机"+i));
}
}
}
消费者
我这里定义了两个消费者
concurrency 消费者并发数 ,我这里暂时都定为1 一会定义多个查看区别
@Component
public class ActiveTopicConsumer {
/**
* destination 为定义的topic的名字
* concurrency 消费者并发数
* containerFactory 之前设置的JmsListenerContainerFactory bean 名字
* @param driver
*/
@JmsListener(destination="lei_topic", concurrency = "1",containerFactory = "leiTopicFactory")
public void ListenTopic(Driver driver){
System.out.println(Thread.currentThread().getName()+"消费者1接收到topic消息:" + driver);
}
@JmsListener(destination="lei_topic",concurrency = "1", containerFactory = "leiTopicFactory")
public void ListenTopic2(Driver driver){
System.out.println(Thread.currentThread().getName()+"--消费者--2--接收到topic消息:" + driver);
}
}
测试
嗯?消息未收到?我topic定义的假的???
排查了很久,代码没有问题,于是百度下topic配置 发现 还需要yml配置
修改配置后,再测一波
果然来了。每一个消费者都消费了一条消息 ,我循环发送了 30次消息,每一个消费者都接收到了三十次消息
上边说了消费者并行数 ,咱们将其中一个消费者并行数设置大一点
设为10个
@JmsListener(destination="lei_topic",concurrency = "10", containerFactory = "leiTopicFactory")
public void ListenTopic2(Driver driver){
System.out.println(Thread.currentThread().getName()+"--消费者--2--接收到topic消息:" + driver);
}
圈红的为消费者1 只有一个并行数,圈紫色的为我设置了消费者并行数为10的消费者2,
我们可以看到,消费者2 同时有多条线程去消费消息,也造成了一个重复消费的情况…
到时可以确定:消费者并行数 设置多线程式的消费者,并发消费
问题:我这里造成了重复消费(百度未解决,难道要手动再判断消息是否被消费了??)
四、虚拟主题VirtualTopic
为什么有了topic还要使用 virtualTopic?
我们从上边的例子先来梳理下topic的工作模式:
使用topic方式,那么如果消费端部署的是集群方式,那么每一个都订阅了,在发送消息的时候,集群中的每一个订阅者都有可能收到,但这肯定不会是我们想要的结果.
所以virtualtopic这个东西,这个东西我们可以理解为是queue和topic的结合,使用topic的一对多的广播功能,又满足了消费者在做了集群的时候,只有一个收到,也就是队列的一对一的特效。
配置
需要注意的是 activemq中 并没有 virtualTopic的构造方法,其更像是一个约定,只要topic 名字是 VirtualTopic.xxx 其就会使其成为一个虚拟主题
factory.setPubSubDomain(false); 这一步尤为重要(实质queue模式则就是设置的false),否则消费者收不到消息
敲黑板,划重点了!!!
这也更好地理解了virtual是 是queue和topic的结合
@Configuration
@EnableJms
public class VirtualConfig {
//定义虚拟主题的名字
private static final String VIRTUAL_TOPIC_NAME = "VirtualTopic.Orders";
@Bean("virtualTopicQueue")
public ActiveMQTopic virtualTopicQueue(){
return new ActiveMQTopic(VIRTUAL_TOPIC_NAME);
}
/**
* 特别特别注意配置虚拟主题 需要设为false
* @param connectionFactory
* @return
*/
@Bean(name = "virtualFactory")
public JmsListenerContainerFactory<?> virtualFactory(ConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(false);
factory.setConnectionFactory(connectionFactory);
return factory;
}
}
生产者
@Component
@Slf4j
public class VirtualProvider {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private ActiveMQTopic virtualTopicQueue;
public void sendTopicMessage(String message){
for (int i = 0; i < 5; i++) {
jmsMessagingTemplate.convertAndSend(virtualTopicQueue,message);
}
}
}
消费者
说明下:我这里定义了三个消费者
监听:Consumer.A.VirtualTopic.Orders
Consumer.A.VirtualTopic.Orders
Consumer.B.VirtualTopic.Orders
虚拟主题的消费者监听:必须以此固定格式 Consumer.xxx.虚拟主题名字(VirtualTopic.xxxx) 例如上方的:Consumer.B.VirtualTopic.Orders
设置两个消费者监听Consumer.A.VirtualTopic.Orders 模拟一个消费者集群的情况下,看看当生产者发送消息的时候,多个Consumer.A.VirtualTopic.Orders 是否只有一个消费了,是否避免了消费者集群下,重复消费问题!
@Component
@Slf4j
public class VirtualConsumer {
/**
* 设置Consumer.A. 两个
* @param receiveStr
* @throws JMSException
*/
@JmsListener(destination = "Consumer.A.VirtualTopic.Orders",containerFactory = "virtualFactory" )
public void receiveTopicQueueA1(String receiveStr) throws JMSException {
log.info("Consumer.A1-----Orders:{}",receiveStr);
}
/**
* 设置Consumer.A. 两个
* @param receiveStr
* @throws JMSException
*/
@JmsListener(destination = "Consumer.A.VirtualTopic.Orders",concurrency = "50",containerFactory = "virtualFactory" )
public void receiveTopicQueueA2(String receiveStr) throws JMSException {
log.info("Consumer.A2-----Orders:{}",receiveStr);
}
/**
* 设置Consumer.B. 1个
* @param receiveStr
* @throws JMSException
*/
@JmsListener(destination = "Consumer.B.VirtualTopic.Orders",containerFactory = "virtualFactory" )
public void receiveTopicQueueB(String receiveStr) throws JMSException {
log.info("Consumer.B-----Orders:{}",receiveStr);
}
}
测试:
由于生产者是循环发送了五次。那么预测结果应该是 监听 Consumer.B.VirtualTopic.Orders 会消费五次, 监听 Consumer.A.VirtualTopic.Orders的两个消费者共消费五次
测试结果正确:
可能细心看代码的小伙伴也发现了,我监听 Consumer.A.VirtualTopic.Orders 的第二个消费者设置了最大并行数。。。。但是测试结果虽然有多个线程,但是也避免了重复消费的情况!!
五、总结
点对点模式:
定义queue 然后 jmsMessagingTemplate 发送消息,但仅支持多对多
注意是否直接发送对象 需要直接发送对象需在Yml配置 spring.activemq.packages.trust-all=true
topic模式:
Yml开启 spring.jms.pub-sub-domain=true
定义个 ActiveMQTopic 设置名字 (除VirtualTopic 外无其他讲究)
定义 JmsListenerContainerFactory 注意设置 setPubSubDomain(true);
消费者设置了最大并行数后,多线程消费,但消息被重复消费
消费者集群下,每个消费者都会受到topic传来的消息(消费者集群下使用不太乐观)
virtualTopic 模式:
虚拟主题模式
定义个 ActiveMQTopic 设置名字 (必须是 VirtualTopic.xxx格式)
这一步与topic模式 是反的
定义 JmsListenerContainerFactory 注意设置 setPubSubDomain(false);
消费者监听格式固定 Consumer.xxx.虚拟主题名字(VirtualTopic.xxxx)
设置消费者并行数后,有多线程,不会被重复消费 消费者集群模式下最佳选择
activemq的目前学习就到这里,后续不断更新!!!
附上项目源码:springboot-activemq