RabbitMq 操作總結

RabbitMq java操作總結

  1. 消息隊列解決了什麼問題?
  1. 異步處理
  2. 應用解耦
  3. 流量削鋒
  4. 日誌處理

 

  1. JAVA操作rabbitmq
  1. Simple 簡單隊列
  2. work queues工作隊列 公平分發 輪詢分發
  3. publish/subscribe發佈訂閱
  4. Routing 路由選擇 通配符模式
  5. Topics 主題
  6. 手動和自動確認消息
  7. 隊列的持久化和非持久化

 

RabbitMq安裝步驟:

https://blog.csdn.net/qq_41950069/article/details/81346675

https://www.cnblogs.com/sam-uncle/p/9050242.html

  1. 先安裝erlong
  2. 再安裝rabbitmq

 

Rabbit mq 教程:

https://blog.csdn.net/hellozpc/article/details/81436980

 

安裝好後rabbitmq後臺登錄地址:http://localhost:15672/

賬號密碼:都是guest

 

Spring boot整合RabbitMq:

https://blog.csdn.net/zhuzhezhuzhe1/article/details/80454956

https://blog.csdn.net/qq_38455201/article/details/80308771

 

SpringBoot整合RabbitMQ之典型應用場景實戰一:

https://blog.csdn.net/u013871100/article/details/82982235

 

springboot+rabbitMq整合開發實戰二:模擬用戶下單的過程:

https://blog.csdn.net/u013871100/article/details/78776390

1、添加用戶

用戶界面

 

Virtual hosts 管理


Virtual hosts 相當於mysql 的 db ,添加的路徑一般以“/”開頭

添加路徑後在上面的表格中就會顯示你添加的路徑,點擊添加的路徑就會進入到下面的界面

給用戶進行授權,選擇添加的用戶進行授權,這樣用戶才能訪問這個地址

 

2、java操作隊列

 

2.1簡單隊列

2.1.1 模型

 

p:消息的生產者

紅色區域:隊列(Queue)

C:消費者

3個對象: 生產者 隊列 消費者

RabbitMQ中的消息都只能存儲在Queue中,生產者(圖中的P)生產消息並最終投遞到Queue中,消費者(圖中的C)可以從Queue中獲取消息並消費。 

2.1.2 獲取MQ連接

Java代碼演示:

1、引入maven依賴

  1. <dependency>  
  2.      <groupId>com.rabbitmq</groupId>  
  3.      <artifactId>amqp-client</artifactId>  
  4.      <version>5.7.3</version>  
  5. </dependency>

2、編寫連接工具類

  1. public class RabbitConnectionUtil {  
  2.     //在rabbitmq頁面自己創建的VirtualHost地址  
  3.     public final static String vHost = "/test_rabbit";  
  4.   
  5.     /** 
  6.      * 創建連接方法 
  7.      * @return 連接 
  8.      */  
  9.     public static Connection getConnection() throws Exception {  
  10.         //創建連接工廠  
  11.         ConnectionFactory connectionFactory = new ConnectionFactory();  
  12.         //設置服務器地址  
  13.         connectionFactory.setHost("127.0.0.1");  
  14.         //設置AHQP 連接端口  
  15.         connectionFactory.setPort(5672);  
  16.         //設置VirtualHost 地址  
  17.         connectionFactory.setVirtualHost(vHost);  
  18.         //設置用戶名  
  19.         connectionFactory.setUsername("guest");  
  20.         //設置密碼  
  21.         connectionFactory.setPassword("guest");  
  22.         return connectionFactory.newConnection();  
  23.     }  
  24. }  

2.1.3 生產者生產消息

1、首先需要定義隊列的名稱,這裏我將隊列名稱定義提取出來作爲常量,生產者和消費者都是在這個隊列中發送和接收消息

  1. public class RabbitConstant {  
  2.     public final static String QUEUE_NAME = "test_simple_queue";  

 

2、發送隊列消息

  1. public static void main(String[] args) throws Exception {  
  2.        //創建連接  
  3.        Connection connection = RabbitConnectionUtil.getConnection();  
  4.        //創建通道  
  5.        Channel channel = connection.createChannel();  
  6.        //創建隊列聲明  
  7.        channel.queueDeclare(RabbitConstant.QUEUE_NAME,false,false,false,null);  
  8.        //向隊列中推送消息  
  9.        String msg = "hello simple !";  
  10.        channel.basicPublish("",RabbitConstant.QUEUE_NAME,null,msg.getBytes());  
  11.        System.out.println("send simple success:"+msg);  
  12.        //關閉通道和連接  
  13.        channel.close();  
  14.        connection.close();  
  15. }

2.1.4 消費者接收消息

1、老版本支持的方法,親測3.x之類的版本支持這種方式,在5.版本已經不支持這種方式

  1. public static void main(String[] args) throws Exception {  
  2.        //創建連接  
  3.        Connection connection = RabbitConnectionUtil.getConnection();  
  4.        //創建通道  
  5.        Channel channel = connection.createChannel();  
  6.        //創建隊列消費者  
  7.        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);  
  8.        //監聽隊列  
  9.        channel.basicConsume(RabbitConstant.QUEUE_NAME,true,queueingConsumer);  
  10.        while (true){  
  11.            Delivery delivery = queueingConsumer.nextDelivery();  
  12.            String msg = new String(delivery.getBody());  
  13.            System.out.println("Recv success:"+msg);  
  14.        }  
  15. }  

 

2、新版本支持方式

  1. public static void main(String[] args) throws Exception {  
  2.      //創建連接  
  3.      Connection connection = RabbitConnectionUtil.getConnection();  
  4.      //創建通道  
  5.      Channel channel = connection.createChannel();  
  6.      //創建隊列聲明  
  7.      channel.queueDeclare(RabbitConstant.QUEUE_NAME,false,false,false,null);  
  8.      //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  9.      Consumer consumer = new DefaultConsumer(channel) {  
  10.          //獲取到達的消息  
  11.          @Override  
  12.          public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  13.              super.handleDelivery(consumerTag, envelope, properties, body);  
  14.              //打印接收到的內容  
  15.              String msg = new String(body,"utf-8");  
  16.              System.out.println("Recv success:"+msg);  
  17.          }  
  18.      };  
  19.      //監聽隊列  
  20.      channel.basicConsume(RabbitConstant.QUEUE_NAME,true,consumer);  

2.1.5 簡單隊列的不足

耦合性高,生產者--對應消費者(如果我想有多個消費者去消費隊列中消息,這時候就不行了)隊列名稱變更 這時候得同時變更

 

