RabbitMq基本组件
一、 channel 信道:
信道是生产消费者与rabbit通信的渠道,生产者publish或是消费者subscribe一个队列都是通过信道来通信的。信道是建立在TCP连接上的虚拟连接,什么意思呢?就是说rabbitmq在一条TCP上建立成百上千个信道来达到多个线程处理,这个TCP被多个线程共享,每个线程对应一个信道,信道在rabbit都有唯一的ID ,保证了信道私有性,对应上唯一的线程使用。
怎么理解?和数据库连接池的连接类似。TCP连接不关闭,支持多次和mq服务端通信。
疑问:为什么不建立多个TCP连接呢?原因是rabbit保证性能,系统为每个线程开辟一个TCP是非常消耗性能,每秒成百上千的建立销毁TCP会严重消耗系统。所以rabbitmq选择建立多个信道(建立在tcp的虚拟连接)连接到rabbit上。
类似概念:TCP是电缆,信道就是里面的光纤,每个光纤都是独立的,互不影响。
二、exchange 交换机和绑定routing key
exchange的作用就是类似路由器,routing key 就是路由键,服务器会根据路由键将消息从交换器路由到队列上去。
exchange有多个种类:direct,fanout,topic,header(非路由键匹配,功能和direct类似,很少用)。前三种类似集合对应关系那样,(direct)1:1,(fanout)1:N,(topic)N:1
direct: 1:1类似完全匹配
fanout:1:N 可以把一个消息并行发布到多个队列上去,简单的说就是,当多个队列绑定到fanout的交换器,那么交换器一次性拷贝多个消息分别发送到绑定的队列上,每个队列有这个消息的副本。
ps:这个可以在业务上实现并行处理多个任务,比如,用户上传图片功能,当消息到达交换器上,它可以同时路由到积分增加队列和其它队列上,达到并行处理的目的,并且易扩展,以后有什么并行任务的时候,直接绑定到fanout交换器不需求改动之前的代码。
topic N:1 ,多个交换器可以路由消息到同一个队列。根据模糊匹配,比如一个队列的routing key 为*.test ,那么凡是到达交换器的消息中的routing key 后缀.test都被路由到这个队列上。
三、结合信道、交换器和路由键到队列
总结几点重要知识:1.信道才是rabbit通信本质,生产者和消费者都是通过信道完成消息生产消费的。2.交换器本质是一张路由查询表(名称和队列id,类似于hash表),这是一个虚拟出来的东西,并不存在真实的交换器。
消息的生命周期:生产者生产消息A 交由信道,信道通过消息(消息由载体和标签)的标签(路由键)放到交换器发送到队列上(其实就是查询匹配,一旦匹配到了规则,信道就直接和队列产生连接,然后将消息发送过去)
mq消息可靠性分析
如何保证mq中的消息稳定可靠地被使用消费?
1 消息从产生到被消费经过以下几步:
1 消息生产者产生发送到exchang
2 交换机根据路由规则将消息转发到对应的队列上
3 消息在队列上存储
4 消费者consumer订阅队列queue进行消费
如何保证上诉4步的可靠性
一消息从生产者发出到交换机需要后 交换机要返回确认消息
以保证 生产者感知到消息是否正确发送到交换器中,如果失败,生产者可以进一步处理重新发送。
确认机制
1.开启事务
RabbiMQ客户端与事务机制相关的方法有三个:
channel.txSelect
用于将当前信道转换为事务模式。
channel.txCommit
用于提交事务
channel.txRollback
用于事务回滚
通过select方法开启事务后便可以发消息到RabbitMQ 如果事务提交成功,那么消息一定到了MQ中,如果事务提交之前异常报错,便可以将其捕获,通过channel.txRollBack方法回滚。注意这里的事务与数据库中事务的区别以及两者共用时的情况,见后面补充A两种事务。
缺点:事务机制可以确保消息一定提交到了MQ中,或回滚重发,但使用事务机制会吸干MQ的性能。
2.发送方确认机制(publisher confirm)
生产者将信道设置为确认模式,一旦信道进入confirm模式,所有在该信道发布的消息都会被指派一个唯一的ID,一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认Basic.Ack给生产者包含消息的唯一ID,这就使得生产者确认消息正确到达目的地了。
RabbitMq回传的生产者确认消息中的deliveryTag包含了确认消息的序号,此外RabbitMQ也可以设置channel.basicAck方法中的multiple参数,表示这个序列号之前的消息都得到了处理。
相比于事务机制在一条消息发送后会使得发送端阻塞,发送确认机制最大的好处是它是异步的,一旦发送一条消息,生产者可以在等待信道返回确认的同时发送下一条消息,当消息得到最终确认后,生产者可以通过回调处理消息,而因为错误导致消息丢失,信道会返回一条nac看(Basic.Nack)命令,生产者通可以通过回调处理它。
具体方法:
channel.confirmSelect
即Confirm.Select命令将信道设置为confirm模式。所有被发送的消息都会被ack或nack一次,但消息被confim的快慢没有任何保证。
异步的确认机制虽然提高了性能,但也存在隐患,因为异步就是一种事后发现机制。这一点见补充B异步确认与自动重试。
注意:
事务机制和publisher confirm机制两者是互斥的,不能共存。
若将已开启事务的信道再设置为publisher confirm模式,RabbitMQ会报错:{amqp_error, precondition_failed, "cannot switch from tx to confirm mode", 'confirm.select'}
若将已开启publisher confirm模式的信道再设置事务模式的话,RabbitMQ会报错{amqp_error, precondition_failed, "cannot switch from confirm to tx mode", 'tx.select' }
事务机制和publisher confirm模式可以确保消息能够正常的发送到RabbitMQ的交换器,但如果交换器没有能够正确的匹配到队列,消息也会丢失。
这就需要下面将要讲的其他机制进行一把骚操作来确保可靠性。
二 交换机将消息路由到队列时的可靠性机制
路由失败返回生产者
channel.basicPublish
中有两个重要的参数mandatory(强制),和immediate(立刻),两者都可以将不可达目的地的消息返回给生产者。
RabbitMQ去掉了对immediate的参数支持,因为它会影响到镜像队列的性能,增加代码复杂性,建议用TTL和DLX方法替代。
当mandatory设置为true后,当消息无法被交换器根据自身类型和路由键路由到某个队列的话,RabbitMQ会调用Basic.Return命令将消息返回给生产者。
生产者可以通过调用channel.addReturnListener来添加ReturnListener的监听实现,代码为:
channel.basicPublish(EXCHANGE_NAME, "", true, MessageProperties.PERSISTENT_TEXT_PLAIN, "mandatory test".getBytes());
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP
.BasicProperties basicProperties, byte[] body) throws IOException {
String message = new String(body);
System.out.println("Basic.Return返回的结果是:" + message);
}
});
上面代码中生产者没有成功的将消息路由到队列,此时RabbitMQ会通过Basic.Return返回“mandatory test”这条消息,之后生产者客户端通过ReturnListener监听到了这个事件,上面代码的最后输出应该是“Basic.Return返回的结果是:mandatory test”。
生产者拿到路由失败的消息可以重试或进行其他处理。
当mandatory设置为false后,这样的消息则会被直接丢弃。
路由失败存储起来
此外RabbitMQ提供了备份交换器AE(Alternate Exchange)可以将不能被l路由的消息存储起来而不返回给生产者。
设置mandatory监听实现的代码较为复杂,可以使用备份路由器来避免消息丢失。
再在需要的时候去处理这些消息。
可以在声明交换器时调用channel.exchangeDeclare
方法时添加alternate-exchange参数来实现,也可以通过设置策略的方式实现,谦和的优先级更高会覆盖后面policy的设置。路由策略设置见补充C。
备份交换器和普通路由器没有太大区别,被重新发送到备用路由器的路由键和从生产者发出的路由键相同,备用交换器建议设置为fanout,且可以将一个备份路由器作为所有路由器的AE。但AE要确保绑定好了队列。如果mandatory参数和备份交换器一起使用那么mandatory失效。
这样一来确保了第二部从路由器到队列的可靠性。
三消息存入队列之后的可靠性保障
持久化可以提高队列的可靠性,以防在异常(重启,关闭,宕机)下的数据丢失。
消息持久化与队列持久化
队列的持久化是在声明队列时,将durable设置为true实现的,若队列不设置持久化,那么当MQ服务重启时,相关队列的元数据都会丢失,队列中的数据也会丢失。
队列的持久化能保证本身的元数据不会因为异常丢失,但不能保证存储的消息不丢失。
消息的持久化,需要通过将消息的投递模式BasicProperties中的deliveryMode属性设置为2即可实现消息的持久化。
队列和消息的持久化都设置了,MQ服务重启后消息仍然存在,单独设消息持久化,队列会在重启后丢失,进而消息丢失。
单独设队列持久化,重启后队列存在但消息会丢失。
消息进入MQ后需要一段时间才能存入磁盘中。MQ不为每条消息同步磁盘,可能仅仅保存到操作系统缓存而不是物理缓存中,若在期间出现宕机,重启等异常情况,消息还没有落盘,消息也会丢失。
注意
在第一步中的事务机制或publisher confim机制,服务端返回是在消息落盘之后执行的,可以进一步提高消息的可靠性。
这里的落盘返回并不和上面第一部说的第一步只到exchange矛盾,没有绑定队列就直接返回 绑定了就落盘返回 相当于sync执行一个方法,没有绑定就是空方法,绑定了就是实方法
镜像队列
镜像队列相当于提供了副本,即多副本保证高可用HA。当主节点挂了,自动切到子节点,除非整个集群挂了,可以保证高可靠性。
四 消费者消费消息的可靠性
要确保消费者完成了对消息的处理后再从队列中删除消息,队列需要得到消费者消费的确认消息,若消费者消费失败则重新入队。
消息确认机制 message acknowledgement
消费者在订阅队列时可以指定autoAck参数。
当ack为false时,RabbitMQ会等待消费者显示的回复了信号后才从内存或磁盘中删除(先打上标记 之后再集中删除)
当ack为false时RabbitMq会把自动发送出去的消息置为确认,然后删除,不管消费者是否真正的消费了这些消息。
采用消息确认机制后,RabbitMQ会一直等待持有消息的消费者显示的调用Basic.ACK命令为止,消费者有足够时间处理消息。
而此时RabbitMQ中的消息分为两部分:一部分是等待投递的消息,另一部分是已经投递给消费者但还没有收到确认消息的消息,若一直没有收到确认消息且此消息的消费者已经断开连接那么RabbitMQ会安排消息重新入队等待投递给笑一个消费者。
RabbitMQ不会为未确认的消息设置过期时间,他判断是否需要重新投递给现在的唯一依据时该消息的消费者链接是否已经断开,未确认的消息可以存在很久。
若消息消费失败可以调用Basic.Reject或Basic.Nack来拒绝当前消息,若只是拒绝那么消息会丢失,若将相应的requeue参数设置为true,那么RabbitMQ会重新将这条消息插入队列,重新投递;若设置requeue参数设置为false,RabbitMQ会把消息立即从队列中移除不再发送。
注意requeue的消息会存入队列头部,可以快速的又被发送给消费者,若消费者又不能正常的消费而又requeue就会加入一个无尽的循环。
因此出现无法正常消费的消息市不要采用requeue发生来确保可靠性,而是重新投递到新的队列中比如设置死信队列,可以确保消息不丢失而避免死循环。
对于死信队列可以用另外的方式消费,找出问题。