RabbitMQ消息確認以及return機制

消息確認機制

消息發送出去後,如果想確定消息發送的狀態信息,發送成功或者發送失敗,rabbitmq中有一套完整的檢測機制來檢測這類問題,我們只需要調用即可,具體的檢測機制我們後面再討論,暫且看一下怎麼用代碼先實現:

public class Send {

    private static final String EXCHANGE_NAME = "test_exchange_confirm";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();
        channel.confirmSelect();
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        //send a msg
        String msg = "hello comfirm";
        String routingKey = "confirm.save";
        channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println(" ack");
            }

            @Override
            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("no ack");
            }
        });
        System.out.println("send:"+msg);
//        channel.close();
//        connection.close();  這裏需要註釋,否則就收不到ack
    }
}

消息發送成功後,發送端發送成功後會收到ack,發送失敗後會收到 no ack

消費端寫法和之前的沒什麼區別,我們這次用topic的路由機制來實現一下:

public class ReceiveOne {
    private static final String QUEUE_NAME = "receive1_queue";
    private static final String EXCHANGE_NAME = "test_exchange_confirm";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.basicQos(1);
        String routingKey = "confirm.#";  //看上一節的topic路由機制
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, routingKey);

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [ConsumerOne is] Received '" + message + "'");
        };
        boolean autoAck = true;  //開啓自動應答
        channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> { });
    }
}

Return 機制

return Listener用於處理一些不可路由的消息(在某些情況下,如果我們發送消息的時候,當前的exchange或者routeKey路由不到的時候,這個時候如果我們需要監聽這種不可到達的消息,就要使用Return Listener)


消費端自定義監聽:

生產者:

public class Produces {

    private static final String EXCHANGE_NAME = "test_undedine_consumer";

    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();

        String routingKey = "consumer.save";
        String msg = "Hello undefine consumer";

        for(int i = 0; i<5; i++){
            channel.basicPublish(EXCHANGE_NAME,routingKey,true,null,msg.getBytes());
        }

    }

消費者:

public class Consumers {

    private static final String QUEUE_NAME = "test_consumer_queue";
    private static final String EXCHANGE_NAME = "test_undedine_consumer";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();

        String routineKey = "consumer.#";
        channel.exchangeDeclare(EXCHANGE_NAME,"topic",true,false,null);
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,routineKey);
        channel.basicConsume(QUEUE_NAME,true,new Myconsumer(channel));
    }
}

自定義內容:

public class Myconsumer extends DefaultConsumer {
    public Myconsumer(Channel channel) {
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("---------");
        System.out.println(consumerTag);
    }
}

消費端限流

生產者:(和上面的沒什麼區別)

public class Produces {

    private static final String EXCHANGE_NAME = "test_limit_flu";

    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();

        String routingKey = "qos.save";
        String msg = "limit fluency";

        for(int i = 0; i<5; i++){
            channel.basicPublish(EXCHANGE_NAME,routingKey,true,null,msg.getBytes());
        }

    }
}

消費者:

public class Consumers {

    private static final String QUEUE_NAME = "test_qos";
    private static final String EXCHANGE_NAME = "test_limit_flu";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();

        String routineKey = "qos.#";
        channel.exchangeDeclare(EXCHANGE_NAME,"topic",true,false,null);
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,routineKey);
        boolean autoAck = false;
        channel.basicQos(0,1,false);
        channel.basicConsume(QUEUE_NAME,autoAck,new Myconsumer(channel));


    }
}

區別在自定義處理裏面:

public class Myconsumer extends DefaultConsumer {
    private Channel channel;
    public Myconsumer(Channel channel) {
        super(channel);
        this.channel = channel;
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("---------");
        System.out.println(consumerTag);
        //channel.basicAck(envelope.getDeliveryTag(),false);
        //這裏之所以是false,前面我們設置的是  channel.basicQos(0,1,false); 表示只接受一個,如果改爲1以上,就要相應的改爲true
    }
}

當我們把channel.basicAck(envelope.getDeliveryTag(),false); 這一段屏蔽時,我們在後臺看到的是:
在這裏插入圖片描述
總共是五條消息,只有一條能被消費,其他的四條需要等這條消費完後發ack響應才能,繼續向前傳遞。屏蔽的那段代碼就是手動響應ACK。


重回隊列

死信隊列

當一個消息在隊列中沒人去消費它,它就會被重新publish到另外一個Exchange,這個Exchange就是死信隊列。

死信隊列有哪幾種情況?

  • 當消息被拒絕(basic.nack),並且拒絕重回隊列
  • TTL過期了
  • 隊列達到最大的長度

下面用代碼實現一下:

生產端:

public class Produces {

    private static final String EXCHANGE_NAME = "test_dlx";

    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();

        String routingKey = "dlx.save";

        String msg = "hello dlx";

        channel.basicPublish(EXCHANGE_NAME,routingKey,true,null,msg.getBytes());
    }
}

消費端:

public class Consumers {

    private static final String QUEUE_NAME = "test_dlx.queue";
    private static final String EXCHANGE_NAME = "test_dlx";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();

        String routineKey = "dlx.#";
        channel.exchangeDeclare(EXCHANGE_NAME,"topic",true,false,null);
        // 下面是配置死信隊列
        Map<String,Object> arg = new HashMap<>();
        arg.put("x-dead-letter-exchange","dlx.exchange");
        channel.queueDeclare(QUEUE_NAME,true,false,false,arg); //這裏表明了路由失敗的話,轉發到死信隊列上
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,routineKey);
        // 死信隊列的聲明
        channel.exchangeDeclare("dlx.exchange","topic",true,false,null);
        channel.queueDeclare("dlx.queue",true,false,false,null);
        channel.queueBind("dlx.queue","dlx.exchange","#");
        boolean autoAck = true;
        channel.basicQos(0,1,false);
        channel.basicConsume(QUEUE_NAME,autoAck,new Myconsumer(channel));
    }
}

這裏需要說明的是我們定義了一個死信隊列 “dlx.queue”,和dlx.exchange綁定在一起,也就是說消息沒人消費就會路由到死信隊列中去。

public class Myconsumer extends DefaultConsumer {
    private Channel channel;
    public Myconsumer(Channel channel) {
        super(channel);
        this.channel = channel;
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
       // channel.basicAck(envelope.getDeliveryTag(),false);
        //這裏之所以是false,前面我們設置的是  channel.basicQos(0,1,false); 表示只接受一個,如果改爲1以上,就要相應的改爲true
        System.out.println(new String(body));
    }
}

在這裏插入圖片描述
可以看到上面的test_dlx.queue後面有個DLX的標記,也就是說,它裏面的無人消費的消息都會直接轉到dlx.queue中。我們可以在dlx.queue中設置監聽來處理這些消息。

發佈了138 篇原創文章 · 獲贊 42 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章