消息發(fā)送

如果你曾經(jīng)使用過RocketMQ,那么一定對(duì)以下發(fā)送消息的代碼不陌生

DefaultMQProducer producer = new DefaultMQProducer("producerGroup");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
Message message = new Message(topic, new byte[] {'hello, world'});
producer.send(message);

Producer啟動(dòng)

image.png

其實(shí)僅僅一行代碼,在produer端的后臺(tái)啟動(dòng)了多個(gè)線程來協(xié)同工作漓帅,接下來我們逐一闡述

我們都知道埠况,RocketMQ是一個(gè)集群部署、跨網(wǎng)絡(luò)的產(chǎn)品昭殉,除了producer苞七、consumer需要網(wǎng)絡(luò)傳輸外,數(shù)據(jù)還需要在集群中流轉(zhuǎn)挪丢。所以一個(gè)高效蹂风、可靠的網(wǎng)絡(luò)組件是必不可少的。而RocketMQ選擇了netty

使用netty首先需要考慮的便是業(yè)務(wù)上的數(shù)據(jù)粘包問題乾蓬,netty提供了一些較為常用的解決方案惠啄,如:固定長(zhǎng)度(比如每次發(fā)送的消息長(zhǎng)度均為1024byte)、固定分隔符等任内。而RocketMQ使用的則是最為通用的head撵渡、body分離方式,即head存儲(chǔ)消息的長(zhǎng)度死嗦,body存儲(chǔ)真正的消息數(shù)據(jù)趋距,具體實(shí)現(xiàn)可參見類o.a.r.r.n.NettyRemotingClient

而消息收發(fā)這塊,RocketMQ將所有的消息都收斂到同一個(gè)協(xié)議類o.a.r.r.p.RemotingCommand中越除,即消息發(fā)送节腐、接收都會(huì)將其封裝在該類中,這樣做的好處是不言而喻的摘盆,即統(tǒng)一規(guī)范翼雀,減輕網(wǎng)絡(luò)協(xié)議適配不同的消息類型帶來的負(fù)擔(dān)

其中較為重要的2個(gè) ChannelHanlder 如下

org.apache.rocketmq.remoting.netty.NettyEncoder
消息編碼,向 broker 或 nameServer 發(fā)送消息時(shí)使用孩擂,將RemotingCommand轉(zhuǎn)換為byte[]形式
org.apache.rocketmq.remoting.netty.NettyDecoder
消息解碼狼渊,將byte[]轉(zhuǎn)換為RemotingCommand對(duì)象,接收 broker 返回的消息時(shí)类垦,進(jìn)行解碼操作

消息格式

消息格式是什么概念狈邑?在《消息存儲(chǔ)》章節(jié)不是已經(jīng)闡述過消息格式了嗎坦弟?其實(shí)這是兩個(gè)概念,《消息存儲(chǔ)》章節(jié)是消息真正落盤時(shí)候的存儲(chǔ)格式官地,本小節(jié)的消息格式是指消息以什么樣的形態(tài)交給netty從而在網(wǎng)絡(luò)上進(jìn)行傳輸

消息格式

消息格式是什么概念酿傍?在《消息存儲(chǔ)》章節(jié)不是已經(jīng)闡述過消息格式了嗎?其實(shí)這是兩個(gè)概念驱入,《消息存儲(chǔ)》章節(jié)是消息真正落盤時(shí)候的存儲(chǔ)格式赤炒,本小節(jié)的消息格式是指消息以什么樣的形態(tài)交給netty從而在網(wǎng)絡(luò)上進(jìn)行傳輸

消息格式由MsgHeader及MsgBody組成,而消息的長(zhǎng)度亏较、標(biāo)記莺褒、版本等重要參數(shù)都放在 header 中,body 中僅僅存儲(chǔ)數(shù)據(jù)雪情,沒有額外字段遵岩;我們主要看一下 header 的數(shù)據(jù)格式

image.png

