RabbitMQ常见问题
1、如何确保消息正确地发送至RabbitMQ?
两种解决方法:
- 通过事务机制
- 事务正常:
- 客户端发送Tx.Select,将信道置为事务模式
- Broker回复Tx.Select-Ok,确认已将信道设置为事务模式
- 在发送完消息后,客户端发送Tx.Commit提交事务
- Broker回复Tx.Commit-Ok确认事务提交
- 回滚:
- 客户端发送Tx.Select,将信道置为事务模式
- Broker回复Tx.Select-Ok,确认已将信道设置为事务模式
- 事务提交之前出现异常,我们可以将其捕获,进而通过执行channel.exRollback方法实现事务回滚
- 事务正常:
- 发送方确认 :
- 信道设置为confirm模式,所有在该信道发送的消息都会被指派一个唯一ID
- 一旦消息被投递到所匹配的队列中,给生产者发送一个Basic.ack消息(包含ID),这样使得生产者知道消息已到达目的地,如果消息和队列是持久化的,那么确认消息会在消息写入磁盘后发出。
- 如果Rabbit因为自身内部错误导致消息丢失,就会发送一条Basic.Nack命令,生产者应用可以在回调函数中处理该Nack命令
public void syncSend(Integer id){ ProducerSyncConfirmMessage message = new ProducerSyncConfirmMessage(); message.setId(id); rabbitTemplate.invoke(new RabbitOperations.OperationsCallback<Object>() { @Override public Object doInRabbit(RabbitOperations operations) { operations.convertAndSend(ProducerSyncConfirmMessage.EXCHANGE, ProducerSyncConfirmMessage.ROUTING_KEY, message); logger.info("[doInRabbit][发送消息完成]"); operations.waitForConfirms(0); // timeout 参数,如果传递 0 ,表示无限等待 logger.info("[doInRabbit][等待 Confirm 完成]"); return null; } }, new ConfirmCallback() { @Override public void handle(long deliveryTag, boolean multiple) throws IOException { logger.info("[handle][Confirm 成功]"); } }, new ConfirmCallback() { @Override public void handle(long deliveryTag, boolean multiple) throws IOException { logger.info("[handle][Confirm 失败]"); } }); }
2、如何确保消息接收方消费了消息?
ack机制,消费者在订阅队列时,可以指定autoAck参数:
- autoAck == false :会等待消费者显示的回复信息后才从内存中删除(先标记,收到确认命令后删除),如果一直没有收到确认命令,并且此消费者已经断开连接,那么RabbitMQ会安排该消息重新进入队列。
- autoAck == true : RabbitMQ会自动把发送出去的消息从内存中删除,不管消费者有没有消费
3、如何避免消息重复消费?
重复消费:保证消息唯一识别性,即使多次发送到消费者,消费者也可以检验是否已消费。
4、消息如何分发?
- 当RabbitMQ队列拥有多个消息者时,队列收到的信息将以轮询的分发方式发给消费者。每条消息都只会发送给订阅列表中的一个消费者。
- 默认情况下,如果有n个消费者,那么RabbitMQ会将第m条消息分发给第m%n(取余)个消费者,RabbitMQ不管消费者是否消费并经确认(Basic.Ack)了消息
如果某些消费者业务繁重,来不及消费这么多消息,而某些消费者由于某些原因很快结束了,进而进程空闲那么造成整体吞吐量下降。可以使用channel.basicQos(int prefetchCount)方法,该方法允许限制信道上的消费者所能保持的最大未确认消息的数量。举例说明:如果订阅消费列表之前,消费端调用了channel.basicQos(5),之后订阅了某个队列进行消费。RabbitMQ会先保存一个消费者的队列,每发送一条消息都会为对应的消费者计数,如果达到所设定的上线。RabbitMQ不会向这个消费者发送消息,待消费者确认消费某条消息之后,计数-1,之后消费者就可以继续接收消息了。
5、消息怎么路由?
生产者发送消息到交换器,交换器根据绑定(RoutingKey 和 BindingKey根据交换器的类型)匹配对应的Queue
Exchange由四个类型:fanout、direct、topic、headers
6、如何确保消息不丢失?
- 生产者发送消息丢失:可以采取生产者confirm模式 ,autoAck == false,如果没有收到Basic.Ack命令需要进行消息补偿:
- 第一种:消息发送前落库并进行状态标记,待收到确认信号再更改状态。定时任务检查超时没有收到确认的消息,进行重发但是要设置重发次数。达到一定次数进行异常处理人工介入
- 第二种:
- 生产者生产两条消息,一条立即发送给下游服务,一条延迟消息给补偿(callback Server)服务
- 下游服务监听到这个消息后,会发送一条消息到callback Server
- callback Server监听到这个消息后,知道刚才有条消息消费成功了,然后把这个持久化到数据库中,当上游服务发送的延迟消息到达callback Server时,就会查询db是否存在对应的消息。如果存在就代表已经被消费了。如果不存在这条记录,那么callback Server就会发送一个RPC请求到上游进行消息重发。
- rabbitmq消息丢失:将队列和消息都持久化(队列:durable == true 、消息:deliveryMode == 2),但是RabbitMQ不会为每条消息进行磁盘同步,可能先写入缓存(三个策略:),待缓存满了之后再进行写磁盘操作。那么这段时间服务器宕机也会带来消息的问题,解决的方案就是镜像队列,如果master节点宕机,可以自动切换到从节点,这样有效保证了高可用性。
- 消费者丢失消息:消费者confirm模式,autoAck == false
7、保证消息队列的高可用?
开启镜像集群模式,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。
8、保证消息的顺序性?
-
拆分多个queue,每个queue一个consumer,这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式取消费。
-
或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理