2.2 工作隊列(輪詢分發 round-robin

2.2.1 模型

 

多個消費者可以訂閱同一個Queue,這時Queue中的消息會被平均分攤給多個消費者進行處理,而不是每個消費者都收到所有的消息並處理。

 

爲什麼會出現工作隊列?

Simple隊列是一一對應的,而且我們實際開發,生產者發送消息是毫不費力的,而消費者一般是要跟業務相結合的,消費者接收到消息之後就需要處理,處理需要花費時間,這時候隊列就會積壓了很多消息。

 

2.2.2 生產者

1、首先需要定義隊列的名稱,這裏我將隊列名稱定義提取出來作爲常量,生產者和消費者都是在這個隊列中發送和接收消息

  1. public class RabbitConstant {  
  2.     public final static String QUEUE_NAME_SIMPLE = "test_simple_queue";  
  3.     public final static String QUEUE_NAME_WORK = "test_work_queue";  
  4. }  

2、創建生產者

  1. public static void main(String[] args) throws Exception {  
  2.       //創建連接  
  3.       Connection connection = RabbitConnectionUtil.getConnection();  
  4.       //創建通道  
  5.       Channel channel = connection.createChannel();  
  6.       //創建隊列聲明  
  7.       channel.queueDeclare(RabbitConstant.QUEUE_NAME_WORK,false,false,false,null);  
  8.       //向隊列中推送消息  
  9.       for(int i = 0; i<50;i++){  
  10.           String msg = "hello work["+i+"] !";  
  11.           channel.basicPublish("",RabbitConstant.QUEUE_NAME_WORK,null,msg.getBytes());  
  12.           System.out.println("send work success:"+msg);  
  13.           Thread.sleep(20);  
  14.       }  
  15.       //關閉通道和連接  
  16.       channel.close();  
  17.       connection.close();  
  18. }  

2.2.3 消費者

消費者1:

  1. public class Recv1 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE,false,false,false,null);  
  9.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  10.         Consumer consumer = new DefaultConsumer(channel) {  
  11.             //獲取到達的消息  
  12.             @Override  
  13.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  14.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  15.                 //打印接收到的內容  
  16.                 String msg = new String(body,"utf-8");  
  17.                 System.out.println("【1】Recv success:"+msg);  
  18.                 try {  
  19.                     Thread.sleep(600);  
  20.                 } catch (InterruptedException e) {  
  21.                     e.printStackTrace();  
  22.                 }finally {  
  23.                     System.out.println("【1】 done !");  
  24.                 }  
  25.             }  
  26.         };  
  27.         //監聽隊列  
  28.         Boolean autoAck = true;  
  29.         channel.basicConsume(RabbitConstant.QUEUE_NAME_SIMPLE,autoAck,consumer);  
  30.     }  

消費者2:

  1. public class Recv2 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE,false,false,false,null);  
  9.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  10.         Consumer consumer = new DefaultConsumer(channel) {  
  11.             //獲取到達的消息  
  12.             @Override  
  13.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  14.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  15.                 //打印接收到的內容  
  16.                 String msg = new String(body,"utf-8");  
  17.                 System.out.println("【2】Recv success:"+msg);  
  18.                 try {  
  19.                     Thread.sleep(200);  
  20.                 } catch (InterruptedException e) {  
  21.                     e.printStackTrace();  
  22.                 }finally {  
  23.                     System.out.println("【2】 done !");  
  24.                 }  
  25.             }  
  26.         };  
  27.         //監聽隊列  
  28.         Boolean autoAck = true;  
  29.         channel.basicConsume(RabbitConstant.QUEUE_NAME_SIMPLE,autoAck,consumer);  
  30.     }  
  31. }  

2.2.4 現象

消費者1休眠時間在600ms和消費者2休眠時間在200ms的情況下,消費者1和消費者2處理的消息是一樣多的
消費者1:偶數
消費者2:奇數
這種方式叫做輪詢分發(round-robin)結果就是不管誰忙活誰清閒,都不會多給一個消息,任務總是你一個我一個

 

2.3 工作隊列(公平分發fail dipatch)

2.3.1 生產者

  1. public class send {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_WORK,false,false,false,null);  
  9.         //在每個消費者發送確認消息之前,消息隊列不發送下一個消息到消費者,1次只處理1個消息  
  10.         //限制發送給同一個消費者 不得超過一次  
  11.         int prefethCount  = 1;  
  12.         channel.basicQos(prefethCount);  
  13.         //向隊列中推送消息  
  14.         for(int i = 0; i<50;i++){  
  15.             String msg = "hello work["+i+"] !";  
  16.             channel.basicPublish("",RabbitConstant.QUEUE_NAME_WORK,null,msg.getBytes());  
  17.             System.out.println("send work success:"+msg);  
  18.             Thread.sleep(20);  
  19.         }  
  20.         //關閉通道和連接  
  21.         channel.close();  
  22.         connection.close();  
  23.     }  

2.3.2 消費者

消費者1:

  1. public class Recv1 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         final Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE,false,false,false,null);  
  9.         //保證一次只分發一個  
  10.         channel.basicQos(1);  
  11.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  12.         Consumer consumer = new DefaultConsumer(channel) {  
  13.             //獲取到達的消息  
  14.             @Override  
  15.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  16.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  17.                 //打印接收到的內容  
  18.                 String msg = new String(body,"utf-8");  
  19.                 System.out.println("【1】Recv success:"+msg);  
  20.                 try {  
  21.                     Thread.sleep(600);  
  22.                 } catch (InterruptedException e) {  
  23.                     e.printStackTrace();  
  24.                 }finally {  
  25.                     System.out.println("【1】 done !");  
  26.                     //手動回執消息  
  27.                     channel.basicAck(envelope.getDeliveryTag(),false);  
  28.                 }  
  29.             }  
  30.         };  
  31.         //監聽隊列  
  32.         Boolean autoAck = false;//自動應答改爲false  
  33.         channel.basicConsume(RabbitConstant.QUEUE_NAME_SIMPLE,autoAck,consumer);  
  34.     }  
  35. }  

 

