RabbitMQ之四----问题与应用

1、消息发送消费流程

在这里插入图片描述

  1. 生产者将消息生产出来,并将消息发送到 RabbitMQ Server 上,即我们发到 RabbitMQ 中的消息,会首先置于 RabbitMQ Server
  2. RabbitMQ Server 根据客户端所发来的连接请求,判断将消息传递到哪个 Virtual Host 中,如果我们在连接 RabbitMQ Server 时,没有设置要连接的 Virtual Host 地址,则 RabbitMQ Server 会将我们的消息传递到地址为 “/” 的 Virtual Host 中;
  3. 在将消息传递到对应的 Virtual Host 中后,Virtual Host 会继续解析我们的连接请求,并在这一步解析出我们需要的 Exchange 的类型,以及 Channel 的名称,Queue 的名称,以及消息和 Exchange 之间是否有 routing_key ,Channel 和 Queue 之间是否有 bidding_key 这些信息
  4. Virtual Host 会根据解析出来的这些信息,将消息和 Exchange 进行匹配,相应的,Exchange 也会和对应的 Channel 进行匹配,并最终将 Queue 和 Channel 进行绑定,使消息进入到对应的消息队列中去、
  5. 待消息进入到对应的消息队列中之后,RabbitMQ Server 会返回给我们一个确认应答(确认应答后续会进行介绍),来通知我们,消息已经成功被 RabbitMQ Server 所发送,于是,消费者变回根据一定的策略来从消息队列中获取消费,并最终将该消息消费掉,消息消费之后,也会给我们返回一个确认应答(确认应答后续会进行介绍),告诉我们消息已经成功消费掉了。

2、如何防止消息丢失

由前文结构图可以看出消息在producer、broker、consumer三方之间流转,导致消息的丢失风险也是同样这三个地方

  • 消息从producer发送到broker过程中,broker没有成功接收消息,但生产者认为消息发送成功,不再重复发消息导致丢失
  • broker接收到了消息,之后丢失了,比如宕机了。
  • broker接收到了消息,没有匹配的路由规则,消息投递不到对应的队列中,消息被抛弃
  • 持久化消息刷盘完成后,broker出现单机故障、磁盘损毁且无法修复
  • consumer成功从broker中消费了消息,那么RabbitMQ的ack机制会自动的返回成功,表明发送消息成功,下次就不会发送这个消息;但消费者处理这条消息时宕机了导致这条消息丢失了。

解决办法:

生产者将信道设置成 confirm (确认)模式,在confirm 模式下所有在该信道上面发布的消息都会被指派一个唯一的 ID(从 l 开始),一旦消息被投递到所有匹配的队列之后, RabbitMQ 就会发送一个确认 (Basic.Ack) 给生产者(包含消息的唯一 ID),这就使得生产者知晓消息已经正确到达队列了

  • 开启RabbitMQ的持久化

当生产者把消息成功写入RabbitMQ之后,RabbitMQ就把消息持久化到磁盘。结合上面的说到的confirm机制,只有当消息成功持久化磁盘之后,才会回调生产者的接口返回ack消息,否则都算失败,生产者会重新发送。存入磁盘的消息不会丢失,就算RabbitMQ挂掉了,重启之后会读取磁盘中的消息,不会导致消息的丢失。
持久化:exchange、queue、message持久化

  • 开启路由失败通知回调,路由失败进行回调或者使用备份交换器

RabbitMQ 3.0版本前可以使用mandatory和immediate是channel.basicPublish方法中的两个参数,它们都有当消息传递过程中不可达目的地时将消息返回给生产者的功能。生产者通过调用channel.addReturnListener来添加ReturnListener监听器实现获取到没有被正确路由到合适队列的消息

  • 当mandatory标志位设置为true时,如果交换器无法根据自身的类型和路由键找到一个符合条件的队列,那么RabbitMQ会调用Basic.Return命令将消息返回给生产者。当mandatory设置为false时,出现上述情况,新消息直接被丢弃。
  • 当immediate参数设置为true时,如果交换器再将消息路由到一个队列中,否则将消息返回给生产者。immediate参数告诉服务器,如果该消息关联的队列上有消费者,则立刻投递;如果所有匹配的队列上都没有消费者,则直接将消息返还给生产者,不用将消息存于的队列而等待消费者
Channel channel = connection.createChanel();
channel.addReturnListener(new ReturnListener() {
    @Override
    public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
        // do something...
    );
    } 
 }); 