而站在 netty 視角來看,不論是 msgHeader 還是 msgBody巡通,都屬于 netty 網(wǎng)絡(luò)消息的body部分尘执,所以我們可以簡(jiǎn)單畫一張 netty 視角的消息格式

image.png

Msg Header的自動(dòng)適配

上文得知,RocketMQ將所有的消息類型宴凉、收發(fā)都收斂到類RemotingCommand中誊锭,但RocketMQ消息類型眾多,除了常見的消息發(fā)送弥锄、接收外丧靡,還有通過msgID查詢消息、msgKey查詢消息籽暇、獲取broker配置温治、清理不再使用的topic等等,用一個(gè)類適配如此多的類型戒悠,具體是如何實(shí)現(xiàn)的呢熬荆?當(dāng)新增、修改一種類型又該怎么應(yīng)對(duì)呢救崔?

翻看源碼便發(fā)現(xiàn)惶看,RemotingCommand的消息頭定義為一個(gè)接口org.apache.rocketmq.remoting.CommandCustomHeader捏顺,不同類型的請(qǐng)求都實(shí)現(xiàn)這個(gè)接口六孵,并在自己的子類中定義成員變量;那RemotingCommand的消息頭又是如何自動(dòng)解析呢幅骄?

public void makeCustomHeaderToNet() {
    if (this.customHeader != null) {
        Field[] fields = getClazzFields(customHeader.getClass());
        if (null == this.extFields) {
            this.extFields = new HashMap<String, String>();
        }
        for (Field field : fields) {
            if (!Modifier.isStatic(field.getModifiers())) {
                String name = field.getName();
                if (!name.startsWith("this")) {
                    Object value = null;
                    try {
                        field.setAccessible(true);
                        value = field.get(this.customHeader);
                    } catch (Exception e) {
                        log.error("Failed to access field [{}]", name, e);
                    }

                    if (value != null) {
                        this.extFields.put(name, value.toString());
                    }
                }
            }
        }
    }
}

答案就是反射劫窒,通過反射獲取子類的全部成員屬性,并放入變量extFields中拆座,makeCustomHeaderToNet()通過犧牲少量性能的方式主巍,換取了程序極大的靈活性與擴(kuò)展性冠息,當(dāng)新增請(qǐng)求類型時(shí),僅需要編寫新請(qǐng)求的encode孕索、decode逛艰,不用修改其他類型請(qǐng)求的代碼

image.png

Topic路由信息

Topic創(chuàng)建
發(fā)送消息的前置是需要?jiǎng)?chuàng)建一個(gè)topic,創(chuàng)建topic的admin命令如下

updateTopic -b <> -t <> -r <> -w <> -p <> -o <> -u <> -s <>

例如:
updateTopic -b 127.0.0.1:10911 -t testTopic -r 8 -w 8 -p 6 -o false -u false -s false

簡(jiǎn)單介紹下每個(gè)參數(shù)的作用

-b broker 地址搞旭,表示 topic 所在 Broker散怖,只支持單臺(tái)Broker,地址為ip:port
-c cluster 地址肄渗,表示 topic 所在 cluster镇眷,會(huì)向 cluster 中所有的 broker 發(fā)送請(qǐng)求
-t topic 名稱
-r 可讀隊(duì)列數(shù)(默認(rèn)為 8,后文還會(huì)展開)
-w 可寫隊(duì)列數(shù)(默認(rèn)為 8翎嫡,后文還會(huì)展開)
-p 指定新topic的讀寫權(quán)限 (W=2|R=4|WR=6)2表示當(dāng)前topic僅可寫入數(shù)據(jù)欠动,4表示僅可讀,6表示可讀可寫
-o set topic's order(true|false)
-u is unit topic (true|false)
-s has unit sub (true|false)

如果執(zhí)行命令updateTopic -b 127.0.0.1:8899 -t testTopic -r 8 -w 8 意味著會(huì)在127.0.0.1:8899對(duì)應(yīng)的broker下創(chuàng)建一個(gè)topic惑申,這個(gè)topic的讀寫隊(duì)列都是 8