消費者2:

  1. public class Recv2 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         final Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE,false,false,false,null);  
  9.         //保證一次只分發一個  
  10.         channel.basicQos(1);  
  11.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  12.         Consumer consumer = new DefaultConsumer(channel) {  
  13.             //獲取到達的消息  
  14.             @Override  
  15.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  16.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  17.                 //打印接收到的內容  
  18.                 String msg = new String(body,"utf-8");  
  19.                 System.out.println("【2】Recv success:"+msg);  
  20.                 try {  
  21.                     Thread.sleep(200);  
  22.                 } catch (InterruptedException e) {  
  23.                     e.printStackTrace();  
  24.                 }finally {  
  25.                     System.out.println("【2】 done !");  
  26.                     //手動回執消息  
  27.                     channel.basicAck(envelope.getDeliveryTag(),false);  
  28.                 }  
  29.             }  
  30.         };  
  31.         //監聽隊列  
  32.         Boolean autoAck = false;//自動應答改爲false  
  33.        channel.basicConsume(RabbitConstant.QUEUE_NAME_SIMPLE,autoAck,consumer);  
  34.     }  

2.3.3 現象

消費者2處理的消息比消費者1多,能者多勞。因爲在消費者1中設置的是每次處理完一個消息休眠600ms,而消費者2是休眠200毫秒,所以消費者2比消費者1多。

 

2.4 消息應答與消息持久化

2.4.1 消息應答

  1. //監聽隊列  
  2. Boolean autoAck = false;//自動應答改爲false  
  3. channel.basicConsume(RabbitConstant.QUEUE_NAME_SIMPLE,autoAck,consumer);

 

Boolean autoAck = true;(自動確認模式),一旦rabbitmq將消息分發給消費者,就會從內存中刪除

這種情況下,如果殺死正在執行的消費者,就會丟失正在處理的消息

 

Boolean autoAck = false;(手動模式),如果有1個消費者掛掉,就會交付給其他消費者,rabbitmq支持消息應答,消費者發送一個消息應答,告訴rabbitmq這個消息我已經處理完成你可以刪了,然後rabbitmq就會刪除內存中的消息

 

消息應答默認是打開的,false

 

Message acknowledgment:(消息應答)

大家想,如果我們的rabbitmq掛了,我們的消息依然會丟失

2.4.2 消息的持久化

  1. //創建隊列聲明  
  2. boolean durale = false;  
  3. channel.queueDeclare(RabbitConstant.QUEUE_NAME_WORK,durale,false,false,null);  

durale:表示是否持久化,false表示不進行持久化,true表示持久化  

我們將程序中的boolean durale = false;改成true是不可以的,儘管代碼是正確的,它也不會執行成功,因爲我們已經定義了一個叫做“test_work_queue”的隊列,這個queue (隊列) 是未持久化的,rabbitmq不允許重新定義(不同參數)一個已經存在的隊列。如果需要重新定義這個隊列的參數,唯一的做法就是進入rabbitmq將這個隊列刪除,再重新啓動java程序,這時就可以重新創建一個修改後參數的隊列。

 

2.4.3 queueDeclare方法 隊列聲明

  1. Queue.DeclareOk queueDeclare (String queue , boolean durable , boolean exclusive , boolean autoDelete , Map arguments) throws IOException;   

該方法有5各參數:

queue 隊列的名稱

durable: 設置是否持久化。爲 true 則設置隊列爲持久化。持久化的隊列會存盤,在 服務器重啓的時候可以保證不丟失相關信息。

exclusive 設置是否排他。爲 true 則設置隊列爲排他的。如果一個隊列被聲明爲排 他隊列,該隊列僅對首次聲明它的連接可見,並在連接斷開時自動刪除。這裏需要注意 三點:排他隊列是基於連接( Connection) 可見的,同 個連接的不同信道 (Channel) 是可以同時訪問同一連接創建的排他隊列; "首次"是指如果 個連接己經聲明瞭 排他隊列,其他連接是不允許建立同名的排他隊列的,這個與普通隊列不同:即使該隊 列是持久化的,一旦連接關閉或者客戶端退出,該排他隊列都會被自動刪除,這種隊列 適用於一個客戶端同時發送和讀取消息的應用場景。

autoDelete: 設置是否自動刪除。爲 true 則設置隊列爲自動刪除。自動刪除的前提是: 至少有一個消費者連接到這個隊列,之後所有與這個隊列連接的消費者都斷開時,纔會 自動刪除。不能把這個參數錯誤地理解爲: "當連接到此隊列的所有客戶端斷開時,這 個隊列自動刪除",因爲生產者客戶端創建這個隊列,或者沒有消費者客戶端與這個隊 列連接時,都不會自動刪除這個隊列。

argurnents: 設置隊列的其他一些參數,如 x-rnessage-ttl 、x-expires 、x-rnax-length 、x-rnax-length-bytes、 x-dead-letter-exchange、 x-deadletter-routing-key 、 x-rnax-priorit

 

2.5 訂閱模式(publish_subscribe)

2.5.1 模型

解讀:

1,一個生產者,多個消費者

2,每一個消費者都有自己的隊列

3.生產者沒有直接把消息發送到隊列 而是發到了交換機 轉發器exchange

4,每個對列都要綁定到交換機上

5.生產者發送的消息經過交換機到達隊列,就能實現一個消息被多個消費者消費

 

註冊 --> 郵件 --> 短信

2.5.2 生產者

  1. 首先定義交換機名稱
  1. public class RabbitConstant {  
  2.     public final static String QUEUE_NAME_SIMPLE = "test_simple_queue";  
  3.     public final static String QUEUE_NAME_WORK = "test_work_queue";  
  4.     public final static String EXCHANGE_NAME_FANOUT = "test_fanout_exchange";//交換機名稱  
  5. }  

   

    2.創建生產者

  1. public class send {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //聲明交換機  
  8.         channel.exchangeDeclare(RabbitConstant.EXCHANGE_NAME_FANOUT,"fanout");//分發  
  9.         //向交換機中推送消息  
  10.         String msg = "hello publish subscribe !";  
  11.         channel.basicPublish(RabbitConstant.EXCHANGE_NAME_FANOUT,"", null, msg.getBytes());  
  12.         System.out.println("send publish subscribe success:" + msg);  
  13.   
  14.         //關閉通道和連接  
  15.         channel.close();  
  16.         connection.close();  
  17.     }  
  18. }  

通過控制檯可以看到,並沒有我們發送的消息,消息哪去了?

丟失了,因爲交換機沒有存儲的能力,在rabbitmq裏面只有隊列有存儲能力,因爲這時候還沒有隊列綁定到這個交換機所以數據丟失了。

​​​​​​​2.5.3 消費者

  1. 首先定義綁定到交換機的隊列的名稱
  1. public class RabbitConstant {  
  2.     public final static String QUEUE_NAME_SIMPLE = "test_simple_queue";  
  3.     public final static String QUEUE_NAME_WORK = "test_work_queue";  
  4.     public final static String EXCHANGE_NAME_FANOUT = "test_fanout_exchange";//交換機名稱  
  5.     public final static String QUEUE_NAME_EXCHANGE_BIND_EMIL = "test_queue_fanout_emil";//綁定到交換機的隊列  
  6.     public final static String QUEUE_NAME_EXCHANGE_BIND_SMS = "test_queue_fanout_sms";//綁定到交換機的隊列  
  7. }  

 

