上一篇文章我们介绍了Spring Boot整合Spring AMQP,也对消息confirm确认机制和Return消息机制进行了代码演练,今天我们来聊聊RabbitMQ的多个生产者和多个消费者之间的消费模式,在此前我们需要先来了解RabbitMQ的消息分发机制;
当RabbitMQ某队列拥有多个消费者时,队列收到的消息将以轮询(round-robin )的分发方式发送给消费者。每条消息只会发送给订阅列表里的一个消费者,如何来理解这个轮询分发,我们用一张图来表示;
生产者将消息进行投递到交换机,交换机经过路由将消息转发至Queue队列,当多个消费者订阅了同一个队列的时候,队列将按照轮询来分发,得出上图消费者1 能获取到1-3-5-7-9 的消息,消费者2同理;
很多时候轮询的分发机制能满足我们大部分需求,但在吞吐量较大的应用,该机制还是有一定弊端,因为 RabbitMQ 不管消费者是否消费并己经确认(Basic.Ack) 了消息;试想一下,如果某些消费者任务繁重,来不及消费那么多的消息,而某些其他消费者由于某些原因(比如业务逻辑简单、机器性能卓越等)很快地处理完了所分配到的消息,进而进程空闲,这样就会造成整体应用吞吐量的下降
那么该如何处理这种情况呢?这里就要用到channel.basicQos(int prefetchCount)这个方法, channel.basicQos 方法允许限制信道上的消费者所能保持的最大未确认消息的数量;我们将使用Springboot来实现;
理论了这么多,是时候来一波代码演示了,还是继续我们上个集成项目来;
生产者
@Component
public class One2ManySender implements RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMsg() throws Exception {
rabbitTemplate.setReturnCallback(this);
for (int i = 0; i < 10; i++) {
CorrelationData cd = new CorrelationData("123456789-" + i);
rabbitTemplate.convertAndSend("one2manyExchange", "one2many.send", "测试发送" + i, cd);
/**官方最新文档
从版本2.1开始,该CorrelationData对象具有ListenableFuture您可以用来获取结果的对象,
而不是ConfirmCallback在模板上使用
**/
boolean isAck = cd.getFuture().get(10, TimeUnit.SECONDS).isAck();
//如果ACK true 进行业务处理
if (isAck) {
//获取发送消息时候的 CorrelationData
System.err.println("通过cd.getFuture() 获取ID:" + cd.getId());
} else {
System.err.println("失败处理");
}
}
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText,
String exchange, String routingKey) {
System.err.println("--------------进入Return-------------");
System.err.println("return exchange: " + exchange + ", routingKey: "
+ routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText);
}
}
两个消费者(复制多一个即可)
@Component
public class One2ManyReceiver1 {
/**@Exchange 默认durable=true 故不需要设置 **
*
* @param message
* @param channel
* @throws Exception
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "one2manyQueue",
durable="true"),
exchange = @Exchange(value = "one2manyExchange",durable = "true",
type= "topic",
ignoreDeclarationExceptions = "true"),
key = "one2many.*")
)
@RabbitHandler
public void onMessage(Message message, Channel channel) throws Exception {
System.err.println("RabbitReceiver1 消费端Payload: " + message.getPayload());
Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
//手工ACK
channel.basicAck(deliveryTag, false);
}
}
最后再来一个controller 请求
@RestController
@RequestMapping("/send")
public class SenderController {
@Resource
private One2ManySender one2ManySender;
@GetMapping("/one2many")
@ResponseBody
public String one2many() throws Exception {
one2ManySender.sendMsg();
return "one2many";
}
}
访问请求地址,最后输出如下效果:
从结果可以看出(附带了我们消息确认),消费者是平均接收了消息;证明了我们之前表述的队列收到的消息将以轮询(round-robin )的分发方式发送给消费者
还是一样,我们复制两个生产者 (Many2ManySender1、Many2ManySender2)和两个消费者(Many2ManyReceiver1 、Many2ManyReceiver2)
生产者:
@Component
public class Many2ManySender1 implements RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMsg() throws Exception {
rabbitTemplate.setReturnCallback(this);
for (int i = 0; i < 10; i++) {
CorrelationData cd = new CorrelationData("987654321-" + i);
rabbitTemplate.convertAndSend("many2manyExchange", "many2many.send", "测试发送" + i, cd);
/**官方最新文档
从版本2.1开始,该CorrelationData对象具有ListenableFuture您可以用来获取结果的对象,
而不是ConfirmCallback在模板上使用
**/
boolean isAck = cd.getFuture().get(10, TimeUnit.SECONDS).isAck();
//如果ACK true 进行业务处理
if (isAck) {
//获取发送消息时候的 CorrelationData
System.err.println("通过cd.getFuture() 获取ID:" + cd.getId());
} else {
System.err.println("失败处理");
}
}
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText,
String exchange, String routingKey) {
System.err.println("--------------进入Return-------------");
System.err.println("return exchange: " + exchange + ", routingKey: "
+ routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText);
}
}
消费者
@Component
public class Many2ManyReceiver1 {
/**@Exchange 默认durable=true 故不需要设置 **
*
* @param message
* @param channel
* @throws Exception
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "many2manyQueue",
durable="true"),
exchange = @Exchange(value = "many2manyExchange",durable = "true",
type= "topic",
ignoreDeclarationExceptions = "true"),
key = "many2many.*")
)
@RabbitHandler
public void onMessage(Message message, Channel channel) throws Exception {
System.err.println("Many2ManyReceiver1 消费端Payload: " + message.getPayload());
Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
//手工ACK
channel.basicAck(deliveryTag, false);
}
}
添加一个controller 请求
@GetMapping("/many2many")
@ResponseBody
public String many2many() throws Exception {
many2ManySender1.sendMsg();
many2ManySender2.sendMsg();
return "many2many";
}
我们运行项目,发现会有如下错误!Only one ReturnCallback is supported by each RabbitTemplate
出现这个问题是因为 RabbitTemplate只允许设置一个callback方法,而默认spring中维护的RabbitTemplate是单例的,所以导致多个生产者设置callback时候报错,我们可以通过将RabbitTemplate的作用域设为@Scope,每次bean都是新的,来解决这个问题!
@Bean
@Scope("prototype")
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
return template;
}
我们前面说到了RabbitMQ 不管消费者是否消费并己经确认(Basic.Ack) 了消息;试想一下,如果某些消费者任务繁重,来不及消费那么多的消息,而某些其他消费者由于某些原因(比如业务逻辑简单、机器性能卓越等)很快地处理完了所分配到的消息,进而进程空闲,这样就会造成整体应用吞吐量的下降
那么针对我们业务的需求和服务器的性能我们可以决定某些消费者,RabbitMQ使用channel.basicQos限制其信道上的消费者所能保持的最大未确认消息的数量;
本文主要介绍了RabbitMQ消息分发的机制一对多、多对多的形式进行代码演示 以及消息的限流;大家可以参考演示代码自己敲一便加深印象,谢谢大家~
下一篇:企业中常用的100%可靠性投递的解决方案