那如果是這樣的場(chǎng)景呢:集群A有3個(gè)master節(jié)點(diǎn)具伍,當(dāng)執(zhí)行命令updateTopic -c clusterName -t testTopic -r 8 -w 8 后,站在集群A角度來看圈驼,當(dāng)前topic總共創(chuàng)建了多少個(gè)寫隊(duì)列沿猜?其實(shí) RocketMQ 接到這條命令后,會(huì)向3個(gè) broker 分別發(fā)送創(chuàng)建 topic 的命令碗脊,這樣每個(gè)broker上都會(huì)有8個(gè)讀隊(duì)列啼肩,8個(gè)寫隊(duì)列,所以站在集群的視角衙伶,這個(gè) topic 總共會(huì)有 24 個(gè)讀隊(duì)列祈坠,24 個(gè)寫隊(duì)列

創(chuàng)建流程

一、創(chuàng)建Topic的客戶端(DefaultMQAdminExt)
第一步:該客戶端的啟動(dòng)流程與Producer矢劲、Consumer類似赦拘,需要start(),它們共用MQClientInstance#start()方法芬沉,啟動(dòng)后還有多個(gè)后臺(tái)輪訓(xùn)線程
第二步:通過與NameServer交互躺同,將指定ClusterName下所有的Broker信息拉下來
第三步:依次向這些Broker發(fā)送創(chuàng)建Topic的請(qǐng)求

二、Broker
第一步:Broker收到創(chuàng)建Topic的請(qǐng)求后丸逸,做一些新Topic的初始化動(dòng)作蹋艺,而后將該Topic的元數(shù)據(jù)存儲(chǔ)在一個(gè)name為topics.json的本地文件中,因?yàn)樵贜ameServer中并沒有對(duì)數(shù)據(jù)進(jìn)行持久化黄刚,所以此文件即為Topic路由數(shù)據(jù)的唯一持久化文件捎谨,當(dāng)然這樣的Broker一般是有多套的(其實(shí)此處是將所有json數(shù)據(jù)全部實(shí)例化后,替換本地文件,真實(shí)生產(chǎn)中涛救,如果頻繁創(chuàng)建畏邢、銷毀topic,會(huì)帶來大量的文件IO检吆,以及內(nèi)存負(fù)擔(dān)舒萎,相信在未來近期的某個(gè)版本一定會(huì)進(jìn)行修復(fù))
第二步:向所有NameServer列表挨個(gè)發(fā)送Topic注冊(cè)請(qǐng)求

三、NameServer
NameServer收到Broker注冊(cè)Topic的消息后蹭沛,便將其路由信息存儲(chǔ)在內(nèi)存中逆甜,當(dāng)有Client請(qǐng)求Topic路由數(shù)據(jù)時(shí),便將結(jié)果同步過去

我們以3個(gè)Broker致板、2個(gè)NameServer的集群舉例:


image.png

Client → Broker :3 次網(wǎng)絡(luò)IO交煞,Client需要挨個(gè)向多個(gè)Broker發(fā)送注冊(cè)請(qǐng)求
Broker → NameServer:6 次網(wǎng)絡(luò)IO,Broker需要向所有NameServer發(fā)送注冊(cè)請(qǐng)求

由此可見斟或,NameServer確實(shí)是輕狀態(tài)的節(jié)點(diǎn)素征,路由的原始數(shù)據(jù)其實(shí)都存儲(chǔ)在Broker上,通過Broker向NameServer注冊(cè)萝挤,再有Client從NameServer處獲取元數(shù)據(jù)的方式來進(jìn)行廣播御毅、同步。此方案是rmq獨(dú)創(chuàng)怜珍,與kafka的重ZooKeeper形成對(duì)比端蛆,不過從實(shí)踐角度看,該架構(gòu)還是比較穩(wěn)定的

writeQueueNum VS readQueueNum

