RabbitMQの信頼できるメッセージ配信

Mavenを紹介します

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

構成の追加:

spring:
  rabbitmq:
    host: 192.168.56.10
    port: 5672
    #虚拟主机
    virtual-host: /
    #开启发送端发送数据到broker确认接收到数据
    publisher-confirms: true
    #开启发送端确认,确认数据已经抵达队列
    publisher-returns: true
    template:
      #抵达队列,以异步模式优先回调组合ReturnCallback
      mandatory: true
    listener:
      simple:
        #手动ack消息 手动确认收货 手动确认模式 防止消息丢失
        acknowledge-mode: manual

構成クラスを追加する

/**
 * 运行之前,一定要小心,否则要删除队列/交换机重新运行 麻烦!
 *
 * 解决消息丢失(最怕)
 *  1 做好消息确认机制(publisher,consumer【手动ack】)
 *  2 每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一次
 * 解决消息重复
 *  1 幂等性
 *  2 防重表
 *  3 RabbitMQ自带redelivered (做法过于暴力)
 * 解决消息积压
 *  1 增加更多的消费者
 *  2 上线专门的队列消费服务,取出来,记录到数据库,离线慢慢处理
 */
//开启RabbitMQ消息队列
@EnableRabbit
@Configuration
public class MyRabbitMQConfig {
    
    

    @Autowired
    RabbitTemplate rabbitTemplate;

    @RabbitListener(queues = "order.release.order.queue")
    public void listening(OrderEntity entity, Channel channel, Message message) throws IOException {
    
    
        System.out.println("收到过期的订单,准备关闭订单。order:"+entity.getOrderSn());
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    //容器中的组建Queue Exchange Binding 都会自动创建(前提是RabbitMQ没有)
    @Bean
    public Queue orderDelayQueue() {
    
    

        // String name, boolean durable, boolean exclusive, boolean autoDelete,
        //			@Nullable Map<String, Object> arguments
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", "order-event-exchange");//死信交换机
        arguments.put("x-dead-letter-routing-key", "order.release.order");//死信路由键
        arguments.put("x-message-ttl", 60000);//消息过期时间 ms 1分钟
        return new Queue("order.delay.queue", true, false, false, arguments);
    }

    @Bean
    public Queue orderReleaseOrderQueue() {
    
    

        //普通队列
        return new Queue("order.release.order.queue", true, false, false);
    }

    @Bean
    public Exchange orderEventExchange() {
    
    

        // String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
        //普通交换机
        return new TopicExchange("order-event-exchange", true, false);
    }

    @Bean
    public Binding orderCreateOrderBinding() {
    
    

        //和延时队列绑定
        return new Binding("order.delay.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.create.order",
                null);
    }

    @Bean
    public Binding orderReleaseOrderBinding() {
    
    

        //和普通队列绑定
        return new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.order",
                null);
    }

    @Bean
    public Binding orderReleaseOtherBinding() {
    
    

        //订单释放直接和库存释放进行绑定
        return new Binding("stock.release.stock.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.other.#",
                null);
    }
//
    @Bean
    public Queue orderSeckillOrderQueue() {
    
    
        return new Queue("order.seckill.order.queue", true, false, false);
    }

