RabbitMQ筆記五:RabbitMQ java client的使用

java client的使用

本篇博客介紹RabbitMQ java client的一些簡單的api使用,如聲明Exchange,Queue,發(fā)送消息特愿,消費消息,一些高級api會在下面的章節(jié)詳細(xì)的說明勾缭。

概述

首先加入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作為其頂級包揍障。關(guān)鍵的類和接口是:

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

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

Connections(連接)和Channels(管道)

核心的類是Connections(連接)和Channels(管道)碘梢,分別代表著AMQP 0-9-1協(xié)議中的Connections(連接)和Channels(管道)咬摇,一般被導(dǎo)入

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

連接服務(wù)器

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

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

所有的這些參數(shù)RabbitMQ服務(wù)器都設(shè)置了默認(rèn)值肛鹏,可以在ConnectionFactory類中查看這些默認(rèn)值。

另外恩沛,URI可以以下面的方法進行連接都有默認(rèn)值在扰。

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

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

Channel channel = conn.createChannel();

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

關(guān)閉連接芒珠,只需要關(guān)閉channel(管道)和connection(連接):

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

注意,關(guān)閉管道是被認(rèn)為是最佳實踐搅裙,但是卻不是嚴(yán)格意義的必要的妓局。當(dāng)?shù)讓拥倪B接關(guān)閉時候,channel(管道)也就自動的被關(guān)閉了呈宇。

使用Exchanges和Queues

客戶端應(yīng)用必須應(yīng)用在exchanges和queues好爬,這些都是AMQP協(xié)議定義的。使用這些(exchanges和queues)首先必須“聲明”它(就是創(chuàng)建的意思)甥啄。

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

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

可以通過參數(shù)去設(shè)置exchange和queue的一些屬性,使用這些方法的一些重載方法進行相關(guān)設(shè)置穆桂。

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

發(fā)送消息(Publishing messages)

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

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

為了更好的控制宫盔,你可以使用重載的參數(shù)來設(shè)置消息的一些屬性(比如說mandatory標(biāo)志,關(guān)于mandatory標(biāo)志享完,下面會講到)灼芭,或者在發(fā)送消息前設(shè)定一些消息屬性。

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

可以自己構(gòu)建BasicProperties的對象般又,如下面的代碼:

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

發(fā)送消息指定頭信息:

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);

發(fā)送一個有過期時間的消息彼绷,下面的博客也會講到:

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

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

根據(jù)經(jīng)驗,在線程間共享Channel(通道)是要避免的茴迁。應(yīng)用應(yīng)該優(yōu)先使用每個線程自己的Channel(通道)實例寄悯,而不是多個線程共享這個Channel(通道)實例。

雖然有些在Channel(通道)上的操作是可以并發(fā)安全的調(diào)用堕义,但是一些操作不行會導(dǎo)致一些邊界交錯猜旬,雙重確認(rèn)等等。

在共享(多線程)Channel(通道)上進行并發(fā)發(fā)布會導(dǎo)致一些邊界交錯倦卖,觸發(fā)連接協(xié)議異常和連接關(guān)閉洒擦。因此需要嚴(yán)格在應(yīng)用中同步調(diào)用(Channel#basicPublish必須在正確關(guān)鍵的地方調(diào)用)。線程之間的共享也會干擾生產(chǎn)者的消息確認(rèn)怕膛。我們強烈的推薦不應(yīng)該在通道上進行并發(fā)的發(fā)布消息秘遏。

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

服務(wù)器推送可以同時發(fā)送邦危,保證每通道的訂閱被保留。 調(diào)度機制使用java.util.concurrent.ExecutorService舍扰。 可以使用單列的ConnectionFactory調(diào)用ConnectionFactory#setSharedExecutor去設(shè)置所有連接共用的executor倦蚪。

當(dāng)我們手動確認(rèn)manual acknowledgements 的時候,很重要的是考慮什么線程去做這個ack確認(rèn)边苹。如果接收傳遞的線程(例如陵且,Consumer#handleDelivery委托給不同線程的傳遞處理)不同于手動確認(rèn)的線程,則將多個線程參數(shù)設(shè)置為true是線程不安全的并導(dǎo)致雙重確認(rèn)个束,因此導(dǎo)致通道協(xié)議異常導(dǎo)致Channel關(guān)閉慕购。一次確認(rèn)一條消息可以確保安全的。

訂閱消息("Push API")

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

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

當(dāng)我們調(diào)用Consumers(消費者)有關(guān)的api的時候阱表,會生成一個消費者標(biāo)識符(consumer tag)殿如。

不同的Consumer實例必須有不同的消費者標(biāo)簽贡珊。 強烈建議不要在連接上重復(fù)使用消費者標(biāo)簽,不然在監(jiān)視消費者時可能導(dǎo)致自動連接恢復(fù)和混淆監(jiān)控數(shù)據(jù)的問題涉馁。

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

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);
         }
     });

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

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

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

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

