• RabbitMQ高级特性


    目录

    消息的可靠投递confirm和return

    Consumer Ack

    消费端限流

    TTL  Time To Live(存活时间/过期时间)

    死信队列(死信交换机)

    延迟队列

    日志与监控

    rabbitmqctl管理和监控

    消息追踪 


    消息的可靠投递confirm和return

    1. 持久化
    • exchange要持久化
    • queue要持久化
    • message要持久化
    1. 生产方确认Confirm
    2. 消费方确认Ack
    3. Broker高可用

            在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。

    • confirm 确认模式
    • return 退回模式

            rabbitmq 整个消息投递的路径为:
    producer--->rabbitmq broker--->exchange--->queue--->consumer

    • 消息从 producer 到 exchange 则会返回一个 confirmCallback
    • 消息从 exchange-->queue 投递失败则会返回一个 returnCallback

    我们将利用这两个 callback 控制消息的可靠性投递

    1. "connectionFactory" host="${rabbitmq.host}"
    2. port="${rabbitmq.port}"
    3. username="${rabbitmq.username}"
    4. password="${rabbitmq.password}"
    5. virtual-host="${rabbitmq.virtual-host}"
    6. publisher-confirms="true"
    7. publisher-returns="true"
    8. />
    1. /**
    2. * 确认模式:
    3. * 步骤:
    4. * 1. 确认模式开启:ConnectionFactory中开启publisher-confirms="true"
    5. * 2. 在rabbitTemplate定义ConfirmCallBack回调函数
    6. */
    7. //2. 定义回调
    8. rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    9. /**
    10. *
    11. * @param correlationData 相关配置信息
    12. * @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
    13. * @param cause 失败原因
    14. */
    15. @Override
    16. public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    17. System.out.println("confirm方法被执行了....");
    18. if (ack) {
    19. //接收成功
    20. System.out.println("接收成功消息" + cause);
    21. } else {
    22. //接收失败
    23. System.out.println("接收失败消息" + cause);
    24. //做一些处理,让消息再次发送。
    25. }
    26. }
    27. });
    28. //3. 发送消息
    29. rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
    1. /**
    2. * 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
    3. * 步骤:
    4. * 1. 开启回退模式:publisher-returns="true"
    5. * 2. 设置ReturnCallBack
    6. * 3. 设置Exchange处理消息的模式:
    7. * 1. 如果消息没有路由到Queue,则丢弃消息(默认)
    8. * 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
    9. */
    10. //设置交换机处理失败消息的模式
    11. rabbitTemplate.setMandatory(true);
    12. //2.设置ReturnCallBack
    13. rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    14. /**
    15. *
    16. * @param message 消息对象
    17. * @param replyCode 错误码
    18. * @param replyText 错误信息
    19. * @param exchange 交换机
    20. * @param routingKey 路由键
    21. */
    22. @Override
    23. public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
    24. System.out.println("return 执行了....");
    25. System.out.println(message);
    26. System.out.println(replyCode);
    27. System.out.println(replyText);
    28. System.out.println(exchange);
    29. System.out.println(routingKey);
    30. //处理
    31. }
    32. });
    33. //3. 发送消息
    34. rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");

    输出结果

    1. return 执行了....
    2. (Body:'message confirm....' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])
    3. 312
    4. NO_ROUTE
    5. test_exchange_confirm
    6. confirm11

            这里有一个坑 ConfirmCallback和returnCallback是异步的,他在执行的之后我们实际上已经关闭了rabbitmq资源 ,所以可以在最后添加上:

    Thread.sleep(2000);

            不然消息找不到回来的路了哈哈哈哈

    在RabbitMQ中也提供了事务机制,但是性能较差,此处不做讲解。

    Consumer Ack

    ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
    有三种确认方式:

    • 自动确认:acknowledge="none"
    • 手动确认:acknowledge="manual"
    • 根据异常情况确认:acknowledge="auto"

    其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。

    如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

    1. /**
    2. * Consumer ACK机制:
    3. * 1. 设置手动签收。acknowledge="manual"
    4. * 2. 让监听器类实现ChannelAwareMessageListener接口
    5. * 3. 如果消息成功处理,则调用channel的 basicAck()签收
    6. * 4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
    7. *
    8. *
    9. */
    10. @Component
    11. public class AckListener implements ChannelAwareMessageListener {
    12. @Override
    13. public void onMessage(Message message, Channel channel) throws Exception {
    14. long deliveryTag = message.getMessageProperties().getDeliveryTag();
    15. try {
    16. //1.接收转换消息
    17. System.out.println(new String(message.getBody()));
    18. //2. 处理业务逻辑
    19. System.out.println("处理业务逻辑...");
    20. int i = 3/0;//出现错误
    21. //3. 手动签收
    22. channel.basicAck(deliveryTag,true);
    23. } catch (Exception e) {
    24. //e.printStackTrace();
    25. //4.拒绝签收
    26. /*
    27. 第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
    28. */
    29. channel.basicNack(deliveryTag,true,true);
    30. //channel.basicReject(deliveryTag,true);
    31. }
    32. }
    33. }

            在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手 动确认

    1. "connectionFactory" acknowledge="manual" >
    2. "AckListener" queue-names="test_queue_confirm">

    消费端限流

    1. 在 中配置 prefetch属性设置消费端一次拉取多少消息
    1. "connectionFactory" acknowledge="manual" prefetch="1" >
    1. 消费端的确认模式一定为手动确认。acknowledge="manual”
    1. /**
    2. * Consumer 限流机制
    3. * 1. 确保ack机制为手动确认。
    4. * 2. listener-container配置属性
    5. * perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。
    6. */
    7. @Component
    8. public class QosListener implements ChannelAwareMessageListener {
    9. @Override
    10. public void onMessage(Message message, Channel channel) throws Exception {
    11. Thread.sleep(1000);
    12. //1.获取消息
    13. System.out.println(new String(message.getBody()));
    14. //2. 处理业务逻辑
    15. //3. 签收
    16. channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    17. }
    18. }

    TTL  Time To Live(存活时间/过期时间)

    当消息到达存活时间后,还没有被消费,会被自动清除。

    RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。

    1. 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。

    1. <rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
    2. <rabbit:queue-arguments>
    3. <entry key="x-message-ttl" value="100000" value-type="java.lang.Integer">entry>
    4. rabbit:queue-arguments>
    5. rabbit:queue>
    1. 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期。
    1. /**
    2. * TTL:过期时间
    3. * 1. 队列统一过期
    4. *
    5. * 2. 消息单独过期
    6. *
    7. *
    8. * 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
    9. * 队列过期后,会将队列所有消息全部移除。
    10. * 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
    11. *
    12. */
    13. // 消息后处理对象,设置一些消息的参数信息
    14. MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
    15. @Override
    16. public Message postProcessMessage(Message message) throws AmqpException {
    17. //1.设置message的信息
    18. message.getMessageProperties().setExpiration("5000");//消息的过期时间
    19. //2.返回该消息
    20. return message;
    21. }
    22. };
    23. for (int i = 0; i < 10; i++) {
    24. if(i == 5){
    25. //消息单独过期
    26. rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
    27. }else{
    28. //不过期的消息
    29. rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
    30. }
    31. }

    如果两者都进行了设置,以时间短的为准

    死信队列(死信交换机)

            DLX    Dead Letter Exchange  当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。

    1. 队列消息长度到达限制;
    2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
    1. //4.拒绝签收,不重回队列 requeue=false
    2. channel.basicNack(deliveryTag,true,false);
    1. 原队列存在消息过期设置,消息到达超时时间未被消费;

            给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key

    1. <rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
    2. <rabbit:queue-arguments>
    3. <entry key="x-dead-letter-exchange" value="exchange_dlx" />
    4. <entry key="x-dead-letter-routing-key" value="dlx.hehe" />
    5. <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
    6. <entry key="x-max-length" value="10" value-type="java.lang.Integer" />
    7. rabbit:queue-arguments>
    8. rabbit:queue>
    9. <rabbit:topic-exchange name="test_exchange_dlx">
    10. <rabbit:bindings>
    11. <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx">rabbit:binding>
    12. rabbit:bindings>
    13. rabbit:topic-exchange>
    1. <rabbit:queue name="queue_dlx" id="queue_dlx">rabbit:queue>
    2. <rabbit:topic-exchange name="exchange_dlx">
    3. <rabbit:bindings>
    4. <rabbit:binding pattern="dlx.#" queue="queue_dlx">rabbit:binding>
    5. rabbit:bindings>
    6. rabbit:topic-exchange>

    延迟队列

            即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。

            很可惜,在RabbitMQ中并未提供延迟队列功能。

    需求:

    • 下单后,30分钟未支付,取消订单,回滚库存。
    • 新用户注册成功7天后,发送短信问候。

    实现方式:

    • 定时器(并不优雅)
    • 延迟队列

            但是可以使用:TTL+死信队列 组合实现延迟队列的效果。

    订单系统

    1. <rabbit:queue id="order_queue" name="order_queue">
    2. <rabbit:queue-arguments>
    3. <entry key="x-dead-letter-exchange" value="order_exchange_dlx" />
    4. <entry key="x-dead-letter-routing-key" value="dlx.order.cancel" />
    5. <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
    6. rabbit:queue-arguments>
    7. rabbit:queue>
    8. <rabbit:topic-exchange name="order_exchange">
    9. <rabbit:bindings>
    10. <rabbit:binding pattern="order.#" queue="order_queue">rabbit:binding>
    11. rabbit:bindings>
    12. rabbit:topic-exchange>
    13. <rabbit:queue id="order_queue_dlx" name="order_queue_dlx">rabbit:queue>
    14. <rabbit:topic-exchange name="order_exchange_dlx">
    15. <rabbit:bindings>
    16. <rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx">rabbit:binding>
    17. rabbit:bindings>
    18. rabbit:topic-exchange>

    库存系统业务逻辑

    1. @Component
    2. public class OrderListener implements ChannelAwareMessageListener {
    3. @Override
    4. public void onMessage(Message message, Channel channel) throws Exception {
    5. long deliveryTag = message.getMessageProperties().getDeliveryTag();
    6. try {
    7. //1.接收转换消息
    8. System.out.println(new String(message.getBody()));
    9. //2. 处理业务逻辑
    10. System.out.println("处理业务逻辑...");
    11. System.out.println("根据订单id查询其状态...");
    12. System.out.println("判断状态是否为支付成功");
    13. System.out.println("取消订单,回滚库存....");
    14. //3. 手动签收
    15. channel.basicAck(deliveryTag,true);
    16. } catch (Exception e) {
    17. //e.printStackTrace();
    18. System.out.println("出现异常,拒绝接受");
    19. //4.拒绝签收,不重回队列 requeue=false
    20. channel.basicNack(deliveryTag,true,false);
    21. }
    22. }
    23. }
    1. "connectionFactory" acknowledge="manual" prefetch="1" >
    2. "orderListener" queue-names="order_queue_dlx">

            这里有个坑 监听的是死信队列

    日志与监控

    RabbitMQ默认日志存放路径: /var/log/rabbitmq/rabbit@xxx.log

    日志包含了RabbitMQ的版本号、Erlang的版本号、RabbitMQ服务节点名称、cookie的hash值、 RabbitMQ配置文件地址、内存限制、磁盘限制、默认账户guest的创建以及权限配置等等

    rabbitmqctl管理和监控

    查看队列
    # rabbitmqctl list_queues
    查看exchanges
    # rabbitmqctl list_exchanges
    查看用户
    # rabbitmqctl list_users
    查看连接
    # rabbitmqctl list_connections
    查看消费者信息
    # rabbitmqctl list_consumers
    查看环境变量
    # rabbitmqctl environment
    查看未被确认的队列
    # rabbitmqctl list_queues name messages_unacknowledged
    查看单个队列的内存使用
    # rabbitmqctl list_queues name memory
    查看准备就绪的队列
    # rabbitmqctl list_queues name messages_ready

    消息追踪 

    在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。

    firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式发送到默认的exchange上。这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类型的exchange。发送到这个exchange上的消息的routing key为 publish.exchangename 和deliver.queuename。其中exchangename和queuename为实际exchange和queue的名称,分别对应生产者投递到exchange的消息,和消费者从queue上获取的消息。
    注意:打开 trace 会影响消息写入功能,适当打开后请关闭。
    rabbitmqctl trace_on:开启Firehose命令
    rabbitmqctl trace_off:关闭Firehose命令
     

     消息可靠性保障--消息补偿

    消息幂等性保障--乐观锁机制

     

  • 相关阅读:
    Vue输入框卡死
    机器学习 —— 计算评估指标
    最新!11月PMP认证考试地址已公布!
    获取url动态参数
    SPDK/NVMe存储技术分析之初识UIO(一)
    金融信创与云化转型|期货超融合架构转型与场景探索合集
    NeuralRecon拜读:单目视频实时连贯三维重建
    Nginx反向代理与负载均衡
    node.js的express模块实现GET和POST请求
    【C++/STL】stack和queue(容器适配器、优先队列、双端队列)
  • 原文地址:https://blog.csdn.net/qq_45412648/article/details/132714507