RabbitMQ combat (three) - Advanced Features

0 Related Items

1 you will learn

  • How to ensure the successful delivery of the message hundred percent
  • Idempotence
  • How to avoid massive orders that generated the message repeated consumption
  • Confirm acknowledgment message, Return a return message
  • Custom consumers
  • ACK message queue and return
  • Limiting
  • TTL
  • Dead Letter Queue

2 hundred percent guarantee message delivery success

Reliability of delivery 2.1 Producer

2.1.1 Requirements

  • Ensure messages successfully sent
  • MQ nodes to ensure successful reception
  • MQ sender receives node (Broker) acknowledgment
  • Improve the compensation mechanism news

In actual production, is difficult to protect completely reliable first three points, such as in extreme environments, the producer failed to send a message, the network sending end flash so the situation suddenly in an interview with the acknowledgment, is difficult to guarantee reliability delivery, so we need to have a perfect fourth point of the message compensation mechanism.

2.1.2 Solution

2.1.2.1 Option One: Information for falling messages, message status marking (common scheme)

Persisted to the DB message and set the status value, Consumer responses received to change the status of the current record.
And then re-transmits the polling response message is not received, attention here to set the number of retries.

Program flow chart
Program implementation process

For example, my next single success
step1 - the BIZ DB library orders to order data, and thus generates the service message into the message MSG DB library

Here thanks to the two databases, requires two persistence operations, in order to ensure consistency of the data, it was possible to think of a distributed transaction, but in practice the manufacturers are basically the use of compensation mechanisms!

Here we must ensure that messages are stored in step1 successful, without any exceptions, then the production side and then send a message. If you fail to quick failure mechanism

Business data and message storage is completed to enter
setp2 - send a message to the MQ service, if everything is normal and correct consumer to listen to the message, enter

step3 - Production end has a Confirm Listenerresponse sent back Broker asynchronous monitor in order to determine whether the message is successfully delivered

  • step4 - If successful, the message to the database query and update status messages 1
  • step5 - If an accident happens, the consumer does not receive or Listener flash occurs when the network receives an acknowledgment, leading to the production side of the Listener will never receive confirm this news answer, and that is the status of this message has been to 0, and this time you need to use our regular tasks distributed to grab those have not yet been consumed MSG message timeout from the database and re-send it again
    at this time we need to set a rule, such messages provided in the storage time a threshold timeout, or after 5 minutes if the 0 state would need to be extracted message. We use here is distributed regular tasks, the timing to fetch the DB was created more than five minutes and status messages to the message from zero.

step6 - to crawl out of the message redelivery (Retry Send), beginning from the second step is to continue to go down

step7 - Of course, some messages might be due to some practical problems can not be routed to the Broker, such as routingKey not set up, the corresponding queue is deleted by mistake, then this message even if retry several times and still can not be delivered successfully, it is necessary to retry do limit the number of times, for example three times limit, if the number is greater than three times the delivery, then it will update the status of the message 2, the message indicates the final delivery failure, then the compensation mechanism to deal with artificial. Actual production, this situation is still relatively small, but you can not live without this compensation mechanism, or else can not do the reliability.

Thoughts: This program is appropriate in a highly concurrent scenarios

For the first scenario, we need to do the database persistence operations twice, apparently database performance bottleneck in high concurrency scenarios.

In fact, only need to be in our core business link in storage on it, the message is no need to put in storage, we can do delayed delivery of the message, do the second confirmation callback inspection. Here then we see Option Two

2.1.2.2 Message delay delivery, twice confirming callback inspection (large-scale mass data plan)

Manufacturers classic implementation

Of course, this scheme does not necessarily guarantee hundred percent successful delivery, but basically you can guarantee about 99.9% of the message is OK, some particularly extreme cases can only be compensated to do it manually, or using a timed task.

The main operation is to reduce DB

Program flow chart
  • Upstream Service
    upstream services, namely, the production end
  • Downstream service
    下游服务,即消费端
  • Callback service
    回调服务

    方案实现流程
  • step1 一定要先将业务消息入库,然后Pro再发出消息,顺序不能错!
  • step2 在发送消息之后,紧接着Pro再发送一条消息(Second Send Delay Check),即延迟消息投递检查,这里需要设置一个延迟时间,比如5分钟之后进行投递.
  • step3 Con监听指定的队列,处理收到的消息.
  • step4 处理完成之后,发送一个confirm消息,也就是回送响应,但是其不是普通的ACK,而是重新生成一条消息,投递到MQ,表示处理成功.
  • Callback service是一个单独的服务,它扮演MSG DB角色,它通过MQ监听下游服务发送的confirm消息,如果监听到confirm消息,那么就对其持久化到MSG DB.
  • step6 5分钟之后延迟消息发送到MQ,然后Callback service还是去监听延迟消息所对应的队列,收到Check消息后去检查DB中是否存在消息,如果存在,则不需要做任何处理,如果不存在或者消费失败了,那么Callback service就需要主动发起RPC通信给上游服务,告诉它延迟检查的这条消息我没有找到,你需要重新发送,生产端收到信息后就会重新查询BIZ DB然后将消息发送出去.