channel.basicCancel(consumerTag);

和生產(chǎn)者一樣忘朝,對于消費者來說并發(fā)處理消息也要慎重考慮灰署。

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

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

翻譯未完待續(xù)......

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();

        //創(chuàng)建exchange寡痰,類型是direct類型
        channel.exchangeDeclare("zhihao.miao","direct");

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

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

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

        //異步創(chuàng)建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(可重復(fù)執(zhí)行)贞滨,刪除一個不存在的也不會報錯
        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();

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

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

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

        //exchange和queue進行綁定(可重復(fù)執(zhí)行骄噪,不會重復(fù)創(chuàng)建)
        channel.queueBind("zhihao.info","zhihao.miao.order","info");

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

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

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

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

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


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

    }
}

消息的發(fā)送:

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();

        //第一個參數(shù)是exchange參數(shù)蠢箩,如果是為空字符串腰池,那么就會發(fā)送到(AMQP default)默認(rèn)的exchange尾组,而且routingKey
        //便是所要發(fā)送到的隊列名
        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,"優(yōu)酷會員到期了".getBytes());


        //topic類型的exchange
        channel.basicPublish("log","user.log",properties,"你的外賣已經(jīng)送達(dá)".getBytes());
        channel.basicPublish("log","user.log.info",properties,"你的外賣正在配送中".getBytes());
        channel.basicPublish("log","user",properties,"你的投訴已經(jīng)采納".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","支付系統(tǒng)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跨跨,也可以通過重載方法進行設(shè)置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方法囱皿,如果是手工確認(rèn)消息勇婴,會在handleDelivery方法中進行相關(guān)的確認(rèn)(調(diào)用相關(guān)api),下面會在確認(rèn)消息博客中去詳細(xì)講解這個嘱腥。

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("消息內(nèi)容為:"+new String(body));
    }
}
connection
連接的客戶端屬性

參考資料
Java Client API Guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耕渴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子齿兔,更是在濱河造成了極大的恐慌橱脸,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件分苇,死亡現(xiàn)場離奇詭異添诉,居然都是意外死亡,警方通過查閱死者的電腦和手機医寿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門栏赴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人靖秩,你說我怎么就攤上這事须眷。” “怎么了沟突?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵花颗,是天一觀的道長。 經(jīng)常有香客問我事扭,道長捎稚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任求橄,我火速辦了婚禮今野,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罐农。我一直安慰自己条霜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布涵亏。 她就那樣靜靜地躺著宰睡,像睡著了一般蒲凶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拆内,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天旋圆,我揣著相機與錄音,去河邊找鬼麸恍。 笑死灵巧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抹沪。 我是一名探鬼主播刻肄,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼融欧!你這毒婦竟也來了敏弃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤噪馏,失蹤者是張志新(化名)和其女友劉穎麦到,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逝薪,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡隅要,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年蝴罪,在試婚紗的時候發(fā)現(xiàn)自己被綠了董济。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡要门,死狀恐怖虏肾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欢搜,我是刑警寧澤封豪,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站炒瘟,受9級特大地震影響吹埠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疮装,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一缘琅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧廓推,春花似錦刷袍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堆生。三九已至,卻和暖如春雷酪,著一層夾襖步出監(jiān)牢的瞬間淑仆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工哥力, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留糯景,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓省骂,卻偏偏與公主長得像蟀淮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钞澳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理怠惶,服務(wù)發(fā)現(xiàn),斷路器轧粟,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • 本文章翻譯自http://www.rabbitmq.com/api-guide.html策治,并沒有及時更新。 術(shù)語對...
    joyenlee閱讀 7,658評論 0 3
  • 來源 RabbitMQ是用Erlang實現(xiàn)的一個高并發(fā)高可靠AMQP消息隊列服務(wù)器兰吟。支持消息的持久化通惫、事務(wù)、擁塞控...
    jiangmo閱讀 10,359評論 2 34
  • 什么叫消息隊列 消息(Message)是指在應(yīng)用間傳送的數(shù)據(jù)混蔼。消息可以非常簡單履腋,比如只包含文本字符串,也可以更復(fù)雜...
    lijun_m閱讀 1,346評論 0 1
  • 關(guān)于消息隊列惭嚣,從前年開始斷斷續(xù)續(xù)看了些資料遵湖,想寫很久了,但一直沒騰出空晚吞,近來分別碰到幾個朋友聊這塊的技術(shù)選型延旧,是時...
    預(yù)流閱讀 584,676評論 51 786