2、創建消費者

消費者1:

  1. public class Recv1 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         final Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_EXCHANGE_BIND_EMIL,false,false,false,null);  
  9.         //綁定隊列到交換機  
  10.         channel.queueBind(RabbitConstant.QUEUE_NAME_EXCHANGE_BIND_EMIL,RabbitConstant.EXCHANGE_NAME_FANOUT,"");  
  11.         //保證一次只分發一個  
  12.         channel.basicQos(1);  
  13.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  14.         Consumer consumer = new DefaultConsumer(channel) {  
  15.             //獲取到達的消息  
  16.             @Override  
  17.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  18.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  19.                 //打印接收到的內容  
  20.                 String msg = new String(body,"utf-8");  
  21.                 System.out.println("【1】Recv success:"+msg);  
  22.                 try {  
  23.                     Thread.sleep(10);  
  24.                 } catch (InterruptedException e) {  
  25.                     e.printStackTrace();  
  26.                 }finally {  
  27.                     System.out.println("【1】 done !");  
  28.                     //手動回執消息  
  29.                     channel.basicAck(envelope.getDeliveryTag(),false);  
  30.                 }  
  31.             }  
  32.         };  
  33.         //監聽隊列  
  34.         Boolean autoAck = false;//自動應答改爲false  
  35.         channel.basicConsume(RabbitConstant.QUEUE_NAME_EXCHANGE_BIND_EMIL,autoAck,consumer);  
  36.     }  
  37. }  

 

消費者2:

  1. public class Recv2 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         final Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_EXCHANGE_BIND_SMS,false,false,false,null);  
  9.         //綁定隊列到交換機  
  10.         channel.queueBind(RabbitConstant.QUEUE_NAME_EXCHANGE_BIND_SMS,RabbitConstant.EXCHANGE_NAME_FANOUT,"");  
  11.         //保證一次只分發一個  
  12.         channel.basicQos(1);  
  13.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  14.         Consumer consumer = new DefaultConsumer(channel) {  
  15.             //獲取到達的消息  
  16.             @Override  
  17.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  18.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  19.                 //打印接收到的內容  
  20.                 String msg = new String(body,"utf-8");  
  21.                 System.out.println("【2】Recv success:"+msg);  
  22.                 try {  
  23.                     Thread.sleep(200);  
  24.                 } catch (InterruptedException e) {  
  25.                     e.printStackTrace();  
  26.                 }finally {  
  27.                     System.out.println("【2】 done !");  
  28.                     //手動回執消息  
  29.                     channel.basicAck(envelope.getDeliveryTag(),false);  
  30.                 }  
  31.             }  
  32.         };  
  33.         //監聽隊列  
  34.         Boolean autoAck = false;//自動應答改爲false  
  35.         channel.basicConsume(RabbitConstant.QUEUE_NAME_EXCHANGE_BIND_SMS,autoAck,consumer);  
  36.     }  
  37. }  

 

2.5.4 總結

  1. 在控制界面可以看到有兩個隊列已經綁定到了轉換器上
  1. 測試結果:同一個消息被多個消費者獲取。一個消費者隊列可以有多個消費者實例,只有其中一個消費者實例會消費到消息。這個怎麼說呢,在同一個隊列中呢,只能有一個消費者可以獲得消息,因爲這是點對點,是隊列的特性,誰把這個機會搶走了,這個機會就沒有了。
  1. 發佈訂閱類似廣播,發送方只管向外發送消息,接收方呢只要去聽這個廣播就能得到廣播的消息,但是如果你剛好不在,那麼廣播裏的內容也就錯過了。在rabbitmq中就是綁定到交換機的隊列的消費者可以接收到這個消息。

2.6 exchange (交換機 轉換器)

2.6.1 說明

作用:一方面是接收生產者的消息,另一方面是向隊列推送消息

2.6.1.1 匿名轉發

  1. //發送消息  
  2. String exchange = ""
  3. channel.basicPublish(exchange,RabbitConstant.QUEUE_NAME_WORK,null,msg.getBytes());  

 

匿名轉發:””  (exchange爲空串表示匿名轉發)也就是不指定轉化器 ,也就是在普通的隊列模式中使用