设计目的

少做一次DB的存储,在高并发场景下,最关心的不是消息百分百投递成功,而是一定要保证性能,保证能抗得住这么大的并发量。所以能节省数据库的操作就尽量节省,异步地进行补偿.

其实在主流程里面是没有Callback service的,它属于一个补偿的服务,整个核心链路就是生产端入库业务消息,发送消息到MQ,消费端监听队列,消费消息。其他的步骤都是一个补偿机制。

小结

这两种方案都是可行的,需要根据实际业务来进行选择,方案二也是互联网大厂更为经典和主流的解决方案.但是若对性能要求不是那么高,方案一要更简单.

3 幂等性

3.1 什么是幂等性

用户对于同一操作发起的一次请求或者多次请求的结果是一致的

比如数据库的乐观锁,在执行更新操作前,先去数据库查询version,然后执行更新语句,以version作为条件,如果执行更新时有其他人先更新了这张表的数据,那么这个条件就不生效了,也就不会执行操作了,通过这种乐观锁的机制来保障幂等性.

3.2 Con - 幂等性

3.2.1 什么是Con - 幂等性

在业务高峰期最容易产生消息重复消费问题,当Con消费完消息时,在给Pro返回ack时由于网络中断,导致Pro未收到确认信息,该条消息就会重新发送并被Con消费,但实际上该消费者已成功消费了该条消息,这就造成了重复消费.

而Con - 幂等性,即消息不会被多次消费,即使我们收到了很多一样的消息.

3.2.2 主流幂等性实现方案

3.2.2.1 唯一ID+指纹码

核心:利用数据库主键去重
  • 唯一ID:业务表的主键
  • 指纹码:为了区别每次正常操作的码,每次操作时生成指纹码;可以用时间戳+业务编号或者标志位(具体视业务场景而定)
  • 优势
    实现简单
  • 弊端
    高并发下有数据库写入的性能瓶颈
  • 解决方案
    根据ID进行分库分表算法路由
小结

首先我们需要根据消息生成一个全局唯一ID,然后还需要加上一个指纹码。这个指纹码它并不一定是系统去生成的,而是一些外部的规则或者内部的业务规则去拼接,它的目的就是为了保障这次操作是绝对唯一的。

将ID + 指纹码拼接好的值作为数据库主键,就可以进行去重了。即在消费消息前呢,先去数据库查询这条消息的指纹码标识是否存在,没有就执行insert操作,如果有就代表已经被消费了,就不需要管了

3.2.2.2 利用Redis原子性

这里我们使用Redis实现幂等,还需要考虑如下问题

  • 我们是否要进行数据落库,如果落库,那么数据库和缓存如何做到原子性?
    如果你想用事务,放弃吧,Redis缓存事务和MySQL事务根本不是同一个事务
  • 如果不落库,那么都存储到缓存中,定时同步的策略如何设置为好?

这里只提用Redis的原子性去解决MQ幂等性重复消费的问题

MQ的幂等性问题 根本在于的是生产端未正常接收ACK,可能是网络抖动、网络中断导致

可能的方案

Con在消费开始时将 ID放入到Redis的BitMap中,Pro每次生产数据时,从Redis的BitMap对应位置若不能取出ID,则生产消息发送,否则不进行消息发送。

但是有人可能会说,万一Con,ProRedis命令执行失败了怎么办,虽然又出现重复消费又出现Redis非正常执行命令的可能性极低,但是万一呢?

OK,我们可以在Redis命令执行失败时,将消息落库,每日用定时器,对这种极特殊的消息进行处理。

4 Confirm机制

4.1 什么是Confirm机制

  • 消息的确认
    Pro投递消息后,如果Broker收到消息,则会给Pro一个应答
  • Pro接收应答
    用来确定这条消息是否正常地发送到Broker,该法也是消息可靠性投递的核心保障!

    4.2 Confirm机制流程图


    Pro发送消息到Broker,Broker接收到消息后,产生回送响应
    Pro中有一个Confirm Listener异步监听响应应答

