我们都知道topic消息是给所有的接收者都会发送一份消息的,但是当消息的数量超过一定数据后,单一的一个消费者是会出现消费瓶颈的,在jms2.0中提供了createSharedConsumer共享消费者的方式来实现负载均衡,降低单个消费者出现的瓶颈,但是在目前的activemq中只实现了jms1.1的支持,那么我们该怎么实现消费者的负载均衡呢?
在activemq中提出了一个virtualTopic的方式来实现负载均衡,我们都知道queue中的消息只能发送给一个消费者进行消费,因此在activemq中就借用了这种思想,它通过将发送给virtualTopic中的消息转发给queue然后实现负载均衡
官方文档路径是 https://activemq.apache.org/virtual-destinations,我们首先看下在activemq服务器的配置如下
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<!--
name:主题名,可以是通配符
prefix:队列的前缀
selectorAware:表示从Topic中将消息转发给Queue时,是否关注Consumer的
selector情况。如果为false,那么Topic中的消息全部转发给Queue,否则只会转发匹配Queue
Consumer的selector的消息
-->
<virtualTopic name="virtualTopic.>" prefix="virtualTopicConsumers.*." selectorAware="false"/>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
通配符说明:https://activemq.apache.org/wildcards
ActiveMQ 的queue和topic名称支持通配符,通过通配符可以增强queue和topic的功能
. 用于分隔path名称
* 用于匹配任何path名称
> 用于递归匹配以xx名称开头的任何目标
示例:
com.study.*.mq 匹配com.study.a.mq,不匹配com.study.a.b.mq
com.> com.study.a.mq和com.study.a.b.mq都匹配
通配符可用于生产和消费者,在生产者中使用通配符时,消息将发送到所有匹配的目标上;在消费者中使用通配符时,将接收所有匹配的目标的消息。
我们实现的代码是结合了springboot来实现的,直接引入spring-boot-starter-activemq即可,生产端代码如下
/**
* 负载均衡测试
* @return
*/
@GetMapping("/testVirtualTopicMQ")
public BaseResponse testVirtualTopicMQ(){
BaseResponse baseResponse = new BaseResponse();
baseResponse.setCode(ResponseCode.SUCCESS);
baseResponse.setMsg("测试virtualTopic");
try {
OrderDeleteDTO orderDeleteDTO = new OrderDeleteDTO();
orderDeleteDTO.setOrderId("5");
orderDeleteDTO.setUserType("01");
for (int i = 0;i < 10;i++){
topicJmsTemplate.convertAndSend("virtualTopic.topic", orderDeleteDTO,message -> {
message.setStringProperty("name","virtualTopic");
return message;
});
/*topicJmsTemplate.execute((session, producer) -> {
Message message = topicJmsTemplate.getMessageConverter().toMessage(orderDeleteDTO, session);
// 添加property属性,因为header为固定的
message.setStringProperty("name","virtualTopic");
Destination destination = session.createTopic("virtualTopic.topic");
producer.send(destination,message);
return null;
});*/
}
} catch (Exception e) {
log.error("测试virtualTopic错误:{}", e);
baseResponse.setCode(ResponseCode.SERVER_ERROR);
baseResponse.setMsg("服务器错误");
}
return baseResponse;
}
消费端的代码如下:
@JmsListener(destination = "virtualTopicConsumers.virtualTopic1.virtualTopic.topic")
public void testVirtualTopicMQ1(OrderDeleteDTO orderDeleteDTO,
@Header(value = "name") String name) {
System.out.println("testVirtualTopicMQ1:" + orderDeleteDTO + ":" + name);
}
@JmsListener(destination = "virtualTopicConsumers.virtualTopic1.virtualTopic.topic")
public void testVirtualTopicMQ1_1(OrderDeleteDTO orderDeleteDTO,
@Header(value = "name") String name) {
System.out.println("testVirtualTopicMQ1_1:" + orderDeleteDTO + ":" + name);
}
@JmsListener(destination = "virtualTopicConsumers.virtualTopic2.virtualTopic.topic")
public void testVirtualTopicMQ2(OrderDeleteDTO orderDeleteDTO,
@Header(value = "name") String name) {
System.out.println("testVirtualTopicMQ2:" + orderDeleteDTO + ":" + name);
}
@JmsListener(destination = "virtualTopicConsumers.virtualTopic2.virtualTopic.topic")
public void testVirtualTopicMQ2_1(OrderDeleteDTO orderDeleteDTO,
@Header(value = "name") String name) {
System.out.println("testVirtualTopicMQ2_1:" + orderDeleteDTO + ":" + name);
}
在这里一定要注意的是activemq的配置文件prefix的配置以及消费者的订阅路径,不然很容易出错。