RabbitMq生产者和消费者?(三)
前言
前面已经讲清楚了rabbitMq的基本使用和4种交换机类型,rabbitmq就仅仅这些内容?当然不是,以下笔者列举了在学习RabbitMq过程中遇到的猜想和疑问。
交换机队列的关系
- 以下举例仅仅是说明,路由成功到队列的情况,路由不成功当然就不算
- 队列要接收到消息的前提是,必须绑定对应的交换机,并且发送消息到绑定的交换机才能够使队列监听到信息的到来,当然默认交换机除外。
- 当一个消息发送给交换机的时候,交换机会同时发送给绑定的队列,也就是说1个交换机对应着n个队列,1对n的关系,当一条交换机收到消息后会发送给n个绑定的队列,也就是总共会收到n条消息。
- 服务t个节点并不会使交换机发送重复的消息,也就是说不会产生t*n条消息,消息只会被某一个节点消费掉。
生产者和消费者如何去确保消息发送成功和消息接收成功?
Channel
为什么笔者要单独把Channel拿出来讲呢?因为笔者认为,Channel在RabbitMq中实在是太重要了!
- Channel是怎么产生的? 这就要补充一下基础知识:很多服务需要走到中间件需要多个可靠的长连接,TCP就是一个,但是我一个应用跟这个中间件打开多个TCP连接是不可取的,因为消耗系统资源,并且服务器的防火墙非常难维护,所以RabbitMq提供了一个Channel的概念,在共享一个TCP连接的情况下资源复用,用Channel隔开,Channel隔开的话,其实就等于是资源隔离,Channel之间数据并不互通,也不收影响。并且Channel只会存在于有TCP链接的情况下,并不会单独存在,也就是说关闭服务后断开连接Channel自然也关闭
- Channel是如何产生的? 那么什么情况下会产生Channel?Channel过多是否会有问题呢?下面笔者就引用官方的话。 每个通道在客户端上消耗的内存量相对较小。根据客户端库的实现细节,它还可以使用一个专用的线程池(或类似的)来调度消费者操作,因此一个或多个线程(或类似的)。 那么官方都已经说小号内存相对比较小,那么笔者作为莽夫,肯定就不关他啦! 说回Channel是如何产生的呢?刚刚说了很多关于Channel的,共享TCP连接,通过Channel做资源隔离,那么肯定就能想到Channel是通过消费者监听的数量来决定去创建多少个Channel的,也就导致了Channel的资源隔离
@RabbitListener(queuesToDeclare = {@Queue(name = "Headers_ALL_QUEUE", arguments = {@Argument(name = "all-1", value = "value1"), @Argument(name = "all-2", value = "value2")})})
public void handlerAll1(Message message) {
log.info("[Headers_ALL_QUEUE1]:{}", new String(message.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queuesToDeclare = {@Queue(name = "Headers_ALL_QUEUE1", arguments = {@Argument(name = "all-1", value = "value1"), @Argument(name = "all-2", value = "value2")})})
public void handlerAll2(Message message) {
log.info("[Headers_ALL_QUEUE2]:{}", new String(message.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {"FANOUT_QUEUE"})
public void handlerH(Message message, Channel channel) {
System.out.println(channel);
log.info("[消息来了]:{}", new String(message.getBody(), StandardCharsets.UTF_8));
}
当这里有3个消费者的时候启动服务
那么我们如何控制Channel创建数量呢?
#使用rabbitmq的starter自己直接设置就好了
spring.rabbitmq.requested-channel-max=2
那么我们有3个消费者设置成2会怎么样呢?
毫无疑问那就报错了!
消费者消息确认机制
- 根据以上的问题RabbitMq指定了一套协议,根据AMQP 0-9-1协议,RabbitMq是如何把消息从队列删除避免重复消费的呢?消费者在接收和处理消息时,可能在接收或者处理的时候节点崩溃,或者说网络不通的情况,到时消息不能正常去消费。
- 根据以上问题RabbitMq会做两件事情
- 代理向应用程序发送消息(使用basic.deliver或basic.get-ok方法)。
- 应用程序发回确认之后(使用basic.ack方法)。
- 当消息发送到某个节点的应用后,rabbitMq允许节点使用以下3种方法之一去回应这一个消息
- basic.ack用于确认
- basic.nack用于否定确认(这个只有在AMPQ 0-9-1协议才会有)
- basic.reject用于否定确认,但是跟nack有一些区别
- rabbitMq接收到ack或者reject就会去队列删除消息,证明这个消息已经被消费,而应用程序如果使用reject的话就不会去消费消息
- 而basic.nack有什么用呢?rabbitMq允许nack批量确认消息,从而减少网络流量和交互,那么如何使用呢?
/**
* @author Kakki
* @version 1.0
* @create 2021-06-18 17:13
*/
@Component
@Slf4j
public class MqCustomer {
@RabbitListener(queues = {"FANOUT_QUEUE"})
public void handlerH(Message message, Channel channel) {
channel.basicAck(Long deliverTag, boolean multiple);
log.info("[消息来了]:{}", new String(message.getBody(), StandardCharsets.UTF_8));
}
}
- 这个basicAck就是可以实现消息ack和nack,当multiple为true就会使用nack,不了解deliverTag的话最好就使用false,(而reject是没有这个字段的)那么deliverTag又是什么呢?
- deliverTag其实就是当mq预取消息后,ack是一条条消息确认,而nack可以批量确认,当设置deliverTag为9的时候,他就会把9之前未确认的消息全部确认,这也就是nack的批量确认
生产者消息发送确认机制
- 生产者的消息如何正确的不丢失,也就是保证信息能够成功到达mq,在rabbitMq的AMQP 0-9-1协议中采用了两种方式去保证消息不丢失
- 使通道成为事务性的,然后为每条消息或一组消息发布、提交。在这种情况下,交易是不必要的重量级,并将吞吐量降低了 250 倍。
- 使用事务吞吐量会降低250倍,性能不好,所以又引入了一个确认机制,他是模仿消费者确认机制的原理
- RabbitMq默认开启生产者确认机制
- 那么如何开启事务模式呢? 很简单看源码就知道了,这里就不细说了,大家可以看看com.rabbitmq.client.Channel.confirmSelect()方法
小结
这篇文章就仅仅说了生产者和消费者是如何进行确认消息发送成功或者接受成功了。 当然笔者觉得自动确认机制不一定在所有的业务场景都可以用,所以还是要根据业务场景去选择自己的确认机制。