rabbitmq
1.使用rabbitmq有什么好处?
解耦:解耦合,系统A在代码中直接调用系统B和C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!
异步:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
削峰:并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常
2.RabbitMQ 概念里的 channel、exchange 和 queue 是逻辑概念,还是对应着进程实体?分别起什么作用?
3.vhost 是什么?起什么作用?
vhost可以理解为虚拟broker,即mini-RabbitMQ server。其内部含有独立的queue、exchange和binding等,但最最重要的是,其拥有独立的权限系统,可以做到vhost范围的用户控制。当然,从rabbitmq的全局角度,vhost可以作为不同权限隔离的手段。
4.消息基于什么传输?
由于tcp连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ使用信道的方式传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制。
5.rabbitmq有哪几种队列的形式?
1.点对点(简单)的队列
2.工作(公平性)队列模式
3.发布订阅模式(fanout)
思路解读(重点解读):
1)一个生产者,多个消费者
2)每一个消费者都有自己的一个队列
3)生产者没有直接发消息到队列中,而是发送到交换机
4)每个消费者的队列都绑定到交换机上
5)消息通过交换机到达每个消费者队列
该模式就是Fanout Exchange(扇形交换机)将消息路由给绑定到它身上的所有队列
以用户发邮件案例讲解
注意:交换机没有存储信息功能,如果消息发送到没有绑定消费队列的交换机,
4.路由模式Routing
生产者发送消息到交换机并指定一个路由key,消费者队列绑定到交换机时要制定路由key(key匹配时就能接受消息,key不匹配就不能接受消息)
例如:wo我们可以把key设置为insert,那么消费者队列key指定包含insert才可以接受消息,消费者队列key定义
为update或者delete就不能接受消息。很好的控制了更新,插入和删除的操作。
采用交换机Direct Exchange(直连交换机)模式
5.通配符模式Topics
说明:此模式是在路由key模式的基础上,使用了通配符来管理消费者接受消息
使用Topic Exchange(主题交换机)模式
6.如何保证消息正确地发送至RabbitMQ
RabbitMQ使用发送方确认模式,确保消息正确地发送到RabbitMq。
发送方确认模式:将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含唯一消息ID). 如果RabbitMQ发生内部错误而导致消息丢失,会发送一条nack(not acknowledged,未确认) 消息。发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来确认消息。
7.如何保证消息接收方消费了消息?
接收方消费确认机制:消费者接受每一条消息后都必须进行确认(消息接受和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMq仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMq给了Consumer足够长的时间来处理消息。
下面罗列几种特殊情况:
1).如果消费者接收到消息,在确认之前断开了连接或者取消订阅,RabbitMq会认为消息没有被分发,然后重新发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要根据bizid去重)
2).如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。
8.如何避免消息重复投递或重复消费?
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;在消息消费时,要求消息体中必须有一个bizid(对于同一业务全局唯一,如支付id、订单id、帖子id等)作为去重和幂等的依据,避免同一条消息被重复消费。
这个问题针对业务场景来答分以下几点:
1).比如,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
2).再比如,你拿到这个消息做redis的set操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本身就算幂等操作。
3).如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费者记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将
9.如何解决丢数据的问题?
1).生产者丢数据
生产者的消息没有投递到MQ中怎么办?从生产者弄丢数据这个角度来看,RabbitMq提供transaction和confirm模式来确保生产者不丢消息。
transaction机制就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送的过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则是提交事务(channel.txCommit()).
然而缺点就是吞吐量下降了。因此,生产上使用confirm模式居多。一旦channel进入confirm模式,所有在该信道上发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitmq就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了.如果rabbitMq没能处理该消息,则会发送一个Nack消息给你你可以进行重试操作。
2).消息队列丢数据
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitmq阵亡了,那么生产者收不到ack信号,生产者会自动重发。
那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步
(1)将queue的持久化标识durable设置为true,则代表是一个持久化的队列
(2)发送消息的时候将deliveryMode=2
这样设置以后,rabbitmq就算挂了,重启后也能恢复数据。在消息还没有持久化到硬盘时,可能服务已经死掉,这种情况可以通过引入mirrored-queue即镜像队列,但也不能保证消息百分之百不丢失(整个集群都挂掉)
3).消费者丢数据
启动手动确认模式可以解决这个问题
1)自动确认模式,消费者挂掉,待ack的消息回归到队列中。消费者抛出异常,消息会不断的被重发,直到处理成功。不会丢失消息,即便服务挂掉,没有处理完的消息会重回队列,但是异常会让消息不断重试。
2)手动确认模式,如果消费者来不及处理就死掉,没有响应ack时会重复发送一条消息给其他消费者;如果监听程序处理异常了,且未对异常进行捕获,会一直重复接受消息,然后抛异常;如果捕获了异常,但是没有在finally里ack,也会一直重复发送消息(重试机制)。
3)不确定模式,acknowledg=”none” 不使用确认机制,只要消息发送完成会立即在队列移出,无论客户端异常还是断开,只要发送完成就移出,不会重发。
补充:要弄清楚哪些是可以恢复的异常,哪些是不可以恢复的异常,在消费者代码中捕获异常,并记录日志表或者放入死信队列。可恢复的异常,那么放入业务队列中重试。
注意:自动确认模式下,消息处理成功,消费者才会去获取下一笔消息;消息处理抛出异常,那么将会消息重回队列。
10.消费者消费消息时的手动确认和自动确认的区别,优缺点是什么?
手动确认需要手动去签收,自动确认只要不发生异常就行。如发送异常手动自动都会默认回到队列头,
如果该异常是不可恢复异常,则会导致消息堆积。
11.消息堆积怎么解决?
1).消费小于生产导致,可以多加些消费者或者优化消费者的时间
2).可能是因为消费者异常导致消息堆积,将重新回到队列的消息手动插入到队列尾部
12.消费者出现异常怎么办?
如果是可恢复的异常则不需要处理,则重新回到队列重推,
如果是不可恢复的异常,则把该消息手动拒收,
会存到死信队列。或者将该消息拒收并且丢弃,然后存到日志
13.死信队列和延时队列的使用?
死信队列:
1)消息被拒绝
2)消息过期了
3)队列达到最大的长度
过期时间:
在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信消息。
14.使用消息队列有什么缺点?
系统稳定性降低:本来系统只要运行的好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了,因此,系统可用性降低
系统复杂性增加:要多考虑很多方面的问题,比如一致性的问题、如何保证消息不被重复消费,如何保证消息的可靠传输。因此,需要考虑的东西更多,系统复杂性增大。