RabbitMQ(2)---高級使用 RabbitMQ(1)---基本概念及簡單demo

1.ack和限流

ack也就是消息確認簽收,分爲自動簽收和手動簽收。之前的交換機demo中:channel.basicConsume(queueName,true, consumer);  第二個參數就是自動簽收,如果我們要手動簽收則需要改成false,再去消息處理中手動簽收即可

當我們消息隊列中已經積壓了大量消息的時候。這個時候消費者才啓動,,如果是自動簽收的話,就會導致大量消息湧入,可能回到服務剛啓動就宕機。這個時候就可以限制消息數量,使用手動簽收。處理完這一批,再處理下一批。

使用手動簽收,我們還可以在拿到消息,進行不同的業務處理,比如如果消息信息有問題,那就不簽收,移除當前隊列,或者放到其他地方去處理之類的

RabbitMQUtils類的代碼在上一節中:RabbitMQ(1)---基本概念及簡單demo

ack:手動簽收消息:

package com.nijunyang.rabbitmq.ack;

import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;

/**
 * Description:
 * Created by nijunyang on 2020/6/7 13:07
 */
public class AckProducer {

    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        String message = "hello rabbitMQ." + new Random().nextInt(100);

        String exchangeName = "ack.exchange";
        String routingKey = "ack.key";


        Map<String,Object> heads = new HashMap<>();
        heads.put("userName", "zhangsan");

        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
                .deliveryMode(2)//消息持久化
                .contentEncoding("UTF-8")
                .correlationId(UUID.randomUUID().toString())
                .headers(heads)//存放頭信息
                .build();

        channel.basicPublish(exchangeName, routingKey, basicProperties, message.getBytes("utf-8"));
        channel.basicPublish(exchangeName, routingKey, null, message.getBytes("utf-8"));
        RabbitMQUtils.close(channel, connection);
    }
}
package com.nijunyang.rabbitmq.ack;

import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.*;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * Description:
 * Created by nijunyang on 2020/6/7 13:07
 */
public class AckConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "ack.exchange";
        String exchangeType = "direct";
        String routingKey = "ack.key";
        String queueName = "ack.queue";

        channel.exchangeDeclare(exchangeName, exchangeType);
        channel.queueDeclare(queueName,true,false,false,null);
        channel.queueBind(queueName, exchangeName, routingKey);


        Consumer consumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                try {
                    Object userName = properties.getHeaders().get("userName");
                    if (!StringUtils.isEmpty(userName)) {
                        //用發送時候放的 頭信息模擬業務問題
                        String message = new String(body, "UTF-8");
                        System.out.println(message);
                        //手動簽收消息
                        channel.basicAck(envelope.getDeliveryTag(),false);
                    }
                    else {
                        throw new RuntimeException();
                    }
                }catch (Exception e) {
                    //requeue參數 true 重回隊列,,false不重回隊列, 或者做其他處理
                    channel.basicNack(envelope.getDeliveryTag(),false,false);
                }
            }
        };

        //autoAck參數 true:開啓自動簽收,false:關閉自動簽收功能
        channel.basicConsume(queueName,false, consumer);


    }
}

限流的話只需要多一個限制:channel.basicQos(0,5,false);  每次只會處理5條消息,簽收完了,在處理後面的

package com.nijunyang.rabbitmq.limit;

import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;

/**
 * Description:
 * Created by nijunyang on 2020/6/7 13:07
 */
public class LimitProducer {

    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();


        String exchangeName = "limit.exchange";
        String routingKey = "limit.key";


        for (int i = 0; i < 20; i++) {
            String message = "limit rabbitMQ." + i;
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes("utf-8"));
        }

        RabbitMQUtils.close(channel, connection);
    }
}
package com.nijunyang.rabbitmq.limit;

import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.*;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * Description:
 * Created by nijunyang on 2020/6/7 13:07
 */
public class LimitConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "limit.exchange";
        String exchangeType = "direct";
        String routingKey = "limit.key";
        String queueName = "limit.queue";

        channel.exchangeDeclare(exchangeName, exchangeType);
        channel.queueDeclare(queueName,true,false,false,null);
        channel.queueBind(queueName, exchangeName, routingKey);

        /**
         * 限流設置:
         * prefetchSize:每條消息大小的設置
         * prefetchCount:標識每次推送多少條消息
         * global:false標識channel級別的  true:標識消費的級別的
         */
        channel.basicQos(0,5,false);

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(message);
                //手動簽收消息, 否則就會一直阻塞了
                channel.basicAck(envelope.getDeliveryTag(), false);

            }
        };

        //autoAck參數 true:開啓自動簽收,false:關閉自動簽收功能
        channel.basicConsume(queueName,false, consumer);


    }
}

 

2.消息投遞確認,開啓這個模式之後 消息投遞了之後 不能關閉連接,因爲監聽是綁定在channel上面的

開啓消息投遞確認模式,,在消息發送者上面綁定一個監聽,消息投遞成功或者失敗回調對應方法。

package com.nijunyang.rabbitmq.confirm;

import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * Description:
 * Created by nijunyang on 2020/6/7 19:53
 */
public class ConfirmProducer {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        //設置消息投遞模式(確認模式)
        channel.confirmSelect();
        /**
         * 消息確認監聽綁定
         */
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息投遞成功");
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息投遞失敗");
            }
        });

        String exchangeName = "confirm.exchange";
        String routingKey = "confirm.key";

        for (int i = 0; i < 20; i++) {
            String message = "limit rabbitMQ." + i;
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes("utf-8"));
        }
        //設置了消息投遞確認就不能關閉channel和連接了