2.6.1.2 不處理路由(Fanout

  1. //聲明交換機  
  2. channel.exchangeDeclare(RabbitConstant.EXCHANGE_NAME_FANOUT,"fanout");//分發  

 

Fanout (不處理路由鍵)

只要綁定到交換機的隊列都能獲取到消息

  1. //向交換機中推送消息  
  2. String routingKey= "";  
  3. channel.basicPublish(RabbitConstant.EXCHANGE_NAME_FANOUT,routingKey, null, msg.getBytes());  

 

不處理路由:”” (routingKey爲空串表示不處理路由)

2.6.1.3 處理路由(Direct

Direct (處理路由鍵)

通過模型我們可以看到,在發送方推送消息時指定路由鍵的名稱,在接收方的隊列必須指定對應的路由鍵才能獲得相應的消息,這就可以讓我們選擇性的訂閱指定消息

發送方:

  1. //聲明交換機  
  2. channel.exchangeDeclare(RabbitConstant.EXCHANGE_NAME_FANOUT,"direct");//分發  
  3. //向交換機中推送消息  
  4. String msg = "hello publish subscribe !";  
  5. String routingKey= "text_routing_key";  
  6. channel.basicPublish(RabbitConstant.EXCHANGE_NAME_FANOUT,routingKey, null, msg.getBytes()); 

接收方:

  1. //綁定隊列到交換機  
  2. String routingKey= "text_routing_key";  
  3. channel.queueBind(RabbitConstant.QUEUE_NAME_EXCHANGE_BIND_EMIL,RabbitConstant.EXCHANGE_NAME_FANOUT,routingKey);  

2.6.1.4 主題(Topic)

將路由鍵和某模式進行匹配

#匹配一個或多個單詞

*匹配一個單詞

比如:goods.*只能匹配到 goods.add , goods.# 則可以匹配到goods.add.chongqing

指定爲主題模式時,可以通過#、*通配符來匹配不同的路由鍵

 

生產者:

  1. /聲明交換機  
  2. channel.exchangeDeclare(RabbitConstant.EXCHANGE_NAME_TOPIC,"topic");//分發  
  3. //向交換機中推送消息  
  4. String msg = "hello publish subscribe !";  
  5. String routingKey = "goods.add";//指定路由鍵  
  6. channel.basicPublish(RabbitConstant.EXCHANGE_NAME_TOPIC,routingKey, null, msg.getBytes());  

接收方:

  1. //綁定隊列到交換機  
  2. String routingKey = "goods.#";//指定路由鍵  
  3. channel.queueBind(RabbitConstant.QUEUE_NAME_TOPIC_TWO,RabbitConstant.EXCHANGE_NAME_TOPIC,routingKey);  

2.6.2 basicPublish 方法 (發送消息)

  1. void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException;  

exchange:交換機的名稱

routingKey:路由鍵,#匹配0個或多個單詞,*匹配一個單詞,在topic exchange做消息轉發用

mandatory:true:如果exchange根據自身類型和消息routeKey無法找到一個符合條件的queue,那麼會調用basic.return方法將消息返還給生產者。false:出現上述情形broker會直接將消息扔掉

immediate:true:如果exchange在將消息route到queue(s)時發現對應的queue上沒有消費者,那麼這條消息不會放入隊列中。當與消息routeKey關聯的所有queue(一個或多個)都沒有消費者時,該消息會通過basic.return方法返還給生產者。
BasicProperties :需要注意的是BasicProperties.deliveryMode,0:不持久化 1:持久化 這裏指的是消息的持久化,配合channel(durable=true),queue(durable)可以實現,即使服務器宕機,消息仍然保留

body:推送的消息內容

簡單來說:mandatory標誌告訴服務器至少將該消息route到一個隊列中,否則將消息返還給生產者;immediate標誌告訴服務器如果該消息關聯的queue上有消費者,則馬上將消息投遞給它,如果所有queue都沒有消費者,直接把消息返還給生產者,不用將消息入隊列等待消費者了。

 

2.7路由模式

2.7.1 模型

通過模型我們可以看到,在發送方推送消息時指定路由鍵的名稱,在接收方的隊列必須指定對應的路由鍵才能獲得相應的消息,這就可以讓我們選擇性的訂閱指定消息

2.7.2 生產者

1、定義路由模式下交換機名稱

  1. public class RabbitConstant {  
  2.     /** 
  3.      * 簡單隊列使用 
  4.      */  
  5.     public final static String QUEUE_NAME_SIMPLE = "test_simple_queue";  
  6.     /** 
  7.      * 工作隊列使用 
  8.      */  
  9.     public final static String QUEUE_NAME_WORK = "test_work_queue";  
  10.     /** 
  11.      * 發佈訂閱使用(不路由) 
  12.      */  
  13.     public final static String EXCHANGE_NAME_FANOUT = "test_fanout_exchange";//交換機名稱  
  14.     public final static String QUEUE_NAME_EXCHANGE_BIND_EMIL = "test_queue_fanout_emil";//綁定到交換機的隊列  
  15.     public final static String QUEUE_NAME_EXCHANGE_BIND_SMS = "test_queue_fanout_sms";//綁定到交換機的隊列  
  16.     /** 
  17.      * 發佈訂閱使用(路由) 
  18.      */  
  19.     public final static String EXCHANGE_NAME_DIRECT = "test_direct_exchange";//交換機名稱  
  20.     public final static String QUEUE_NAME_DIRECT_ONE = "test_queue_direct_one";//綁定到交換機隊列  
  21.     public final static String QUEUE_NAME_DIRECT_TWO = "test_queue_direct_two";//綁定到交換機對列  
  22. }  

2. 創建生產者

  1. public class send {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //聲明交換機  
  8.         channel.exchangeDeclare(RabbitConstant.EXCHANGE_NAME_DIRECT,"direct");//分發  
  9.         //向交換機中推送消息  
  10.         String msg = "hello publish subscribe !";  
  11.         String routingKey = "error";//指定路由鍵  
  12. //        routingKey = "warning";  
  13. //        routingKey = "info";  
  14.         channel.basicPublish(RabbitConstant.EXCHANGE_NAME_DIRECT,routingKey, null, msg.getBytes());  
  15.         System.out.println("send publish subscribe success:" + msg);  
  16.   
  17.         //關閉通道和連接  
  18.         channel.close();  
  19.         connection.close();  
  20.     }  

 

2.7.3 消費者

隊列名稱的定義在上面生產者那裏已經給出

 

消費者1:

  1. public class Recv1 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         final Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_DIRECT_ONE,false,false,false,null);  
  9.         //綁定隊列到交換機  
  10.         String routingKey = "error";//指定路由鍵  
  11.         channel.queueBind(RabbitConstant.QUEUE_NAME_DIRECT_ONE,RabbitConstant.EXCHANGE_NAME_DIRECT,routingKey);  
  12.         //保證一次只分發一個  
  13.         channel.basicQos(1);  
  14.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  15.         Consumer consumer = new DefaultConsumer(channel) {  
  16.             //獲取到達的消息  
  17.             @Override  
  18.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  19.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  20.                 //打印接收到的內容  
  21.                 String msg = new String(body,"utf-8");  
  22.                 System.out.println("【1】Recv success:"+msg);  
  23.                 try {  
  24.                     Thread.sleep(600);  
  25.                 } catch (InterruptedException e) {  
  26.                     e.printStackTrace();  
  27.                 }finally {  
  28.                     System.out.println("【1】 done !");  
  29.                     //手動回執消息  
  30.                     channel.basicAck(envelope.getDeliveryTag(),false);  
  31.                 }  
  32.             }  
  33.         };  
  34.         //監聽隊列  
  35.         Boolean autoAck = false;//自動應答改爲false  
  36.         channel.basicConsume(RabbitConstant.QUEUE_NAME_DIRECT_ONE,autoAck,consumer);  
  37.     }  
  38. }  

 