4.2 实现Confirm机制

  1. 在channel上开启确认模式:channel.confirmSelect()
  2. 在channel上添加监听:addConfirmListener
    监听成功和失败的返回结果,根据具体的结果对消息进行重新发送、或记录日志等后续处理

接下来就让我们根据原理进行实操吧!

  • Con端
  • Pro端
  • 启动Con,检查管控台

  • 启动Pro

    5 Return机制

    5.1 什么是Return机制

  • Return Listener 用于处理一些不可路由的消息
  • Pro通过指定一个Exchange和Routingkey,把消息送到某一个队列中,然后Con监听队列,进行消费
  • 但如果我们在发送消息时,当前Exchange不存在或者Routingkey路由不到,如果我们要监听这种不可达的消息,就要用到Return Listener

    5.2 Return机制示意图

5.3 实现Return机制

  • 添加return监听:addReturnListener,生产端去监听这些不可达的消息,做一些后续处理,比如说,记录下消息日志,或者及时去跟踪记录,有可能重新设置一下就好了

在基础的API中的一个关键的配置项:Mandatory

  • 如果为true,则监听器会接收到路由不可达的消息,然后进行后续处理
  • 如果为false,那么broker端自动删除该消息

  • Con
  • Pro
  • 启动Con


  • 启动Pro
    由于Pro设置的是一个错误的路由key,所以消费端没有任何打印,而生产端打印了如下内容

  • 如果我们将 Mandatory 属性设置为false,对于不可达的消息会被Broker直接删除,那么Pro就不会进行任何打印了。如果我们的路由key设置为正确的,那么Con能够正确消费,Pro也不会进行任何打印。

    6 Con - 自定义监听

  • 之前我们都是在代码中编写while循环,通过consumer.nextDelivery方法获取下一条消息,然后进行消费处理
  • 其实我们还有另一种选择,使用自定义的Consumer,它更方便,解耦性更强,也是在实际工作中最常用的使用方式

自定义Con实现只需要继承 DefaultConsumer 类,重写 handleDelivery 方法即可!

6.1 代码实现

  • 自定义Con
  • Con
  • Pro
  • 启动Con后,查看管控台

  • 启动Pro,Con接收消息

    7 Con - 限流

    7.1 什么是Con - 限流

    7.1.1 消息过载场景

  • 假设我们有这样的场景
    Rabbitmq服务器有上万条未处理的消息,我们随便打开一个Con - Client,会造成:巨量的消息瞬间全部推送过来,然而我们单个客户端无法同时处理这么多数据!此时很有可能导致服务器崩溃,严重的可能导致线上的故障。
  • 还有一些其他的场景,比如说单个Pro一分钟产生了几百条数据,但是单个Con一分钟可能只能处理60条,这个时候Pro-Con肯定是不平衡的。通常Pro是没办法做限制的。所以Con肯定需要做一些限流措施,否则如果超出最大负载,可能导致Con性能下降,服务器卡顿甚至崩溃等一系列严重后果

因此,我们需要限流

7.1.2 Con - 限流机制

RabbitMQ提供了一种qos (服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息 (通过基于Con或者channel设置Qos的值) 未被确认前,不消费新的消息

不能设置自动签收功能(autoAck = false)
如果消息未被确认,就不会到达Con,目的就是给Pro减压

限流设置API

void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);

  • prefetchSize: 单条消息的大小限制,Con通常设置为0,表示不做限制
  • prefetchCount: 一次最多能处理多少条消息
  • global: 是否将上面设置true应用于channel级别还是取false代表Con级别

prefetchSize和global这两项,RabbitMQ没有实现,暂且不研究
prefetchCount在 autoAck=false 的情况下生效,即在自动应答的情况下该值无效

手工ACK
void basicAck(Integer deliveryTag,boolean multiple)
调用这个方法就会主动回送给Broker一个应答,表示这条消息我处理完了,你可以给我下一条了。参数multiple表示是否批量签收,由于我们是一次处理一条消息,所以设置为false