首選需要明確的是酥泛,讀今豆、寫隊(duì)列,這兩個(gè)概念是 RocketMQ 獨(dú)有的柔袁,而 kafka 中只有一個(gè)partition的概念呆躲,不區(qū)分讀寫。一般情況下捶索,這兩個(gè)值建議設(shè)置為相等插掂;我們分別看一下 client 端對(duì)它們的處理 (均在類MQClientInstance.java中)

producer端:

for (int i = 0; i < qd.getWriteQueueNums(); i++) {
    MessageQueue mq = new MessageQueue(topic, qd.getBrokerName(), i);
    info.getMessageQueueList().add(mq);
}

consumer端

for (int i = 0; i < qd.getReadQueueNums(); i++) {
    MessageQueue mq = new MessageQueue(topic, qd.getBrokerName(), i);
    mqList.add(mq);
}

如果2個(gè)隊(duì)列設(shè)置不相等,例如我們?cè)O(shè)置6個(gè)寫隊(duì)列腥例,4個(gè)讀隊(duì)列的話:

image.png

這樣辅甥,4、5號(hào)隊(duì)列中的數(shù)據(jù)一定不會(huì)被消費(fèi)燎竖。

writeQueueNum > readQueueNum
大于 readQueueNum 部分的隊(duì)列永遠(yuǎn)不會(huì)被消費(fèi)
writeQueueNum < readQueueNum
所有隊(duì)列中的數(shù)據(jù)都會(huì)被消費(fèi)璃弄,但部分讀隊(duì)列數(shù)據(jù)一直是空的

這樣設(shè)計(jì)有什么好處呢?其實(shí)是更精細(xì)的控制了讀寫操作底瓣,例如當(dāng)我們要遷移 broker 時(shí)谢揪,可以首先將寫入隊(duì)列設(shè)置為0,將客戶端引流至其他 broker 節(jié)點(diǎn)捐凭,等讀隊(duì)列數(shù)據(jù)也處理完畢后拨扶,再關(guān)閉 read 操作

路由數(shù)據(jù)格式

topic的路由數(shù)據(jù)如何由Admin發(fā)起創(chuàng)建,再被各個(gè)broker響應(yīng)茁肠,繼而被nameServer統(tǒng)一組織創(chuàng)建的流程我們暫且不討論患民,為防止發(fā)散,我們直接從producer從nameServer獲取路由數(shù)據(jù)開始垦梆。從nameServer獲取到的路由數(shù)據(jù)格式如下

public class TopicRouteData extends RemotingSerializable {
    private String orderTopicConf;
    private List<QueueData> queueDatas;
    private List<BrokerData> brokerDatas;
    private HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
}

而存放路由數(shù)據(jù)的結(jié)構(gòu)是queueDatas及brokerDatas

public class QueueData implements Comparable<QueueData> {
    private String brokerName;
    private int readQueueNums;
    private int writeQueueNums;
}

public class BrokerData implements Comparable<BrokerData> {
    private String cluster;
    private String brokerName;
    private HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs;
}

在此匹颤,簡(jiǎn)單闡述一下RocketMQ的cluster嗦明、brokerName钉答、brokerId的概念


image.png

上圖描述了一個(gè)cluster下有3個(gè)broker,每個(gè)broker又有1個(gè)master遵馆,2個(gè)slave組成京腥;這也就是為什么類BrokerData中有HashMap<Long, String> brokerAddrs變量的原因赦肃,因?yàn)榭赡芡粋€(gè)brokerName下由多個(gè)節(jié)點(diǎn)組成。注:master節(jié)點(diǎn)的編號(hào)始終為0

Topic路由信息何時(shí)發(fā)生變化

這些路由信息什么時(shí)候發(fā)生變化呢公浪?我們舉例說明