消費者2:

  1. public class Recv2 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         final Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_DIRECT_TWO,false,false,false,null);  
  9.         //綁定隊列到交換機  
  10.         String routingKey = "error";//指定路由鍵  
  11.         channel.queueBind(RabbitConstant.QUEUE_NAME_DIRECT_TWO,RabbitConstant.EXCHANGE_NAME_DIRECT,routingKey);  
  12.         routingKey = "info";  
  13.         channel.queueBind(RabbitConstant.QUEUE_NAME_DIRECT_TWO,RabbitConstant.EXCHANGE_NAME_DIRECT,routingKey);  
  14.         routingKey = "warning";  
  15.         channel.queueBind(RabbitConstant.QUEUE_NAME_DIRECT_TWO,RabbitConstant.EXCHANGE_NAME_DIRECT,routingKey);  
  16.         //保證一次只分發一個  
  17.         channel.basicQos(1);  
  18.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  19.         Consumer consumer = new DefaultConsumer(channel) {  
  20.             //獲取到達的消息  
  21.             @Override  
  22.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  23.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  24.                 //打印接收到的內容  
  25.                 String msg = new String(body,"utf-8");  
  26.                 System.out.println("【2】Recv success:"+msg);  
  27.                 try {  
  28.                     Thread.sleep(200);  
  29.                 } catch (InterruptedException e) {  
  30.                     e.printStackTrace();  
  31.                 }finally {  
  32.                     System.out.println("【2】 done !");  
  33.                     //手動回執消息  
  34.                     channel.basicAck(envelope.getDeliveryTag(),false);  
  35.                 }  
  36.             }  
  37.         };  
  38.         //監聽隊列  
  39.         Boolean autoAck = false;//自動應答改爲false  
  40.         channel.basicConsume(RabbitConstant.QUEUE_NAME_DIRECT_TWO,autoAck,consumer);  
  41.     }  

 

2.7.4 總結

通過消費者代碼我們可以看到,消費者1只能接收到路由鍵是“error”的消息,消費者2可以接收到路由鍵是“error”、“info”、“warning”的消息。

我們通過改變生產者中的路由鍵名稱就能指定到哪些消費者可以接收消息了。

 

2.8 主題模式

2.8.1 模型

Topic Exchange-將路由鍵和某模式進行匹配。此時隊列需要綁定要一個模式上,符號"#"匹配一個或多個詞,符號匹配不多不少一個詞。因此" audit.# "能夠匹配到"audit.irs.corporate",但是" audit.* "只會匹配到"auditirs.irs"。

 

2.8.2 生產者

1、定義主題模式下轉換器名稱

  1. public class RabbitConstant {  
  2.     /** 
  3.      * 簡單隊列使用 
  4.      */  
  5.     public final static String QUEUE_NAME_SIMPLE = "test_simple_queue";  
  6.     /** 
  7.      * 工作隊列使用 
  8.      */  
  9.     public final static String QUEUE_NAME_WORK = "test_work_queue";  
  10.     /** 
  11.      * 發佈訂閱使用(不處理路由模式【訂閱模式】 Fanout) 
  12.      */  
  13.     public final static String EXCHANGE_NAME_FANOUT = "test_fanout_exchange";//交換機名稱  
  14.     public final static String QUEUE_NAME_EXCHANGE_BIND_EMIL = "test_queue_fanout_emil";//綁定到交換機的隊列  
  15.     public final static String QUEUE_NAME_EXCHANGE_BIND_SMS = "test_queue_fanout_sms";//綁定到交換機的隊列  
  16.     /** 
  17.      * 發佈訂閱使用(處理路由模式 Direct) 
  18.      */  
  19.     public final static String EXCHANGE_NAME_DIRECT = "test_direct_exchange";//交換機名稱  
  20.     public final static String QUEUE_NAME_DIRECT_ONE = "test_queue_direct_one";//綁定到交換機隊列  
  21.     public final static String QUEUE_NAME_DIRECT_TWO = "test_queue_direct_two";//綁定到交換機對壘  
  22.   
  23.     /** 
  24.      * 發佈訂閱使用(主題模式 Topic) 
  25.      */  
  26.     public final static String EXCHANGE_NAME_TOPIC = "test_topic_exchange";//交換機名稱  
  27.     public final static String QUEUE_NAME_TOPIC_ONE = "test_queue_topic_one";//綁定到交換機隊列  
  28.     public final static String QUEUE_NAME_TOPIC_TWO = "test_queue_topic_two";//綁定到交換機對壘  
  29. }  

 

2、創建生產者

  1. public class send {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //聲明交換機  
  8.         channel.exchangeDeclare(RabbitConstant.EXCHANGE_NAME_TOPIC,"topic");//分發  
  9.         //向交換機中推送消息  
  10.         String msg = "hello publish subscribe !";  
  11.         String routingKey = "goods.add";//指定路由鍵  
  12. //        routingKey = "goods.update";  
  13.         channel.basicPublish(RabbitConstant.EXCHANGE_NAME_TOPIC,routingKey, null, msg.getBytes());  
  14.         System.out.println("send publish subscribe success:" + msg);  
  15.         //關閉通道和連接  
  16.         channel.close();  
  17.         connection.close();  
  18.     }  

2.8.3 消費者

隊列名稱的定義在上面生產者那裏已經給出

 

消費者1:

  1. public class Recv1 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         final Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_TOPIC_ONE,false,false,false,null);  
  9.         //綁定隊列到交換機  
  10.         String routingKey = "goods.add";//指定路由鍵  
  11.         channel.queueBind(RabbitConstant.QUEUE_NAME_TOPIC_ONE,RabbitConstant.EXCHANGE_NAME_TOPIC,routingKey);  
  12.         //保證一次只分發一個  
  13.         channel.basicQos(1);  
  14.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  15.         Consumer consumer = new DefaultConsumer(channel) {  
  16.             //獲取到達的消息  
  17.             @Override  
  18.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  19.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  20.                 //打印接收到的內容  
  21.                 String msg = new String(body,"utf-8");  
  22.                 System.out.println("【1】Recv success:"+msg);  
  23.                 try {  
  24.                     Thread.sleep(600);  
  25.                 } catch (InterruptedException e) {  
  26.                     e.printStackTrace();  
  27.                 }finally {  
  28.                     System.out.println("【1】 done !");  
  29.                     //手動回執消息  
  30.                     channel.basicAck(envelope.getDeliveryTag(),false);  
  31.                 }  
  32.             }  
  33.         };  
  34.         //監聽隊列  
  35.         Boolean autoAck = false;//自動應答改爲false  
  36.         channel.basicConsume(RabbitConstant.QUEUE_NAME_TOPIC_ONE,autoAck,consumer);  
  37.     }  
  38. }  

 

