RabbitMQ筆記五:RabbitMQ java client的使用

java client的使用

本篇博客介紹RabbitMQ java client的一些簡單的api使用,如聲明Exchange,Queue,發送消息,消費消息,一些高級api會在下面的章節詳細的說明。

概述

首先加入RabbitMQ java client依賴:

<dependencies>
      <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>4.0.2</version>
      </dependency>
</dependencies>

RabbitMQ的java client使用com.rabbitmq.client作為其頂級包。關鍵的類和接口是:

com.rabbitmq.client.Channel
com.rabbitmq.client.Connection
com.rabbitmq.client.ConnectionFactory
com.rabbitmq.client.Consumer

通過Channel可以進行一系列的api操作。 Connection(連接)用于打開通道,注冊連接生命周期事件處理程序,并關閉不再需要的連接。 Connection(連接)通過ConnectionFactory實例化,ConnectionFactory可以設置一些Collection(連接)的一些配置,比如說vhost或者說username等等。

Connections(連接)和Channels(管道)

核心的類是Connections(連接)和Channels(管道),分別代表著AMQP 0-9-1協議中的Connections(連接)和Channels(管道),一般被導入

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;

連接服務器

下面的代碼時使用給定的參數(host name,端口等等)連接AMQP的服務器。

ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);
Connection conn = factory.newConnection();

所有的這些參數RabbitMQ服務器都設置了默認值,可以在ConnectionFactory類中查看這些默認值。

另外,URI可以以下面的方法進行連接都有默認值。

ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://userName:password@hostName:portNumber/virtualHost");
Connection conn = factory.newConnection();

Connection(連接)接口可以被用作創建一個channel(管道):

Channel channel = conn.createChannel();

可以使用channel(管道)發送和接收消息,下面會有講到。

關閉連接,只需要關閉channel(管道)和connection(連接):

channel.close();
conn.close();

注意,關閉管道是被認為是最佳實踐,但是卻不是嚴格意義的必要的。當底層的連接關閉時候,channel(管道)也就自動的被關閉了。

使用Exchanges和Queues

客戶端應用必須應用在exchanges和queues,這些都是AMQP協議定義的。使用這些(exchanges和queues)首先必須“聲明”它(就是創建的意思)。

下面的代碼就是怎樣去"聲明"一個exchange和隊列,并且將它們綁定在一起。

channel.exchangeDeclare(exchangeName, "direct", true);
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, exchangeName, routingKey);

可以通過參數去設置exchange和queue的一些屬性,使用這些方法的一些重載方法進行相關設置。

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

發送消息(Publishing messages)

使用Channel.basicPublish方法將消息發送給一個exchange:

byte[] messageBodyBytes = "Hello, world!".getBytes();
channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes);

為了更好的控制,你可以使用重載的參數來設置消息的一些屬性(比如說mandatory標志,關于mandatory標志,下面會講到),或者在發送消息前設定一些消息屬性。

channel.basicPublish(exchangeName, routingKey, mandatory,
                     MessageProperties.PERSISTENT_TEXT_PLAIN,
                     messageBodyBytes);

可以自己構建BasicProperties的對象,如下面的代碼:

channel.basicPublish(exchangeName, routingKey,
             new AMQP.BasicProperties.Builder()
               .contentType("text/plain")
               .deliveryMode(2)
               .priority(1)
               .userId("bob")
               .build()),
               messageBodyBytes);

發送消息指定頭信息:

Map<String, Object> headers = new HashMap<String, Object>();
headers.put("latitude",  51.5252949);
headers.put("longitude", -0.0905493);

channel.basicPublish(exchangeName, routingKey,
             new AMQP.BasicProperties.Builder()
               .headers(headers)
               .build()),
               messageBodyBytes);

發送一個有過期時間的消息,下面的博客也會講到:

channel.basicPublish(exchangeName, routingKey,
             new AMQP.BasicProperties.Builder()
               .expiration("60000")
               .build()),
               messageBodyBytes);

通道和并發注意事項(線程安全)

根據經驗,在線程間共享Channel(通道)是要避免的。應用應該優先使用每個線程自己的Channel(通道)實例,而不是多個線程共享這個Channel(通道)實例。

雖然有些在Channel(通道)上的操作是可以并發安全的調用,但是一些操作不行會導致一些邊界交錯,雙重確認等等。