舉例1:某集群有3臺(tái) master他宛,分別向其中的2臺(tái)發(fā)送了創(chuàng)建topic的命令,此時(shí)所有的clent端都知道這個(gè)topic的數(shù)據(jù)在這兩個(gè)broker上欠气;這個(gè)時(shí)候通過admin向第3臺(tái)broker發(fā)送創(chuàng)建topic命令厅各,nameServer的路由信息便發(fā)生了變更,等client端30秒輪訓(xùn)后预柒,便可以更新到最新的topic路由信息

舉例2:某集群有3臺(tái) master队塘,topic分別在3臺(tái)broker上都創(chuàng)建了,此時(shí)某臺(tái)broker宕機(jī)宜鸯,nameServer將其摘除人灼,等待30秒輪詢后,client拿到最新路由信息

思考:client 端路由信息的變化是依托于30秒的輪詢顾翼,如果路由信息已經(jīng)發(fā)生變化投放,且輪詢未發(fā)生,client端拿著舊的topic路由信息訪問集群适贸,一定會(huì)有短暫報(bào)錯(cuò)灸芳,此處也是待優(yōu)化的點(diǎn)

定時(shí)更新Topic路由信息

RocketMQ會(huì)每隔30秒更新topic的路由信息

image.png

與Broker心跳

主要分為兩部分:

1、清空無效broker
2拜姿、向有效的broker發(fā)送心跳
2.4.1烙样、清空無效的broker
由上節(jié)得知,RocketMQ會(huì)獲取所有已經(jīng)注冊(cè)的topic所在的broker信息蕊肥,并將這些信息存儲(chǔ)在變量brokerAddrTable中谒获,brokerAddrTable的存儲(chǔ)結(jié)構(gòu)如下

key: brokerName蛤肌,例如一個(gè)master帶2個(gè)slave都屬于同一個(gè)brokerName
val: HashMap<Long, String>,key為brokerId(其中master的brokerId固定為0)批狱,val為ip地址

如何判斷某個(gè)broker有效無效呢裸准?判斷依據(jù)便是MQClientInstance#topicRouteTable,這個(gè)變量是上節(jié)中從nameServer中同步過來的赔硫,如果brokerAddrTable中有broker A炒俱、B、C爪膊,而topicRouteTable只有A权悟、B的話,那么就需要從brokerAddrTable中刪除C推盛。

需要注意的是峦阁,在整個(gè)check及替換過程中都添加了獨(dú)占鎖lockNamesrv,而上節(jié)中維護(hù)更新topic路由信息也是指定的該鎖

發(fā)送心跳數(shù)據(jù)
image.png

此處目的僅為與broker保持網(wǎng)絡(luò)心跳耘成,如果連接失敗或發(fā)生異常拇派,僅會(huì)打印日志,并不會(huì)有額外操作

多Producer

這里簡(jiǎn)單提一下凿跳,其實(shí)在單個(gè)進(jìn)程中件豌,是可以啟動(dòng)多個(gè)Producer的,且相互隔離控嗜;實(shí)現(xiàn)起來感覺也比較容易茧彤,感覺直接new DefaultMQProducer()就行。不過這里有個(gè)性能上的問題疆栏,就是如果兩個(gè)Producer操作了同樣的Topic曾掂,此時(shí)去NameServer拉取路由數(shù)據(jù)的時(shí)候,將會(huì)線性的放大壁顶,因此RMQ引入了MQClientInstance概念珠洗,即在單個(gè)進(jìn)程中,MQClientInstance是單例的若专,諸如獲取Topic路由數(shù)據(jù)等许蓖,均是其統(tǒng)一發(fā)起,讀者在源碼中看到這個(gè)類時(shí)不要覺得陌生哈

image.png

消息發(fā)送

image.png

消息發(fā)送比較重要的是2點(diǎn)內(nèi)容

發(fā)送數(shù)據(jù)的負(fù)載均衡問題调衰;RocketMQ默認(rèn)采用的是輪訓(xùn)的方式
消息發(fā)送的方式膊爪;分同步、異步嚎莉、單向

消息保序 vs 負(fù)載均衡

