終篇:理解并使用RabbitMQ

說明

RabbitMQ中MQ是MessageQueue的簡寫,整體的意思應該是像兔子一樣快的高效消息中間件,組件本質上就是生產者消費者模式,由一邊接受消息,另一邊轉發消息,其中可以對消息進行緩存,轉發或清除。

一般實際中使用情況是推送和IM(及時通訊)

如何使用

首先要含有封裝底層的jar包,下載RabbitMQ-java-Client,下載之后放在AndroidStudio項目中libs包中編譯,或者在module中build.gradle中添加依賴:

dependencies {
    compile 'com.rabbitmq:amqp-client:4.2.0'
}

RabbitMQ是由pivotal公司開源的一個項目,在Github上可以查看的源碼。

需要訪問網絡,不要忘記在清單文件中添加網絡權限:

<uses-permission android:name="android.permission.INTERNET"/>

一張簡易的圖表示工作原理:

RabbitMQ.png

連接

首先配置連接服務端基本信息:

    ConnectionFactory factory = new ConnectionFactory();  //創建連接工廠
    
    //設置服務端連接認證:ip號,端口號,登錄名,密碼
    factory.setHost(“HOST_ID");
    factory.setPort(PORT);
    factory.setUsername("USERNAME");
    factory.setPassword("PASSWORD")
    
    factory.setAutomaticRecoveryEnabled(true); //設置自動恢復連接
    factory.setNetworkRecoveryInterval(5000);  //設置自動連接間隔(毫秒)

以上都是一些基本配置,設置服務端上認證信息,設置恢復連接機制,在已經連接過之后斷開連接,會主動嘗試連接服務,直至連接上服務或者關閉連接工廠。

如果第一沒有連接上,則會報錯。需要循環創建連接,直至第一次連接很成功之后才會自動連接,包括所有的通道(Channel)。

現在有了連接工廠,創建一個連接就夠多個通道使用,也是高效率的方式:

Connection connection = factory.newConnection();

注意這個需要在子線程中執行,否則會報錯誤: Android.os.NetworkOnMainThreadException。

發送消息

  • 建立通道及配置
  • 發送消息
Channel channelSend = connection.createChannel();
//第一種方式,直接指定消費隊列
1.channelSend.queueDeclare(queueName, false, false, false, null);
  channelSend.basicPublish("", queueName, null, message.getBytes());
    
//第二種方式,指定轉換器和綁定密鑰(routingkey)
2.channelSend.exchangeDeclare(EXCHANGENAME, "topic"); //類型四種:fanout,direct,topic,handers
  channelSend.basicPublish(EXCHANGENAME, routingkey, null, message.getBytes());

就是這么簡單,但還是需要簡單說明。

完整的流程:發送者->轉換器->隊列->接受者

第一種情況,雖然沒有聲明轉換器,但是會使用匿名轉換器,發送到routingKey為隊列名的隊列中。

第二種情況,只說明轉換器的四種類型特點:

  • fanout類型:忽略routingkey的值發送給所有綁定該類型的隊列中,
  • direct類型:根據routingkey的值發送給匹配該值的隊列中,
  • topic類型:它的routingKey可以使用*和#來表示,前者表示一個連著的單詞,如work;后者表示零個或多個連著的單詞,如work.first。只要發送消息的routingKey匹配接受者設置綁定exchange的routingKey的值,接受者隊列就可以收到到由exchange發送的消息。
  • headers類型:以匹配鍵值對的形式發送和接受消息。匹配有兩種方式all和any。用的比較少,所以就沒有深入了解。

接受消息

  • 建立通道及配置
  • 設置監聽通道消息
Channel channel = connection.createChannel();

//不定義裝換器(默認匿名裝換器),命名隊列,獲取實時和緩存在隊列中的消息
1. final Channel channel = connection.createChannel();
    channel.queueDeclare(queueName, false, false, false, null);

    channel.basicConsume(queueName, false, 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");
            Log.d(TAG, queueName + "接受消息->" + message);
            channel.basicAck(envelope.getDeliveryTag(), false);  //消息應答
        }
    });

