RabbitMq 實現一條消息被多個客戶端消費

RabbitMQ接口分爲 通道、交換器、路由、和隊列4個模塊

生產者/消費者連接消息隊列,創建通道,創建交換器,創建隊列。把隊列綁定到交換器上。

生產者發送消息到交換器,交換器根據路由把消息轉到不同隊列上。

消費者創建通道後,指定交換器,路由,和隊列。

交換器:

DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers");

測試代碼

package com.cn.rabbitmq.cp1.exchange;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.impl.ErrorOnWriteListener;

import java.io.IOException;

public class BaseQueue {

    public static Connection getConnection()  throws  Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setErrorOnWriteListener(new ErrorOnWriteListener() {
            public void handle(Connection connection, IOException exception) throws IOException {

            }
        });
        return factory.newConnection();
    }

}

服務端發送消息:

package com.cn.rabbitmq.cp1.exchange.topic;

import com.cn.rabbitmq.cp1.exchange.BaseQueue;
import com.rabbitmq.client.*;

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

/**
 *  隊列參數說明:
 *      隊列名稱  queue :隊列名
 *      是否持久隊列 durable  :true  持久隊列,該隊列將在服務器重新啓動後繼續存在。當然了,隊列的消息內容也會被寫入磁盤
 *      是否獨佔隊列 (單消費者隊列)exclusive : 如果需要消費者獨佔隊列,在隊列創建的時候,設定屬性exclusive爲true。 只能有一個消費者
 *      是否自動刪除 autoDelete :自動刪除隊列,客戶端連接時自動刪除。 服務端會連接會不會刪除?
 *
 *
 *      其他參數  arguments
 *              參數名 	                    目的
 *              x-dead-letter-exchange      死信交換器
 *              x-dead-letter-routing-key 	 死信消息的可選路由鍵
 *              x-expires 	                 隊列在指定毫秒數後被刪除
 *              x-ha-policy 	             創建HA隊列
 *              x-ha-nodes 	                 HA隊列的分佈節點
 *              x-max-length 	             隊列的最大消息數
 *              x-message-ttl 	             毫秒爲單位的消息過期時間,隊列級別
 *              x-max-priority 	             最大優先值爲255的隊列優先排序功能
 *
 */
public class TopicProduct extends BaseQueue {

    public static final  String  TOPIC_EXCHANGE="topic_exchange";  //交換器名稱
    public static final  String  BAK_EXCHANGE_NAME ="bak_exchange"; //備用交換器名稱

    public static final  String TOPIC_TOPIC = "topic_queue";  //隊列名稱

    public static void main(String[] args) throws  Exception {
        Connection connection = getConnection(); //獲取連接
        Channel channel =connection.createChannel(); //創建通道
        Map<String, Object> exchangeArguments = new HashMap();
        exchangeArguments.put("alternate-exchange",BAK_EXCHANGE_NAME); //設置備用交換器 ,備用開啓後,發送失敗和無路由會走備用交換器
        channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC,false,false,exchangeArguments); //創建交換器, 名稱,類型
        //備用交換器
        channel.exchangeDeclare(BAK_EXCHANGE_NAME,BuiltinExchangeType.FANOUT, true,false,null);