默認(rèn)選擇隊(duì)列的策略為輪詢方式米酬,來保證消息可以均勻的分配到每個(gè)隊(duì)列;

既然說到隊(duì)列趋箩,就不得不提到消息的有序性問題

普通消息

消息是無序的赃额,可發(fā)送至任意隊(duì)列加派,producer 也不關(guān)心消息會(huì)存儲(chǔ)在哪個(gè)隊(duì)列。在這種模式下跳芳,如果發(fā)送失敗芍锦,producer 會(huì)按照輪詢的方式,重新選取下一個(gè)隊(duì)列進(jìn)行重試

producer.send(message);

普通有序消息

用戶可根據(jù)消息內(nèi)容來選擇一個(gè)隊(duì)列發(fā)送 筛严,在這種情況下醉旦,消息也一般是保序的饶米,例如我們可以通過業(yè)務(wù)字段(例如用戶id)的 msgKey 取模來選擇隊(duì)列桨啃,這樣同樣 msgKey 的消息必定會(huì)落在同一個(gè)隊(duì)列中。

與發(fā)送普通消息不同檬输,如果發(fā)送失敗照瘾,將不會(huì)進(jìn)行重試,也比較好理解丧慈,普通消息發(fā)送失敗后析命,也不會(huì)針對(duì)當(dāng)前隊(duì)列進(jìn)行重試,而是選擇下一個(gè)隊(duì)列

producer.send(zeroMsg, (mqs, msg, arg) -> {
    int index = msg.getKeys().hashCode() % mqs.size();
    return mqs.get(index);
}, 1000);

但也存在異常情況逃默,例如當(dāng)前 topic 的路由信息發(fā)生了變化鹃愤,取模后消息可能命中了另外一個(gè)隊(duì)列,自然也無法做到嚴(yán)格保序

嚴(yán)格有序消息

即 producer 自己嚴(yán)格發(fā)送給指定的隊(duì)列完域,如果發(fā)送異常則快速失敗软吐,可見這種方式可以嚴(yán)格保證發(fā)送的消息在同一個(gè)隊(duì)列中,即便 topic 路由信息發(fā)生變化吟税,也可以嚴(yán)格保序

producer.send(message, messageQueue);

消息發(fā)送的3種方式

RocketMQ的rpc組件采用的是netty凹耙,而netty的網(wǎng)絡(luò)請(qǐng)求設(shè)計(jì)是完全異步的,所以一個(gè)請(qǐng)求一定可以拆成以下3個(gè)步驟

a肠仪、客戶端發(fā)送請(qǐng)求到服務(wù)器(由于完全異步肖抱,所以請(qǐng)求數(shù)據(jù)可能只放在了socket緩沖區(qū),并沒有出網(wǎng)卡)
b异旧、服務(wù)器端處理請(qǐng)求(此過程不涉及網(wǎng)絡(luò)開銷意述,不過通常也是比較耗時(shí)的)
c、服務(wù)器向客戶端返回應(yīng)答(請(qǐng)求的response)

同步發(fā)送消息

SendResult result = producer.send(zeroMsg);

此過程比較好理解吮蛹,即完成a欲险、b、c所有步驟后才會(huì)返回匹涮,耗時(shí)也是 a + b + c 的總和

3.2.2天试、異步發(fā)送消息
通常在業(yè)務(wù)中發(fā)送消息的代碼如下:

SendCallback sendCallback = new SendCallback() {
    @Override
    public void onSuccess(SendResult sendResult) {
        // doSomeThing;
    }
    @Override
    public void onException(Throwable e) {
        // doSomeThing;
    }
};
producer.send(zeroMsg, sendCallback);

而RocketMQ處理異步消息的邏輯是,直接啟動(dòng)一個(gè)線程然低,而最終的結(jié)果異步回調(diào)SendCallback