在共享(多線程)Channel(通道)上進行并發發布會導致一些邊界交錯,觸發連接協議異常和連接關閉。因此需要嚴格在應用中同步調用(Channel#basicPublish必須在正確關鍵的地方調用)。線程之間的共享也會干擾生產者的消息確認。我們強烈的推薦不應該在通道上進行并發的發布消息。

在共享的Channel(通道)上一個線程生產(publish)消息,一個線程消費(consume)消息是線程安全的。

服務器推送可以同時發送,保證每通道的訂閱被保留。 調度機制使用java.util.concurrent.ExecutorService。 可以使用單列的ConnectionFactory調用ConnectionFactory#setSharedExecutor去設置所有連接共用的executor。

當我們手動確認manual acknowledgements 的時候,很重要的是考慮什么線程去做這個ack確認。如果接收傳遞的線程(例如,Consumer#handleDelivery委托給不同線程的傳遞處理)不同于手動確認的線程,則將多個線程參數設置為true是線程不安全的并導致雙重確認,因此導致通道協議異常導致Channel關閉。一次確認一條消息可以確保安全的。

訂閱消息("Push API")

import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

最有效的接收消息的方法是使用Consumer接口去訂閱。當消息到達消費端的時候會自動的傳遞消費(delivered),而不需要去請求。

當我們調用Consumers(消費者)有關的api的時候,會生成一個消費者標識符(consumer tag)。

不同的Consumer實例必須有不同的消費者標簽。 強烈建議不要在連接上重復使用消費者標簽,不然在監視消費者時可能導致自動連接恢復和混淆監控數據的問題。

實現Consumer的最簡單的方法是將便利(convenience)類DefaultConsumer子類化。 該子類的對象可以在basicConsume方法調用中傳遞以設置訂閱:

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "myConsumerTag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             String routingKey = envelope.getRoutingKey();
             String contentType = properties.getContentType();
             long deliveryTag = envelope.getDeliveryTag();
             // (process the message components here ...)
             channel.basicAck(deliveryTag, false);
         }
     });

在這里,因為我們設置了自動確認(autoAck)的值為false,所以有必要在傳遞給消費者的方法中進行自動確認(handleDelivery方法中)。

更復雜的消費者將會重寫更多的方法。事實上,handleShutdownSignal方法被調用當Channel(通道)和連接關閉的時候。并且在調用該消費者的任何回調方法之前將consumer tag傳遞給handleConsumeOk(com.rabbitmq.client.Consumer接口中定義的方法)方法

消費者還可以分別實現handleCancelOk(com.rabbitmq.client.Consumer接口中定義的方法)和handleCancel(com.rabbitmq.client.Consumer接口中定義的方法)方法來通知顯式和隱式取消。

你也可以使用Channel.basicCancel方法明確的取消一個特定的消費,傳遞consumer tag,

channel.basicCancel(consumerTag);

和生產者一樣,對于消費者來說并發處理消息也要慎重考慮。

回調給消費者是在與實例化其Channel(管道)的線程分開的線程池中調度的。 這意味著消費者可以安全地在ConnectionChannel上調用阻塞方法,例如Channel#queueDeclareChannel#basicCancel。

每一個Channel(管道)都有自己的調度線程。對于最常用的使用方式就是一個消費者一個Channel(管道),意味著一個消費者不會阻塞其他的消費。如果是一個Channel(管道)多消費者必須明白一個長時間的消費調用可能會阻塞其他消費者的回調調度。

翻譯未完待續......

demo

通過ConnectionFactory獲得Connection,Connection得到Channel

public class ExchangeTest {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.1.131");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("zhihao.miao");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        //創建exchange,類型是direct類型
        channel.exchangeDeclare("zhihao.miao","direct");

        //創建exchange,類型是direct類型
        channel.exchangeDeclare("zhihao.miao.info", BuiltinExchangeType.DIRECT);

        //第三個參數表示是否持久化,同步操作,有返回值
        AMQP.Exchange.DeclareOk ok = channel.exchangeDeclare("zhihao.miao.debug",BuiltinExchangeType.DIRECT,true);
        System.out.println(ok);

        //設置屬性
        Map<String,Object> argument = new HashMap<>();
        argument.put("alternate-exchange","log");
        channel.exchangeDeclare("zhihao.miao.warn",BuiltinExchangeType.TOPIC,true,false,argument);

        //異步創建exchange,沒有返回值
        channel.exchangeDeclareNoWait("zhihao.miao.log",BuiltinExchangeType.TOPIC,true,false,false,argument);

        //判斷exchange是否存在,存在的返回ok,不存在的exchange則報錯
        /*
        AMQP.Exchange.DeclareOk declareOk = channel.exchangeDeclarePassive("zhihao.miao.info");
        System.out.println(declareOk);

        declareOk = channel.exchangeDeclarePassive("zhihao.miao.info2");
        System.out.println(declareOk);
        */

        //刪除exchange(可重復執行),刪除一個不存在的也不會報錯
        channel.exchangeDelete("zhihao.miao");
        channel.exchangeDelete("zhihao.miao.debug");
        channel.exchangeDelete("zhihao.miao.info");
        channel.exchangeDelete("zhihao.miao.warn");


        //刪除exchange
        channel.exchangeDelete("zhihao.miao.log");

        channel.close();
        connection.close();

    }
}

隊列的api操作。