7.2 实现Con - 限流

  • 自定义Con
  • Con
  • Pro
  • 启动Con,查看管控台
  • 启动Pro,开始发送消息,Con接收消息
  • 实现限流,仅仅处理一条消息,其余的都在等待
  • 现在,我们开启ACK应答处理
  • 重新启动Con,发现剩余的2条消息也全都发送并接收了!
  • 我们之前是注释掉手工ACK方法,然后启动消费端和生产端,当时Con只打印一条消息,这是因为我们设置了手工签收,并且设置了一次只处理一条消息,当我们没有回送ACK应答时,Broker端就认为Con还没有处理完这条消息,基于这种限流机制就不会给Con发送新的消息了,所以Con那时只打印了一条消息

    8 Con - ACK & 重回队列机制

    8.1 ACK & NACK

    当我们设置autoACK=false 时,就可以使用手工ACK方式了,其实手工方式包括了手工ACK与NACK

当我们手工 ACK 时,会发送给Broker一个应答,代表消息处理成功,Broker就可回送响应给Pro.
NACK 则表示消息处理失败,如果设置了重回队列,Broker端就会将没有成功处理的消息重新发送.

使用方式

  • Con消费时,如果由于业务异常,我们可以手工 NACK 记录日志,然后进行补偿
API:void basicNack(long deliveryTag, boolean multiple, boolean requeue)
  • 如果由于服务器宕机等严重问题,我们就需要手工 ACK 保障Con消费成功
API:void basicAck(long deliveryTag, boolean multiple)

8.2 重回队列

  • 重回队列是为了对没有处理成功的消息,将消息重新投递给Broker
  • 重回队列,会把消费失败的消息重新添加到队列的尾端,供Con继续消费
  • 一般在实际应用中,都会关闭重回队列,即设置为false

8.3 实现机制

  • Con,关闭自动签收功能
  • 自定义Con,对第一条消息(序号0)进行NACK,并设置重回队列
  • Pro 对消息设置序号,以便区分
  • 启动Con,查看管控台

  • 启动Pro,这里第一条消息由于我们调用了NACK,并且设置了重回队列,所以会导致该条消息一直重复发送,消费端就会一直循环消费

    9 TTL机制

    9.1 什么是TTL

  • TTL(Time To Live),即生存时间
  • RabbitMQ支持消息的过期时间,在消息发送时可以进行指定
  • RabbitMQ支持为每个队列设置消息的超时时间,从消息入队列开始计算,只要超过了队列的超时时间配置,那么消息会被自动清除

9.2 管控台演示

  • 新增一个Q

  • 新增一个交换机

  • 绑定

  • Q中也显示了相关的绑定信息
  • 发送消息

  • 10s后,消息被清除为0

    10 死信队列机制

    10.1 什么是死信队列

    DLX - 死信队列(dead-letter-exchange)
    利用DLX,当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange中,这个Exchange就是DLX.

    10.2 死信队列的产生场景

  • 消息被拒绝(basic.reject / basic.nack),并且requeue = false
  • 消息因TTL过期
  • 队列达到最大长度

    10.3 死信的处理过程

  • DLX亦为一个普通的Exchange,它能在任何队列上被指定,实际上就是设置某个队列的属性
  • 当某队列中有死信时,RabbitMQ会自动地将该消息重新发布到设置的Exchange,进而被路由到另一个队列
  • 可以监听这个队列中的消息做相应的处理.该特性可以弥补RabbitMQ 3.0以前支持的immediate参数的功能

10.4 死信队列的配置

  • 设置死信队列的exchange和queue,然后进行绑定
    • Exchange:dlx.exchange
    • Queue: dlx.queue
    • RoutingKey:#
  • 正常声明交换机、队列、绑定,只不过我们需要在队列加上一个参数即可
arguments.put(" x-dead-letter-exchange","dlx.exchange");

这样消息在过期、requeue、 队列在达到最大长度时,消息就可以直接路由到死信队列!

10.5 实操演示

  • 自定义Con
  • Pro
  • Con
  • 启动Con,查看管控台



  • 现在,让我们停止Con,并启动Pro,由于没有Con,TTL为10s的消息将送往死信队列
  • 10s后
  • 实际环境我们还需要对死信队列进行一个监听和处理,当然具体的处理逻辑和业务相关,这里只是简单演示死信队列是否生效。

11 总结

本文专注RabbitMQ高级特性的学习

首先介绍了大厂在实际使用中是如何保障消息投递成功和幂等性的,以及对RabbitMQ的确认消息、返回消息、ACK与重回队列、消息的限流,以及对超时时间、死信队列的使用

最后,感谢您的阅读!

参考

RabbitMQ 100% 投递成功方案详解
RabbitMQ 从入门到精通(二)
RabbitMQ 幂等性概念及业界主流解决方案
RabbitMQ从入门到精通(三)

更多内容请关注JavaEdge公众号

Guess you like

Origin www.cnblogs.com/washabi/p/11112024.html