    @Bean
    public Binding orderSeckillOrderQueueBinding() {
    
    
        return new Binding("order.seckill.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.seckill.order",
                new HashMap<>());
    }
//
//    /**
//     * 下面全都是基础配置
//     */
//
    @Bean
    public MessageConverter messageConverter() {
    
    

        return new Jackson2JsonMessageConverter();
    }
//
//    /**
//     * 定制rabbitTemplate
//     * 1.publisher-confirms: true
//     * 3.消费端确认 (保证每个消息被正确消费 此时才可以braker删除这个消息)
//     * 1.默认是自动确认的 只要消息接收到  客户端自动确认服务端就要移除这个消息
//     * 问题 :
//     * 收到很多消息 自动回复给服务器ack 只有一个消息处理成功 宕机了 发现消息丢失
//     * 手动确认模式: 只要我们没有确认高随MQ 货物被签收 没有ack
//     * 消息就一直是unacked状态 即使Consumer宕机 消息不会丢失 会重新变成ready
//     * 2.如果签收
//     */
    @PostConstruct  //MyRabbitConfig对象创建完成以后执行这个方法
    public void initRabbitTemplate() {
    
    

        //设置确认回调 消息到了队列
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    
    

            /**
             * 1、消息抵达服务器 ack=true
             * @param correlationData 当前消息唯一关联的数据 这个是消息唯一id
             * @param ack 消息是否成功收到
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    
    
                //服务器收到了
                System.out.println("消息抵达服务器confirm....correlationData[" + correlationData + "]==>ack[" + ack + "]cause>>>" + cause);
            }
        });

        //设置消息队列的确认回调 发送了,但是队列没有收到
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    
    

            /**
             * 只要消息没有投递给指定的队列 就触发这个失败回调
             * @param message  投递失败的消息详细信息
             * @param replyCode 回复的状态码
             * @param replyText 回复的文本内容
             * @param exchange 当时这个消息发给那个交换机
             * @param routingKey 当时这个消息用那个路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
    
    
                //报错误 未收到消息
                System.out.println("Fail!! Message[" + message + "]==>[" + exchange + "]==>routingKey[" + routingKey + "]");
            }
        });
    }
}

RabbitMQ確認メッセージメカニズム、信頼できるメッセージ

送信者:
メッセージが失われて確実に到着しないようにするために、トランザクションメッセージを使用でき、パフォーマンスが250分の1に低下します。
このため、確認メカニズムの
パブリッシャーconfirmCallbackが導入され、メッセージがブローカーに到着したかどうかが確認されます。
パブリッシャーreturnCallback。メッセージがキューに配信されない場合、
confirmCallbackとreturnCallbackがトリガーされます。すでに構成クラスに追加されています。

信頼性の高い到着-確認メッセージ確認メカニズム

受信終了:

  • コンシューマーはメッセージを取得して正常に処理し、
    確認のためにブローカーのbasic.ackにackを返信できます。ブローカーはメッセージを削除します。
    basic.nackは否定的な確認に使用され、ブローカーがこのメッセージを破棄するかどうかを指定できます。
    否定的な確認のためにbasic.rejectをバッチ操作できます。上記と同じですが、バッチ操作はできません。
  • デフォルトでは、メッセージがコンシューマーによって受信されると、コンシューマーはブローカーのキューから削除されます。
    メッセージはデフォルトで自動的に確認応答されますが、メッセージが処理されたか、正常に処理されたかを判別できない場合は、手動ackモードをオンにすることができます。
    メッセージ処理が成功すると、ack()は次のメッセージを受信し、このメッセージブローカーはそれを削除します。
    メッセージ処理は失敗します。nack()/ reject()、処理のために他のユーザーに再送信するか、フォールトトレラント処理後にackします。
    メッセージはack / nack()メソッドを呼び出していません。ブローカーは、メッセージが処理中であり、他のユーザーに配信されないと考えています。この時点で、クライアントは切断され、メッセージはブローカーによって削除されませんが、他の人に配信されます。
    受信者コード:
//这个类能接受hello-java-queue消息
@RabbitListener(queues = {
    
    "hello-java-queue"})
@Service("orderItemService")
public class OrderItemServiceImpl extends ServiceImpl<OrderItemDao, OrderItemEntity> implements OrderItemService {
    
    

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
    
    
        IPage<OrderItemEntity> page = this.page(
                new Query<OrderItemEntity>().getPage(params),
                new QueryWrapper<OrderItemEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 监听消息
     * queues 声明需要监听的所有队列
     * org.springframework.amqp.core.Message
     * <p>
     * 参数可以写一下类型
     * 1、Message message: 原生消息详细信息。头+体
     * 2、发送的消息的类型: OrderReturnReasonEntity content;
     * 3、Channel channel:当前传输数据的通道
     * <p>
     * Queue:可以很多人都来监听,只要收到消息,队列删除消息,而且只能有一个收到此消息
     * 1)、订单服务启动多个:同一个消息,只能有一个客户端收到
     * 2)、只有一个消息完全处理完,方法运行结束,我们就可以接收到下一个消息
     */
//    @RabbitListener(queues = {"hello-java-queue"})
//    这个类的这个方法才能接受hello-java-queue消息
    @RabbitHandler
    public void receiveMessage(Message message, OrderReturnReasonEntity content, Channel channel) {
    
    

        //拿到消息体
//        byte[] body = message.getBody();
        //拿到消息头
//        MessageProperties properties = message.getMessageProperties();

        System.out.println("接收到消息:" + content);

        //消息处理完 手动确认  deliveryTag在Channel内按顺序自增
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag->" + deliveryTag);

        try {
    
    
            if (deliveryTag % 2 == 0) {
    
    
                //确认签收 队列删除该消息 false非批量模式
                channel.basicAck(deliveryTag, false);
                 System.out.println("签收了货物");
            } else {
    
    
                //拒收退货 第三个参数 -> true:重新入队 false:丢弃
                channel.basicNack(deliveryTag, false, true);
                 System.out.println("没有签收货物");
            }
        } catch (IOException e) {
    
    
            //网络中断
        }
    }

メッセージの信頼性を確保する方法-メッセージの損失、メッセージの重複
1、メッセージの損失:

  • メッセージが送信された後、ネットワークの問題のためにサーバーに到達しませんでした。
  • 優れたフォールトトレラント方式(try-catch)を使用すると、メッセージの送信時にネットワーク障害が発生する可能性があります。障害が発生した後は、データベースに記録できる再試行メカニズムが必要です。定期的なスキャンと再送を使用する
  • 各メッセージのステータスがサーバーによって受信されたかどうかを記録する必要があるかどうか、適切なログ記録を作成します。
  • 定期的に再送信することをお勧めします。メッセージが正常に送信されない場合は、データベーススキャンの失敗したメッセージを定期的に取得して再送信します。
    メッセージがBrokeに到着すると、Brokeはメッセージをディスク(永続性)に書き込んで成功したと見なす必要があります。この時点で、Brokeは永続性を完了しておらず、マシンはダウンしています。
  • 公開では、成功したメッセージを確認し、データベースメッセージのステータスを変更するための確認メカニズムも追加する必要があります。
  • 自動ACK状態では、コンシューマーはメッセージを受信しますが、メッセージは時間内に失敗します。
    手動ACKをオンにする必要があります。消費は、消費が成功した後にのみ削除されます。失敗した場合、または処理してチームに再参加する時間がない場合は、noAckが処理されます。

2.繰り返し消費

  • 消費が成功した場合、トランザクションガールが送信されています。ackの場合、マシンはダウンしており、ackは成功していません。ブローカーのメッセージはunackからreadyに変更され、他のコンシューマーに送信されます。
  • 消費が失敗した場合、再試行メカニズムにより、メッセージは自動的に再送信されます。
  • 消費が成功すると、cakがクラッシュし、メッセージがunackからreadyに変わり、Brokeが再送信します。
    たとえば、消費者ビジネスの消費インターフェイスは、べき等になるように設計する必要があります。

おすすめ

転載: blog.csdn.net/u014496893/article/details/114104540