消費者2:

  1. public class Recv2 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         final Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_TOPIC_TWO,false,false,false,null);  
  9.         //綁定隊列到交換機  
  10.         String routingKey = "goods.#";//指定路由鍵  
  11.         channel.queueBind(RabbitConstant.QUEUE_NAME_TOPIC_TWO,RabbitConstant.EXCHANGE_NAME_TOPIC,routingKey);  
  12.         //保證一次只分發一個  
  13.         channel.basicQos(1);  
  14.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  15.         Consumer consumer = new DefaultConsumer(channel) {  
  16.             //獲取到達的消息  
  17.             @Override  
  18.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  19.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  20.                 //打印接收到的內容  
  21.                 String msg = new String(body,"utf-8");  
  22.                 System.out.println("【2】Recv success:"+msg);  
  23.                 try {  
  24.                     Thread.sleep(200);  
  25.                 } catch (InterruptedException e) {  
  26.                     e.printStackTrace();  
  27.                 }finally {  
  28.                     System.out.println("【2】 done !");  
  29.                     //手動回執消息  
  30.                     channel.basicAck(envelope.getDeliveryTag(),false);  
  31.                 }  
  32.             }  
  33.         };  
  34.         //監聽隊列  
  35.         Boolean autoAck = false;//自動應答改爲false  
  36.         channel.basicConsume(RabbitConstant.QUEUE_NAME_TOPIC_TWO,autoAck,consumer);  
  37.     }  
  38. }  

 

2.8.4 總結

通過消費者代碼我們可以看到,消費者1只能接收到路由鍵是“goods.add”的消息,消費者2路由鍵爲“goods.#”,可以接收到路由鍵是“goods.add”、“goods.update”、“goods.delete.tt”等含“goods.”前綴的1-n個單詞組成的鍵的消息。

如果路由鍵是“goods.*”的話,只能匹配到前綴爲“goods.”的一個單詞的路由鍵,比如可以匹配“goods.add”,但是不能匹配“goods.delete.tt”。

 

2.9 RabiitMq的消息確認機制(事務+confirm)

在rabbitmq中我們可以通過持久化數據解決rabbitmq服務器異常的數據丟失問題

問題:生產者將消息發送出去之後,消息到底有沒有到達rabbitmq服務器,默認的情況是不知道的

兩種方式:
AMOP實現了事務機制
Confirm模式

 

2.9.1 事務機制

txSelect  txCommit  txRollback
txSelect:用戶將當前channel設置成transation模式,
txCommit:用於提交事務
txRollback:回滾事務

 

2.9.1.1 生產者

  1. public class send {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE,false,false,false,null);  
  9.         try {  
  10.             //開啓事務  
  11.             channel.txSelect();  
  12.             //向隊列中推送消息  
  13.             String msg = "hello tx message!";  
  14.             channel.basicPublish("",RabbitConstant.QUEUE_NAME_SIMPLE,null,msg.getBytes());  
  15.             //事務提交  
  16.             channel.txCommit();  
  17.             System.out.println("send tx success:"+msg);  
  18.         }catch (Exception e){  
  19.             //事務回滾  
  20.             channel.txRollback();  
  21.             System.out.println("send tx message rollback !");  
  22.         }  
  23.         //關閉通道和連接  
  24.         channel.close();  
  25.         connection.close();  
  26.     }  
  27. }  

2.9.1.2 消費者

  1. public class Recv {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE,false,false,false,null);  
  9.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  10.         Consumer consumer = new DefaultConsumer(channel) {  
  11.             //獲取到達的消息  
  12.             @Override  
  13.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  14.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  15.                 //打印接收到的內容  
  16.                 String msg = new String(body,"utf-8");  
  17.                 System.out.println("Recv【tx】 success:"+msg);  
  18.             }  
  19.         };  
  20.         //監聽隊列  
  21.         channel.basicConsume(RabbitConstant.QUEUE_NAME_SIMPLE,true,consumer);  
  22.     }  
  23. }  

2.9.1.3 總結

在生產者處開啓事務處理,如果發生異常則消息將不會進行提交,而是會進行回滾,那麼消費者端是接收不到消息的。這個事務處理機制因爲連接增加了,降低了消息服務器mq的吞吐量。

 

2.9.2 confirm模式

2.9.2.1 實現原理

生產者將信道設置成confirm模式,一旦信道進入confirm模式,所有在該信道上面發的消息都會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊列之後,broker就會發送一個確認給生產者(包含消息的唯一ID),這就使得生產者知道消息已經確到達目的隊列了,如果消息和隊列是可持久化的,那麼確認消息會將消息寫入磁盤之後發出,broker回傳給生產者的確認消息中deliver-tag域包含了確認消息的序列號,此外broker也可以設置basic.ack的multiple域,表示到這個序列號之前的所有消息都已經得到了處理。

 

Confirm最大的好處是異步處理的,不用等待信道返回確認消息,而繼續發送其他消息

如果mq崩潰異常導致數據丟失會返回一條Nack消息,生產者可以利用回調進行處理

 

開啓confirm:

channel.confirmSelect();

 

編程模式:

       普通模式:

               發一條:waitForConfirms()

               批量發:waitForConfirms()

       異步confirm模式:

               提供一個回調方法

2.9.2.2 生產者(簡單模式)

定義隊列名稱:

  1. public final static String QUEUE_NAME_SIMPLE_CONFIRM = "test_simple_confirm_queue";  

 

1、Confirm 單條:

  1. public class send {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE_CONFIRM, falsefalsefalse, null);  
  9.         //開啓confirm模式  
  10.         channel.confirmSelect();  
  11.         //向隊列中推送消息  
  12.         String msg = "hello confirm message!";  
  13.         channel.basicPublish("", RabbitConstant.QUEUE_NAME_SIMPLE_CONFIRM, null, msg.getBytes()); 
  14.          //確認 
  15.         if(!channel.waitForConfirms()){  
  16.             System.out.println("send confirm failed:" + msg);  
  17.         }else {  
  18.             System.out.println("send confirm success:" + msg);  
  19.         }  
  20.         //關閉通道和連接  
  21.         channel.close();  
  22.         connection.close();  
  23.     }  
  24. }

 

2、批量

  1. public class SendBatch {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE_CONFIRM, falsefalsefalse, null);  
  9.         //開啓confirm模式  
  10.         channel.confirmSelect();  
  11.         //批量向隊列中推送消息  
  12.         String sendMsg = "";  
  13.         for(int i = 0;i<20;i++){  
  14.             sendMsg = "hello confirm message{"+i+"] !";  
  15.             channel.basicPublish("", RabbitConstant.QUEUE_NAME_SIMPLE_CONFIRM, null, sendMsg.getBytes());  
  16.         }  
  17.        //確認
  18.         if(!channel.waitForConfirms()){  
  19.             System.out.println("Send confirm failed:");  
  20.         }else {  
  21.             System.out.println("Send confirm success:");  
  22.         }  
  23.         //關閉通道和連接  
  24.         channel.close();  
  25.         connection.close();  
  26.     }  

 

發完消息在進行確認

 

2.9.2.3 生產者(異步模式)