ExecutorService executor = this.getAsyncSenderExecutor();
try {
    executor.submit(new Runnable() {
        @Override
        public void run() {
            try {
                sendDefaultImpl(msg, CommunicationMode.ASYNC, sendCallback, timeout - costTime);
            } catch (Exception e) {
                sendCallback.onException(e);
            }
        }

    });
} catch (RejectedExecutionException e) {
    throw new MQClientException("executor rejected ", e);
}

單向發(fā)送消息

producer.sendOneway(zeroMsg);

此模式與sync模式類似喜每,都要經(jīng)過producer端在數(shù)據(jù)發(fā)送前的數(shù)據(jù)組裝工作务唐,不過在將數(shù)據(jù)交給netty,netty調(diào)用操作系統(tǒng)函數(shù)將數(shù)據(jù)放入socket緩沖區(qū)后带兜,所有的過程便已結(jié)束枫笛。什么場(chǎng)景會(huì)用到此模式呢?比如對(duì)可靠性要求并不高刚照,但要求耗時(shí)非常短的場(chǎng)景刑巧,比如日志收集等

三個(gè)請(qǐng)求哪個(gè)更快呢?如果單論一個(gè)請(qǐng)求的話无畔,肯定是async異步的方式最快啊楚,因?yàn)樗苯影压ぷ鹘唤o另外一個(gè)線程去完成,主線程直接返回了浑彰;但不論是async還是sync恭理,它們都是需要將 a、b郭变、c 3個(gè)步驟都走完的颜价,所以總開銷并不會(huì)減少。但oneWay因?yàn)橹恍鑼?shù)據(jù)放入socket緩沖區(qū)后诉濒,client 端就直接返回了周伦,少了監(jiān)聽并解析 server 端 response 的過程,所以可以得到最好的性能

小結(jié)

本文闡述了producer端相對(duì)重要的一些功能點(diǎn)未荒,感覺比較核心的還是隊(duì)列相關(guān)的概念专挪;但RocketMQ發(fā)展迭代了這么多年,也涵蓋了很多及細(xì)小的特性茄猫,本文不能窮盡狈蚤,比如“消息的壓縮”、“規(guī)避發(fā)送延遲較長(zhǎng)的broker”划纽、“超時(shí)異炒辔辏”等等,這些功能點(diǎn)獨(dú)立且零碎勇劣,讀源碼時(shí)可以帶著問題跟進(jìn)靖避,這樣針對(duì)性強(qiáng),效率也會(huì)高很多

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末比默,一起剝皮案震驚了整個(gè)濱河市幻捏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌命咐,老刑警劉巖篡九,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異醋奠,居然都是意外死亡榛臼,警方通過查閱死者的電腦和手機(jī)伊佃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沛善,“玉大人航揉,你說我怎么就攤上這事〗鸬螅” “怎么了帅涂?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)尤蛮。 經(jīng)常有香客問我媳友,道長(zhǎng),這世上最難降的妖魔是什么抵屿? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任庆锦,我火速辦了婚禮捅位,結(jié)果婚禮上轧葛,老公的妹妹穿的比我還像新娘。我一直安慰自己艇搀,他們只是感情好尿扯,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著焰雕,像睡著了一般衷笋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矩屁,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天辟宗,我揣著相機(jī)與錄音,去河邊找鬼吝秕。 笑死泊脐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烁峭。 我是一名探鬼主播容客,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼约郁!你這毒婦竟也來了缩挑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤鬓梅,失蹤者是張志新(化名)和其女友劉穎供置,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绽快,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芥丧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年悲关,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娄柳。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寓辱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赤拒,到底是詐尸還是另有隱情秫筏,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布挎挖,位于F島的核電站这敬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蕉朵。R本人自食惡果不足惜崔涂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望始衅。 院中可真熱鬧冷蚂,春花似錦、人聲如沸汛闸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诸老。三九已至隆夯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間别伏,已是汗流浹背蹄衷。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厘肮,地道東北人愧口。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像轴脐,于是被迫代替她去往敵國(guó)和親调卑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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