//第一个参数表示交换机的名称,
//第二个参数表示路由 Key 的名称,
//第三个参数是 mandatory 属性,表示是否开启消息返回机制,如果这个属性被置为 false ,则消息返回监听器就不会生效
channel.basicPublish(exchangeName, routingKey, true, null, msg.getBytes()); ```

RabbitMQ3.0版本开始去掉了对于immediate参数的支持,对此RabbitMQ官方解释是:immediate参数会影响镜像队列的性能,增加代码复杂性,建议采用TTL和DLX的方法替代

如果你不想复杂化生产者的编程逻辑,又不想消息丢失,那么可以使用备份交换器,RabbitMQ提供的备份交换器(Alternate
Exchange)可以将未能被交换器路由的消息(没有绑定队列或者没有匹配的绑定)存储起来,再在需要的时候去处理这些消息。可以通过在声明交换器的时候添加alternate-exchange参数来实现,优先级高于mandatory参数

  • 引入RabbitMQ镜像队列,增加消息副本备份,在所有副本都完成刷盘后才返回ack消息
  • 关闭 RabbitMQ 的自动 ack,对这条消息的业务处理完成后,手动调用ack api

3、如何防止重复消费

正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除;

消息重复来源于两个部分:生成者产生重复的消息,消费者消费了重复的消息

  • 网络传输等等故障,消费者消费完成的ack没有传到到broker,导致消息队列认为还没有消费,消息分发给其他的消费者
  • 集群模式下,master将该ack消息组播到每个slave节点是异步的,当在mater节点失效时,ack消息并未同步到所有节点,造成消息的重复发送,
  • 生产者主动重发
  • consumer 负载变化也会导致重复消息

解决办法:

  • 保证消息的唯一性,比如:生产者生产消息时具备唯一ID,消费时进行处理
  • 保证消息内容中关键业务属性幂等性,比如:订单id

消息重复从技术上看是不可避免的,为了避免业务出现问题,对于消息中间件幂等是必须要做的

3、如何保证顺序消费

从宏观集群上看,生产者发送消息有网络原因或是其他故障,消息源头的顺序已经不能够保证顺序。
从微观角度看,队列是有顺序的,一个业务流程下也是按序按步骤产生消息。

所以保证集群中的消费顺序,也得两头抓,消息按序进队列,消息从队列中按序取出进行消费

  • 同一个业务流程下的消息,发送到同一个MQServer下
  • 按路由拆分多个队列,每个队列有自己的消费者
  • 消费者从队列中取出消息,按关键字进行哈希到不同的内存队列,多线程处理各自的内存队列中的数据

4、消息积压问题

导致这个问题原因就只有一个,生产速度 > 消费速度

  • 生成者生产消息太多太快
  • 消费者太少,故障,或者消费消息太慢

解决办法:实际的业务中生产者的生产速度是按需的,不好修改,所以优化消费者消费速度

  • 增加消费端实例,说白了就是增加机器
  • 增加消费端的消费能力,使用内存队列缓存数据后,再多线程消费
  • 延迟队列
  • 先记录(可写到redis、db等)后处理,

5、快速修复大量积压的消息流程

将消息快速转到已扩充10倍的服务器上,再以10倍消费速度快速处理

  • 1)先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉

  • 2)新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量

  • 3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue

  • 4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据

  • 5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据

  • 6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息

6、延迟队列

基于TTL和死信队列

在这里插入图片描述

1、生产者发送一个X分钟过期的消息到正常交换机
2、正常交换机将消息死信队列
3、由于死信队列没有消费者,X分钟以后,消息会成为死信,出列
4、转给死信交换机,死信交换机将消息转发给正常队列
5、消费者监听正常队列,可以获得到消息

在这里插入图片描述

基于RabbitMQ 延迟消息插件 rabbitmq-delayed-message-exchange

根据TTL原理,消息超时后不是马上进入死信队列,而是在消息到队列的顶端(队首)即将被消费时进入死信队列,
如果不能实现在消息粒度上的 TTL,并使其在设置的 TTL 时间及时死亡,就无法设计成一个通用的延时队列。
可以使用基于RabbitMQ插件来实现延迟队列,从而解决这个问题。

基于RabbitMQ 3.5.3及之后的插件实现延迟,是交换机实现延迟,而不再是队列实现延迟,声明 x-delayed-message 类型的 Exchange,消息发送时指定消息头 x-delay 以毫秒为单位将消息进行延迟投递,消息在发布之后不会立即进入队列,先将消息保存至 Mnesia(一个分布式数据库管理系统的Erlang应用)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值