//自定義裝換器名稱和類型,匿名隊列,監聽符合routingKey的消息,只能獲取實時消息            
2. final Channel channel = connection.createChannel();

    channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
    String queueName = channel.queueDeclare().getQueue();
    channel.queueBind(queueName, EXCHANGE_NAME, routingKey);

    channel.basicConsume(queueName, false, 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");
            Log.d(TAG, routingKey + "接受消息->" + message);
            channel.basicAck(envelope.getDeliveryTag(), false);  //消息應答
        }
    });
        
//自定義裝換器名稱和類型,命名隊列,監聽符合routingKey的消息,獲取實時和緩存在隊列中的消息
3. final Channel channel = connection.createChannel();

    channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
    channel.queueDeclare(queueName, false, false, false, null);
    channel.queueBind(queueName, EXCHANGE_NAME, routingKey);  //設置綁定

    channel.basicConsume(queueName, false, 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");
            Log.d(TAG, routingKey + queueName + "接受消息->" + message);
            channel.basicAck(envelope.getDeliveryTag(), false);  //消息應答
        }
    });

以上是主要的三種監聽消息方式:

第一種,匿名轉換器,命名隊列,監聽隊列中的消息。只能一對一發送并且接受,局限比較大。

第二種,指定轉換器類型,匿名隊列,監聽實時routingKey的消息,篩選出匹配routingKey消息進行發送,所以有可能發送到多個隊列中。每次連接都是獲取新的空的隊列,并且失去連接后隊列將被刪除,所以只能獲取實時的消息。

第三種,指定裝換器類型,命名隊列,監聽隊列中routingKey的消息,篩選出匹配routingKey消息進行發送,所以有可能發送到多個隊列中。獲取實時和緩存在隊列中的消息

匿名隊列的特點:

  1. 一旦被創建就是一個新的空的隊列
  2. 一旦失去消費者連接該隊列就會被服務端刪除

命名隊列特點:

  1. 默認循環均勻發送消息給多個消費者,但是可以設置channel.basicQos(1)高效的發送消息給多個消費者。
  2. 消息應答機制,服務端確認消息被銷毀才會移除該消息。
  3. 隊列和消息持久化,在服務端奔潰或者重啟是可以保存隊列和消息。
  4. 一個消息只能被一個消費者消費一次。

關閉

if (channelSend != null && channelSend.isOpen()) {
    try {
        channelSend.close();
    } catch (IOException | TimeoutException e) {
        e.printStackTrace();
    }
}
if (channelReceive != null && channelReceive.isOpen()) {
    try {
        channelReceive.close();
    } catch (IOException | TimeoutException e) {
        e.printStackTrace();
    }
}
if (connection != null && connection.isOpen()) {
    try {
        connection.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

封裝RabbitMQ-Android的簡單使用,歡迎下載使用,并且反饋

注:這是RabbitMQ-java版Client的指導教程翻譯系列文章,歡迎大家批評指正
第一篇Hello Word了解RabbitMQ的基本用法
第二篇Work Queues介紹隊列的使用
第三篇Publish/Subscribe介紹轉換器以及其中fanout類型
第四篇Routing介紹direct類型轉換器
第五篇Topics介紹topic類型轉換器
第六篇RPC介紹遠程調用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 關于消息隊列,從前年開始斷斷續續看了些資料,想寫很久了,但一直沒騰出空,近來分別碰到幾個朋友聊這塊的技術選型,是時...
    預流閱讀 585,481評論 51 786
  • 來源 RabbitMQ是用Erlang實現的一個高并發高可靠AMQP消息隊列服務器。支持消息的持久化、事務、擁塞控...
    jiangmo閱讀 10,408評論 2 34
  • 注:這份文檔是我和幾個朋友學習后一起完成的。 目錄 RabbitMQ 概念 exchange交換機機制什么是交換機...
    Mooner_guo閱讀 33,386評論 8 97
  • 本文章翻譯自http://www.rabbitmq.com/api-guide.html,并沒有及時更新。 術語對...
    joyenlee閱讀 7,705評論 0 3