Channel對象提供的Confirmlistener()回調方法只包含deliveryTag (當前Chanel發出的消息序號),我們需要自己爲每一個Channel維護一個unconfirm的消息序號集合,每 publish一條數據,集合中元素加1,每回調一次handleAck方法, unconfirm集合刪掉相應的一條(multiple=false)或多條(multiple=true)記錄。從程序運行效率上看,這個unconfirm集合最好採用有序集合SortedSet存儲結構。

 

生產者:

  1. public class Send {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE_CONFIRM, falsefalsefalse, null);  
  9.         //開啓confirm模式  
  10.         channel.confirmSelect();  
  11.         //未確認的消息標識  
  12.         final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());  
  13.         channel.addConfirmListener(new ConfirmListener(){  
  14.             //沒有問題的handleAck  
  15.             public void handleAck(long deliveryTag, boolean multiple) {  
  16.                 if(multiple){  
  17.                     System.out.println("----handleAck----multiple");  
  18.                     confirmSet.headSet(deliveryTag+1).clear();  
  19.                 }else {  
  20.                     System.out.println("----handleAck----multiple false");  
  21.                     confirmSet.remove(deliveryTag);  
  22.                 }  
  23.             }  
  24.             //handleNack  
  25.             public void handleNack(long deliveryTag, boolean multiple) {  
  26.                 if(multiple){  
  27.                     System.out.println("----handleNack----multiple");  
  28.                     confirmSet.headSet(deliveryTag+1).clear();  
  29.                 }else {  
  30.                     System.out.println("----handleNack----multiple false");  
  31.                     confirmSet.remove(deliveryTag);  
  32.                 }  
  33.             }  
  34.         });  
  35.         //向隊列中推送消息  
  36.         for(int i = 0 ;i<50;i++){  
  37.             long nextPublishSeqNo = channel.getNextPublishSeqNo();  
  38.             String msg = "hello confirm message["+i+"] !";  
  39.             channel.basicPublish("", RabbitConstant.QUEUE_NAME_SIMPLE_CONFIRM, null, msg.getBytes());  
  40.             confirmSet.add(nextPublishSeqNo);  
  41.         }  
  42.   
  43.         //關閉通道和連接  
  44.         channel.close();  
  45.         connection.close();  
  46.     }  
  47. }  

 

 

2.9.2.4 消費者

  1. public class Recv {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //創建連接  
  4.         Connection connection = RabbitConnectionUtil.getConnection();  
  5.         //創建通道  
  6.         Channel channel = connection.createChannel();  
  7.         //創建隊列聲明  
  8.         channel.queueDeclare(RabbitConstant.QUEUE_NAME_SIMPLE_CONFIRM, falsefalsefalse, null);  
  9.         //重寫handleDelivery 方法,實現阻塞之後獲取消息的處理  
  10.         Consumer consumer = new DefaultConsumer(channel) {  
  11.             //獲取到達的消息  
  12.             @Override  
  13.             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {  
  14.                 super.handleDelivery(consumerTag, envelope, properties, body);  
  15.                 //打印接收到的內容  
  16.                 String msg = new String(body, "utf-8");  
  17.                 System.out.println("Recv【confirm】 success:" + msg);  
  18.             }  
  19.         };  
  20.         //監聽隊列  
  21.         channel.basicConsume(RabbitConstant.QUEUE_NAME_SIMPLE_CONFIRM, true, consumer);  
  22.     }  

 

3、Spring集成Rabbitmq

3.1 引入maven依賴

  1. <!-- spring 整合rabbit jar 包-->  
  2. <dependency>  
  3.       <groupId>org.springframework.amqp</groupId>  
  4.       <artifactId>spring-rabbit</artifactId>  
  5.       <version>2.1.8.RELEASE</version>  
  6. </dependency>  

3.2 配置application.xml文件

  1. <beans xmlns="http://www.springframework.org/schema/beans"  
  2.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.        xmlns:rabbit="http://www.springframework.org/schema/rabbit"  
  4.        xsi:schemaLocation="http://www.springframework.org/schema/rabbit  
  5.            https://www.springframework.org/schema/rabbit/spring-rabbit.xsd  
  6.            http://www.springframework.org/schema/beans  
  7.            https://www.springframework.org/schema/beans/spring-beans.xsd">  
  8.   
  9.     <!--創建rabiitmq連接工廠-->  
  10.     <rabbit:connection-factory id="connectionFactory" host="127.0.0.1" port="5672" username="guest" password="guest" virtual-host="/test_rabbit"/>  
  11.   
  12.     <!--定義Rabiit模板,指定連接工廠和 exchange-->  
  13.     <rabbit:template id="amqpTemplate" connection-factory="connectionFactory" exchange="fanoutExchange"/>  
  14.   
  15.     <!--mq的管理,包括隊列、交換器的聲明等-->  
  16.     <rabbit:admin connection-factory="connectionFactory"/>  
  17.   
  18.     <!--定義隊列,自動聲明-->  
  19.     <rabbit:queue name="myqueue" auto-declare="true" durable="true"/>  
  20.   
  21.     <!--定義交換器 自動聲明-->  
  22.     <rabbit:fanout-exchange name="fanoutExchange" auto-declare="true">  
  23.         <rabbit:bindings>  
  24.             <rabbit:binding queue="myqueue"/>  
  25.         </rabbit:bindings>  
  26.     </rabbit:fanout-exchange>  
  27.   
  28.     <!--隊列監聽 指定用哪個類中的哪個方法去監聽隊列-->  
  29.     <rabbit:listener-container connection-factory="connectionFactory">  
  30.         <rabbit:listener ref="foo" method="listen" queue-names="myqueue"/>  
  31.     </rabbit:listener-container>  
  32.   
  33.     <!--消費者-->  
  34.     <bean id="foo" class="com.rabbit.test.spring.MyConsumer"/>  
  35.   
  36. </beans>  

 

3.3 創建消費者監聽

  1. public class MyConsumer {  
  2.   
  3.     //具體執行的業務方法  
  4.     public void listen(String foo){  
  5.         System.out.println("消費者:"+foo);  
  6.     }  
  7.   
  8. }  

3.4 創建生產者發送消息

  1. public class SpringMain {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //獲得配置上下文  
  4.         AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:application.xml");  
  5.         //得到rabbitmq模板  
  6.         RabbitTemplate rabbitTemplate = ctx.getBean(RabbitTemplate.class);  
  7.         //發送消息  
  8.         rabbitTemplate.convertAndSend("hello word !");  
  9.         Thread.sleep(1000);  
  10.         ctx.destroy();  
  11.     }  

 

 

 

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