        Map<String, Object> arguments = new HashMap();
        arguments.put("x-expires",45*1000); //隊列在超過一定時間沒有被使用,會被從RabbitMQ中刪除  這裏要注意是否有權限
        arguments.put("x-message-ttl",20*1000);//設定了該隊列所有消息的存活時間,時間單位毫秒
        //創建隊列 當然了,也可以再消費者方創建隊列
        channel.queueDeclare(TOPIC_TOPIC,false,false,false,arguments); //隊列名稱,是否持久隊列,是否獨佔隊列,是否自動刪除,其他參數
        //隊列綁定交換器
        channel.queueBind(TOPIC_TOPIC,TOPIC_EXCHANGE,null,null);//隊列名稱,交換器名稱,路由key,arguments
        channel.confirmSelect();//啓動生產者確認
        // 3種確認方式 channel.waitForConfirms();普通發送方確認模式   channel.waitForConfirmsOrDie();批量確認模式   channel.addConfirmListener異步監聽發送方確認模式
        channel.addConfirmListener(new ConfirmListener() { //發送結果監聽
            public void handleAck(long deliveryTag, boolean multiple) throws IOException { //成功
                System.out.println("消息發送成功");
            }
            public void handleNack(long deliveryTag, boolean multiple) throws IOException { //失敗
                System.out.println("消息發送失敗");
            }
        });
        for (int i=0;i<10;i++) {
            String[] keys = new String[]{"topic.test","topic.mymsg","topic.mytest"};
            Random random = new Random();
            String routingKey =keys[random.nextInt(3)];
            String message="這是個測試數據"+i+"使用的路由是"+routingKey;
            //BasicProperties props消息屬性
            //發送消息到交換器,路由器
            channel.basicPublish(TOPIC_EXCHANGE,routingKey,null,message.getBytes("utf-8")); //交換器名稱,路由鍵,消息屬性,消息內容
            System.out.println("發送了第"+i+"條消息。消息內容爲:"+message);
        }
        //不可路由到隊列的消息監聽
        channel.addReturnListener(new ReturnListener() {
            /**
             * int replyCode, 響應嗎, 路由成沒成功
             * String replyText, 回覆內容
             * String exchange,
             * String routingKey,
             * AMQP.BasicProperties properties,
             * byte[] body 實際的消息體內容
             * @param replyCode
             * @param replyText
             * @param exchange
             * @param routingKey
             * @param properties
             * @param body
             * @throws IOException 跑出ioexception 異常
             */
            public void handleReturn(int replyCode, String replyText,
                                     String exchange, String routingKey,
                                     AMQP.BasicProperties properties,
                                     byte[] body) {
                String message = new String(body);
                System.out.println("RabbitMq返回的replyCode:  "+replyCode+"RabbitMq返回的replyText:  "+replyText);
                System.out.println("RabbitMq返回的exchange:  "+exchange+"RabbitMq返回的routingKey:  "+routingKey);
                System.out.println("RabbitMq返回的message:  "+message);
            }
        });
        //信道關閉監聽
        channel.addShutdownListener(new ShutdownListener() {
            public void shutdownCompleted(ShutdownSignalException cause) {
                System.out.println("關閉了監聽連接");
            }
        });
        //連接關閉監聽   不知道爲什麼創建連接的時候沒有連接創建監聽?
        connection.addShutdownListener(new ShutdownListener() { //連接關閉加你聽
            public void shutdownCompleted(ShutdownSignalException cause) {

            }
        });
        /**
         * 這個監聽器在服務器內存報警或者硬盤報警的情況下,使用發送接口會產生阻塞,如果發送和接受等使用同一個連接,則會都阻塞,因此我們應該爲消費者和生產者使用不同的CachingConnectionFactory,或者設置rabbitTemplate.setUsePublisherConnection(true);
         */
        connection.addBlockedListener(new BlockedListener() { //連接阻塞情況的監聽器
            public void handleBlocked(String reason) throws IOException {
                System.out.println("connection blocked, reason: "+reason);
            }
            public void handleUnblocked() throws IOException { //需要同時解除內存和磁盤的報警纔會收到unblock的消息
                System.out.println("==============================connection unblocked");
            }
        });
        channel.close();
        connection.close();

    }
}

 創建兩個消費端,都是從通道獲取的臨時隊列,並把隊列指定路由綁定到交換器上

public class TopicConsumer1 extends BaseQueue {
    public static void main(String[] args) throws Exception{

        Connection connection = getConnection();
        Channel channel =connection.createChannel();
        String queueName =channel.queueDeclare().getQueue();//從通道獲取隊列
        channel.queueBind(queueName,TopicProduct.TOPIC_EXCHANGE,"topic.mymsg"); //指定路由綁定到交換器

        channel.basicConsume(queueName,true,new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String str = new String(body,"utf-8");
                System.out.println(str);
            }
        });
    }
}

public class TopicConsumer2 extends BaseQueue {
    public static void main(String[] args) throws Exception{

        Connection connection = getConnection();
        Channel channel =connection.createChannel();
        String queueName =channel.queueDeclare().getQueue();//從通道獲取隊列
        channel.queueBind(queueName,TopicProduct.TOPIC_EXCHANGE,"topic.mymsg"); //指定路由綁定到交換器

        channel.basicConsume(queueName,true,new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String str = new String(body,"utf-8");
                System.out.println(str);
            }
        });
    }
}

每個客戶端都會消費一個臨時隊列裏的消息

springboot 下客戶端實現臨時隊列綁定到交換器路由

@Component(value = "testListener")
public 	class TestListener{
		public final static String  ROUTING_KEY        = "test.queue";
	public final static String TEST_EXCHANGE = "topic_exchange;
        //指定監聽,綁定隊列隊列的路由和校驗器
		@RabbitListener(bindings ={@QueueBinding(value = @Queue,
			key = ROUTING_KEY,exchange = @Exchange(value=TEST_EXCHANGE,type = ExchangeTypes.TOPIC))})
		@RabbitHandler
		public void process(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throws IOException {
			System.out.println(message);
			channel.basicAck(deliveryTag,false);
		}
	}

 

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