ActiveMQ概念:
- Queue - Point-to-Point (点对点)
一条消息只能被一个消费者消费, 且是持久化消息 - 当没有可用的消费者时,该消息保存直到被消费为止;当消息被消费者收到但不响应时(具体等待响应时间是多久,如何设置,暂时还没去了解),该消息会一直保留或会转到另一个消费者当有多个消费者的情况下。当一个Queue有多可用消费者时,可以在这些消费者中起到负载均衡的作用。 - Topic - Publisher/Subscriber Model (发布/订阅者)
一条消息发布时,所有的订阅者都会收到,topic有2种模式,Nondurable subscription(非持久订阅)和durable subscription (持久化订阅 - 每个持久订阅者,都相当于一个持久化的queue的客户端), 默认是非持久订阅。
- 持久化:消息产生后,会保存到文件/DB中,直到消息被消费, 如上述Queue的持久化消息。默认保存在ActiveMQ中:%ActiveMQ_Home%/data/kahadb
- 非持久化:消息不会保存,若当下没有可用的消费者时,消息丢失。
点对点(PTP)消息通信模型:
也可称之为队列Queue
模式,特定的一条消息只能被一个消费者消费。生产者将消息发送到指定的Queue当中,Broker(中间件)针对消息是否需要持久化进行持久化存储后通知消费者进行处理,消费者处理完毕后发送一个回执(Acknowledge)给Broker,Broker认为该消息已被正常消费,于是从持久化存储中删除该条消息,回执的发送逻辑内嵌在MQ的API中,无需主动调用。消费者通常可以通过两种方式获取新消息:Push和Pull。Push方式:由ActiveMQ收到消息后主动调用消费者的新消息通知接口,需要消耗ActiveMQ宝贵的线程资源,同时消费者只能被动等待消息通知。Pull方式:由消费者轮询调用 ActiveMQ API 去获取消息,不消耗ActiveMQ 线程,消费者更加主动,虽然消费者的处理逻辑变得稍稍复杂。两种方式的根本区别在于线程消耗问题,由于ActiveMQ 的线程资源相对客户端更加宝贵,Push方式会占用ActiveMQ 过多的线程从而难以适应高并发的消息场景。同时当某一消费者离线一段时间再次上线后,大量积压消息处理会消耗大量ActiveMQ 线程从而拖累其它消费者的消息处理,所以Pull方式相对来说更好(Kafka消息中间件已经抛弃了PUSH模式,全面拥抱PULL模式)。
发布/订阅模式(Pub/Sub):
也可称之为主题Topic模式,特定的一条消息可以被多个消费者所接收,只要消费者订阅了某个主题。消息生产者(发布者)将消息发送到某个称为主题(Topic)的虚拟通道中,Topic可以被多个消费者订阅,因此该模式类似于广播的方式。发布/订阅模式采用PUSH的方式传送消息,Subscriber只需保持在线即可。Subscriber分为临时性的和持久性的,当订阅者离线时,ActiveMQ 会为持久性的Sub持久化消息,当Sub恢复时会重新收到消息。但是既然采用Pub/Sub模式就表明允许部分消费者接收不到消息,所以通常会采用临时性的Subscriber而不是持久性的。
默认只能发送和接收queue消息
测试效果:
有两个相同的项目,A与B
通过A发送消息,A接收到消息
通过B发送项目,B接收到消息,
如果把A项目的接收消息功能关闭,通过A发送消息,B接收消息
Queue 与Topic 自由切换
如果要发送和接收topic消息,需要在application.properties文件中加入:
spring.jms.pub-sub-domain=true
新建一个JMS的配置类:
@Configuration
public class JmsConfig {
public final static String TOPIC = "springboot.topic.test";
public final static String QUEUE = "springboot.queue.test";
@Bean
public Queue queue() {
return new ActiveMQQueue(QUEUE);
}
@Bean
public Topic topic() {
return new ActiveMQTopic(TOPIC);
}
// topic模式的ListenerContainer
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}
// queue模式的ListenerContainer
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerQueue(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}
}
消息发送者
@RestController
@RequestMapping(value = "/active_mq2/producer")
public class ActiveMQProducerController2 {
@Autowired
private JmsMessagingTemplate jmsTemplate;
@Autowired
private Topic topic;
@Autowired
private Queue queue;
@RequestMapping("/send_topic")
public String sendTopic(HttpServletRequest request) {
jmsTemplate.convertAndSend(topic, request.getParameter("msg"));
return "msg发送成功";
}
@RequestMapping("/send_queue")
public String sendQueue(HttpServletRequest request) {
jmsTemplate.convertAndSend(queue, request.getParameter("msg"));
return "msg发送成功";
}
}
消息接收者
@Component
public class ActiveMQConsumer2 {
@JmsListener(destination = JmsConfig.TOPIC, containerFactory = "jmsListenerContainerTopic")
public void onTopicMessage(String msg) {
System.out.println("TOPIC==============msg===============" + msg);
}
@JmsListener(destination = JmsConfig.QUEUE, containerFactory = "jmsListenerContainerQueue")
public void onQueueMessage(String msg) {
System.out.println("QUEUE==============msg===============" + msg);
}
}