public class QueueTest {
    public static void main(String[] args) throws Exception{

        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.1.131");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("zhihao.miao");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        //第二個參數表示是否持久化,第三個參數是判斷這個隊列是否在連接是否生效,為true表示連接關閉隊列刪除。
        AMQP.Queue.DeclareOk ok = channel.queueDeclare("zhihao.info",true,false,false,null);
        System.out.println(ok);

        //異步沒有返回值的方法api
        channel.queueDeclareNoWait("zhihao.info.miao",true,false,false,null);

        //判斷queue是否存在,不存在會拋出異常
        //channel.exchangeDeclarePassive("zhihao.info");
        //拋出錯誤
        //channel.exchangeDeclarePassive("zhihao.info.miao2");

        //exchange和queue進行綁定(可重復執行,不會重復創建)
        channel.queueBind("zhihao.info","zhihao.miao.order","info");

        //異步進行綁定
        channel.queueBindNoWait("zhihao.info.miao","zhihao.miao.pay","info",null);

        //exchange與exchange進行綁定(可重復執行,不會重復創建)
        channel.exchangeBind("zhihao.miao.email","zhihao.miao.weixin","debug");

        //exchange和queue進行解綁(可重復執行)
        channel.queueUnbind("zhihao.info","zhihao.miao.order","info");

        //exchange和exchange進行解綁(可重復執行)
        channel.exchangeUnbind("zhihao.info.miao","zhihao.miao.pay","debug");

        //刪除隊列
        channel.queueDelete("zhihao.info");


        channel.close();
        connection.close();

    }
}

消息的發送:

public class Sender {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
                contentEncoding("UTF-8").build();

        //第一個參數是exchange參數,如果是為空字符串,那么就會發送到(AMQP default)默認的exchange,而且routingKey
        //便是所要發送到的隊列名
        channel.basicPublish("","zhihao.info.miao",properties,"忘記密碼,驗證碼是1234".getBytes());
        channel.basicPublish("","zhihao.miao",properties,"忘記密碼,六位驗證密碼是343sdf".getBytes());

        //direct類型的exchange類型的exchange,zhihao.miao.order綁定zhihao.info.miao隊列,route key是order
        channel.basicPublish("zhihao.miao.order","order",properties,"愛奇藝會員到期了".getBytes());
        //zhihao.miao.pay綁定zhihao.info.miao隊列,route key是order
        channel.basicPublish("zhihao.miao.pay","pay",properties,"優酷會員到期了".getBytes());


        //topic類型的exchange
        channel.basicPublish("log","user.log",properties,"你的外賣已經送達".getBytes());
        channel.basicPublish("log","user.log.info",properties,"你的外賣正在配送中".getBytes());
        channel.basicPublish("log","user",properties,"你的投訴已經采納".getBytes());

        channel.close();
        connection.close();
    }
}

消息消費:

public class Consumer {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.1.131");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("zhihao.miao");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/");

        //客戶端的消費消息
        Map<String,Object> clientProperties = new HashMap<>();
        clientProperties.put("desc","支付系統2.0");
        clientProperties.put("author","zhihao.miao");
        clientProperties.put("user","zhihao.miao@xxx.com");

        connectionFactory.setClientProperties(clientProperties);

        //給客戶端的connetction命名
        Connection connection = connectionFactory.newConnection("log隊列的消費者");

        //給channel起個編號
        Channel channel = connection.createChannel(10);

        //返回consumerTag,也可以通過重載方法進行設置consumerTag
        String consumerTag = channel.basicConsume("user_log_queue",true,new SimpleConsumer(channel));
        System.out.println(consumerTag);

        TimeUnit.SECONDS.sleep(30);

        channel.close();
        connection.close();
    }
}

具體的消息邏輯,繼承DefaultConsumer類重寫handleDelivery方法,如果是手工確認消息,會在handleDelivery方法中進行相關的確認(調用相關api),下面會在確認消息博客中去詳細講解這個。

public class SimpleConsumer extends DefaultConsumer{

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println(consumerTag);
        System.out.println("-----收到消息了---------------");
        System.out.println("消息屬性為:"+properties);
        System.out.println("消息內容為:"+new String(body));
    }
}
connection
連接的客戶端屬性

參考資料
Java Client API Guide

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,787評論 18 139
  • 本文章翻譯自http://www.rabbitmq.com/api-guide.html,并沒有及時更新。 術語對...
    joyenlee閱讀 7,686評論 0 3
  • 來源 RabbitMQ是用Erlang實現的一個高并發高可靠AMQP消息隊列服務器。支持消息的持久化、事務、擁塞控...
    jiangmo閱讀 10,377評論 2 34
  • 什么叫消息隊列 消息(Message)是指在應用間傳送的數據。消息可以非常簡單,比如只包含文本字符串,也可以更復雜...
    lijun_m閱讀 1,360評論 0 1
  • 關于消息隊列,從前年開始斷斷續續看了些資料,想寫很久了,但一直沒騰出空,近來分別碰到幾個朋友聊這塊的技術選型,是時...
    預流閱讀 585,259評論 51 786