1. Queue 点对点
一条消息只能被一个消费者消费,且是持久化消息-当没有可用的消费者时,该消息保存直到被消费位置;当消息被消费者收到但不响应时,该消息会一直保留或会转到另一个消费者,这是在有多个消费者的情况。当一个Queue有多个可用消费者的时候,可以在这些消费者中起到负载均衡的作用。
2. Topic 发布-订阅者模式
一条消息发布时,所有订阅者都会收到,Topic有2种模式,Nondurable subscription(非持久化订阅)和durable subscription(持久化订阅-每个持久化订阅者都相当于一个持久话的queue的客户端),默认是非持久化订阅。
- 持久化:消息产生后,会保存到文件或数据库中,直到消息被消费,Queue的持久化消息。
- 非持久化:消息不会保存,当没有消费者消费时,就会被丢弃

3. 代码
- pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- application.properties
server.port=9090 #springboot中的activemq配置,springboot会根据这些自动建立连接工厂 spring.activemq.broker-url=tcp://localhost:61616 spring.activemq.non-blocking-redelivery=false spring.activemq.send-timeout=0 #使用Topic时设置为true #使用Queue时设置为false spring.jms.pub-sub-domain=true spring.activemq.user=admin spring.activemq.password=admin #activemq配置类用到的配置项,自定义连接工程,为了实现Virtual Topic activemq.url=tcp://10.195.229.8:61616 activemq.username=admin activemq.password=admin activemq.virtual.topic=VirtualTopic.Topic1 activemq.virtual.topic.A=Consumer.A.VirtualTopic.Topic1 activemq.virtual.topic.B=Consumer.B.VirtualTopic.Topic1
- activemq配置类,可以配置多个activemq服务器的连接工厂
springboot会根据application中关于activemq的配置自动生成相关的连接工厂,但是这个连接工厂没法实现虚拟的Topic
因为springboot会根据 spring.jms.pub-sub-domain 当为true生成的是topic的连接工厂,为false生成的是queue的连接工厂
package com.example.demo.config;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.scheduling.annotation.EnableAsync;
import javax.jms.ConnectionFactory;
/**
* @author
* @version V1.0
* @modify: {原因}
*/
@Configuration
@EnableJms
@EnableAsync
public class JmsConfig {
@Value("${activemq.url}")
private String url;
@Value("${activemq.username}")
private String username;
@Value("${activemq.password}")
private String password;
private Logger logger = LoggerFactory.getLogger(JmsConfig.class);
@Bean(name = "firstConnectionFactory")
public ActiveMQConnectionFactory firstConnectionFactory(){
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(url);
connectionFactory.setUserName(username);
connectionFactory.setPassword(password);
return connectionFactory;
}
// @Bean(name = "firstJmsTemplate")
// public JmsMessagingTemplate getFirstJmsTemplate(@Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory) {
// JmsMessagingTemplate template = new JmsMessagingTemplate(connectionFactory);
// return template;
// }
@Bean(name="firstTopicListener")
public DefaultJmsListenerContainerFactory firstTopicListenerFactory(@Qualifier("firstConnectionFactory")ConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
@Bean(name="firstQueueListener")
public DefaultJmsListenerContainerFactory firstQueueTopicListenerFactory(@Qualifier("firstConnectionFactory")ConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
return factory;
}
}
- 生产者类
package com.example.demo.VirtualTopic;
import org.apache.activemq.command.ActiveMQTopic;
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.jms.annotation.JmsListener;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Topic;
@RestController
public class ProducerVirtualTopicController {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@RequestMapping(value = "/sendMessageVirtual",method = RequestMethod.GET)
public void sendMessageVirtual(){
for(int i =0;i<5;i++){
//虚拟Topic具有自己的命名规则
ActiveMQTopic topic = new ActiveMQTopic("VirtualTopic.Topic1");
this.jmsMessagingTemplate.convertAndSend(topic,"sss");
}
}
}
- 消费者
package com.example.demo.VirtualTopic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.jms.JMSException;
@Component
public class Consumer {
private static final Logger logger = LoggerFactory.getLogger(Consumer.class);
@JmsListener(destination="Consumer.B.VirtualTopic.Topic1",containerFactory = "firstQueueListener")
@Async
public void receiveVTopicB(String message) throws JMSException{
logger.debug("VTopic B ===== "+ message);
System.out.println("VTopic B ======="+ message);
try{
Thread.sleep(500L);
}catch(InterruptedException e){
e.printStackTrace();
}
}
@JmsListener(destination="Consumer.A.VirtualTopic.Topic1",containerFactory = "firstQueueListener")
@Async
public void receiveVTopicA1(String message) throws JMSException {
logger.debug("VTopic A1 ===== "+ message);
System.out.println("VTopic A1 ======="+ message);
try{
Thread.sleep(500L);
}catch(InterruptedException e){
e.printStackTrace();
}
}
@JmsListener(destination="Consumer.A.VirtualTopic.Topic1",containerFactory = "firstQueueListener")
@Async
public void receiveVTopicA2(String message)throws JMSException{
logger.debug("VTopic A2 ===== "+ message);
System.out.println("VTopic A2 ======="+ message);
try{
Thread.sleep(500L);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
启动springboot工程,调用接口 http://localhost:9090/sendMessageVirtual 可以看到A1 A2一共消费到5条消息,而B也消费到了5条消息

4. 总结
- 应用场景
某个程序被部署在多台服务器上,就有了多个相同的程序,但是要求这些相同的程序每次只有一个能接收到消息,VirtualTopic就是为了解决这个问题,对于生产者来说将消息发送到topic中,对于这些部署在多个服务器上的相程序,它们每一个都是在消费同一个queue,而其他部署在服务器上的程序依然在监听的是topic.
- Virtual Topic命名规则
Topic命名: VirtualTopic.xxx
消费者命名: Consumer.yyy.VirtualTopic.xxx
转自 https://www.jianshu.com/p/a924c30554ca