//        RabbitMQUtils.close(channel, connection);
    }
}

 

3.不可達消息處理:有些消息發送之後,由於設置的原因,不能正常的路由到隊列上面。

和消息投遞確認差不多,只不過是在生產者的channel上面綁定一個ReturnListener(channel.addReturnListener(new RetrunListener())),然後投遞消息的時候使用這個方法channel.basicPublish(exchangeName,routingKey,true,null, message.getBytes()),相比之前的投遞方式多了一個布爾類型的mandatory參數。如果true那麼就會調用的綁定的ReturnListener,實現的方法,如果是false那麼就會直接刪除這個消息。

4.死信隊列。專門用來接收沒有消費的消息的隊列。消息發送到正常隊列上面但是沒有被消費,就會被轉發到死信隊列上面。所以說死信隊列是和一個正常隊列綁定的。消息變成死信的幾種情況:1.消息被拒絕(basicNack   basicReject)並且重回隊裏設置的false,2.消息設置了過期時間,時間到了也沒有被消費,3.隊列已經達到最大長度,後面進來的消息直接轉到死信隊列。死信隊列也是一個正常的交換機和隊列。

 

package com.nijunyang.rabbitmq.deadqueue;

import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;

import java.io.IOException;

/**
 * Description:
 * Created by nijunyang on 2020/6/7 20:48
 */
public class DeadQueueProducer {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        String exchangeName = "normal.exchange";
        String routingKey = "normal.key";

        //設置消息的過期時間10s
        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
                .deliveryMode(2)
                .expiration("10000")
                .build();
        for (int i = 0; i < 20; i++) {
            String message = "dead rabbitMQ." + i;
            channel.basicPublish(exchangeName, routingKey, basicProperties, message.getBytes("utf-8"));
        }
        RabbitMQUtils.close(channel, connection);
    }
}
package com.nijunyang.rabbitmq.deadqueue;

import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Description:
 * Created by nijunyang on 2020/6/7 20:51
 */
public class DeadQueueConsumer {
    public static void main(String[] args) throws Exception{
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        //聲明正常的隊列
        String normalExchangeName = "normal.exchange";
        String exchangeType = "direct";
        String normalQueueName = "normal.queue";
        String routingKey = "normal.key";

        channel.exchangeDeclare(normalExchangeName, exchangeType);

        //申明死信隊列
        String deadExchangeName = "dead.exchange";
        String deadExchangeType = "topic";
        String deadQueueName = "dead.queue";

        Map<String, Object> queueArgs = new HashMap<>();
        //正常隊列上綁定死信隊列信息
        queueArgs.put("x-dead-letter-exchange", deadExchangeName);
        queueArgs.put("x-max-length", 4); //隊列的最大長度
        channel.queueDeclare(normalQueueName,true,false,false, queueArgs);
        channel.queueBind(normalQueueName, normalExchangeName, routingKey);

        //聲明死信隊列
        channel.exchangeDeclare(deadExchangeName, deadExchangeType);
        channel.queueDeclare(deadQueueName,true,false,false,null);
        channel.queueBind(deadQueueName, deadExchangeName,"#");

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(message);
                channel.basicNack(envelope.getDeliveryTag(),false,false); //拒籤
            }
        };
        channel.basicConsume(normalQueueName, false, consumer);

    }
}

 

 

可有看到所有的消息最後都轉到了 死信隊列中去了。這個模式還可以用於延遲隊列。只需要設置正常隊列消息的過期時間,然後轉到死信隊列,,消費者監聽消費死信隊列,就可以實現延時隊列了。

 

 

5.單播消費模式,首先我們要明確一點消費者最終都是從隊列中拿到消息消費的,我們將多個消費者都綁定到同一個隊列上面去,這個時候,隊列消息只會被一個消費者消費,不會重複讓每個消費者都消費。

 

6.多播消費模式,和單播消費差不多,這個時候我們需要申明多個隊列綁定同一個交換機,這樣交換機的信息就會發到多個隊列上面,這樣通過同一個交換機將同一條消息發送到不同的隊列上面去了,也就實現了讓不同的消費者消費了同一條消息了。

 

package com.nijunyang.rabbitmq.deadqueue;

import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
* Description:
* Created by nijunyang on 2020/6/7 20:51
*/
public class DeadQueueConsumer {
public static void main(String[] args) throws Exception{
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();

//聲明正常的隊列
String normalExchangeName = "normal.exchange";
String exchangeType = "direct";
String normalQueueName = "normal.queue";
String routingKey = "normal.key";

channel.exchangeDeclare(normalExchangeName, exchangeType);

//申明死信隊列
String deadExchangeName = "dead.exchange";
String deadExchangeType = "topic";
String deadQueueName = "dead.queue";

Map<String, Object> queueArgs = new HashMap<>();
//正常隊列上綁定死信隊列信息
queueArgs.put("x-dead-letter-exchange", deadExchangeName);
queueArgs.put("x-max-length", 4); //隊列的最大長度
channel.queueDeclare(normalQueueName,true,false,false, queueArgs);
channel.queueBind(normalQueueName, normalExchangeName, routingKey);

//聲明死信隊列
channel.exchangeDeclare(deadExchangeName, deadExchangeType);
channel.queueDeclare(deadQueueName,true,false,false,null);
channel.queueBind(deadQueueName, deadExchangeName,"#");

Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
channel.basicNack(envelope.getDeliveryTag(),false,false); //拒籤
}
};
channel.basicConsume(normalQueueName, false, consumer);

}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章