一文讀懂物聯(lián)網(wǎng) MQTT 協(xié)議之基礎(chǔ)特性篇

點(diǎn)擊上方老周聊架構(gòu)關(guān)注我
一、前言
上個(gè)月有個(gè)讀者問(wèn)我物聯(lián)網(wǎng) MQTT 協(xié)議實(shí)戰(zhàn)相關(guān)的問(wèn)題,我說(shuō)后面會(huì)搞,沒(méi)想到不知不覺(jué)一個(gè)月了,太忙了,再怎么忙答應(yīng)的事情還是要給讀者一個(gè)交代,所以就有了此文。

二、MQTT 協(xié)議概要
2.1 什么是 MQTT 協(xié)議
MQTT(Message Queuing Telemetry Transport,消息隊(duì)列遙測(cè)傳輸協(xié)議),是一種基于發(fā)布/訂閱(publish/subscribe)模式的“輕量級(jí)”通訊協(xié)議,該協(xié)議構(gòu)建于 TCP/IP 協(xié)議上,由 IBM 于 1999 年發(fā)明。MQTT 協(xié)議的主要特征是開(kāi)放、簡(jiǎn)單、輕量級(jí)和易于實(shí)現(xiàn),這些特征使得它適用于受約束的應(yīng)用環(huán)境,如:
網(wǎng)絡(luò)受限:網(wǎng)絡(luò)帶寬較低且傳輸不可靠終端受限:協(xié)議運(yùn)行在嵌入式設(shè)備上,嵌入式終端的處理器、內(nèi)存等是受限的
通過(guò) MQTT 協(xié)議,目前已經(jīng)擴(kuò)展出了數(shù)十種 MQTT 服務(wù)器端程序,可以通過(guò) PHP、Java、Python、C、C# 等語(yǔ)言向 MQTT 發(fā)送消息。由于開(kāi)放源代碼、耗電量小等特點(diǎn),MQTT 非常適用于物聯(lián)網(wǎng)領(lǐng)域,如傳感器與服務(wù)器的通信、傳感器信息采集等。
2.2 發(fā)布/訂閱模式
發(fā)布/訂閱模式并不是 MQTT 協(xié)議特有的模式,像我們很多消息中間件都有使用發(fā)布/訂閱模式,這里你是不是想說(shuō),這不就是我們所說(shuō)的觀察者模式嘛,還真不是,這兩個(gè)模式很容易混淆。觀察者模式只有 觀察者 + 被觀察者兩個(gè)角色,而發(fā)布/訂閱模式還有一個(gè)經(jīng)紀(jì)人 Broker;往更深層次的講觀察者和被觀察者,是松耦合的關(guān)系,而發(fā)布者和訂閱者,則完全不存在耦合。
在客戶端/服務(wù)器模型中,客戶端直接與服務(wù)器端點(diǎn)通信。而發(fā)布/訂閱模式 pub/sub 就不一樣了,發(fā)布/訂閱模式會(huì)將發(fā)送消息的發(fā)布者 publisher 與接收消息的訂閱者 subscribers 進(jìn)行分離,publisher 與 subscribers 并不會(huì)直接通信,他們甚至都不清楚對(duì)方是否存在,他們之間的交流由第三方組件 broker 代理。

pub/sub 最重要的方面是消息的發(fā)布者與接收者(訂閱者)的解耦。這種解耦有幾個(gè)維度:
空間解耦:發(fā)布者和訂閱者不需要相互了解(例如,不交換 IP 地址和端口)。
時(shí)間解耦:發(fā)布者和訂閱者不需要同時(shí)運(yùn)行。
同步解耦:兩個(gè)組件的操作在發(fā)布或接收時(shí)不需要中斷。
總之,發(fā)布/訂閱模式消除了傳統(tǒng)客戶端/服務(wù)器之間的直接通信,把通信這個(gè)操作交給了 broker 進(jìn)行代理,并在空間、時(shí)間、同步三個(gè)維度上進(jìn)行了解耦。
2.3 可擴(kuò)展性
pub/sub 比傳統(tǒng)的客戶端/服務(wù)器模式有了更好的拓展,這是由于 broker 的高度并行化,并且是基于事件驅(qū)動(dòng)的模式。可擴(kuò)展性還體現(xiàn)在消息的緩存和消息的智能路由,還可以通過(guò)集群代理來(lái)實(shí)現(xiàn)數(shù)百萬(wàn)的連接,使用負(fù)載均衡器將負(fù)載分配到更多的單個(gè)服務(wù)器上,這就是 MQTT 的深度應(yīng)用了。
2.4 消息過(guò)濾
很明顯,broker 在 pub/sub 過(guò)程中起著舉足輕重的作用。但是代理如何過(guò)濾所有消息,以便每個(gè)訂閱者只接收感興趣的消息?broker 有幾個(gè)可以過(guò)濾的選項(xiàng):
基于主題的過(guò)濾
此過(guò)濾基于屬于每條消息的主題。接收客戶端向代理訂閱感興趣的主題,訂閱后,broker 就會(huì)確??蛻舳耸盏桨l(fā)布到 topic 中的消息。基于內(nèi)容的過(guò)濾
在基于內(nèi)容的過(guò)濾中,broker 會(huì)根據(jù)特定的內(nèi)容過(guò)濾消息,接受客戶端會(huì)經(jīng)過(guò)過(guò)濾他們感興趣的內(nèi)容。這種方法的一個(gè)顯著的缺點(diǎn)就是必須事先知道消息的內(nèi)容,不能加密或者輕易修改。基于類型的過(guò)濾
當(dāng)使用面向?qū)ο蟮恼Z(yǔ)言時(shí),基于消息(事件)的類型/類進(jìn)行過(guò)濾是一種常見(jiàn)做法。例如,訂閱者可以收聽(tīng)所有類型為 Exception 或任何子類型的消息。
2.5 MQTT 與消息隊(duì)列的區(qū)別
這里你又會(huì)說(shuō)了,既然 MQTT 與主流的消息的隊(duì)列都采用發(fā)布/訂閱模式,那他們就是一樣的。這里老周得再提一嘴,確實(shí)和消息隊(duì)列很多相似的地方,但還有有些差異的,下面就來(lái)說(shuō)道說(shuō)道:
消息隊(duì)列存儲(chǔ)消息直到消息被消費(fèi)使用消息隊(duì)列時(shí),每條傳入消息都存儲(chǔ)在隊(duì)列中,直到被客戶端(通常稱為消費(fèi)者)接收。如果沒(méi)有客戶端接收到消息,消息將保持在隊(duì)列中并等待被消費(fèi)。在消息隊(duì)列中,不會(huì)存在消息沒(méi)有客戶端消費(fèi)的情況,但是在 MQTT 中,卻存在 topic 無(wú) subscriber 訂閱的情況。一條消息只被一個(gè)客戶端消費(fèi)另一個(gè)很大的區(qū)別是,在傳統(tǒng)的消息隊(duì)列中,一條消息只能被一個(gè)消費(fèi)者處理。負(fù)載分布在隊(duì)列的所有消費(fèi)者之間。在 MQTT 中,行為完全相反:訂閱主題的每個(gè)訂閱者都會(huì)收到消息,每個(gè)訂閱者有相同的負(fù)載。隊(duì)列是命名的,必須顯式創(chuàng)建?隊(duì)列比主題嚴(yán)格得多。在使用隊(duì)列之前,必須使用單獨(dú)的命令顯式創(chuàng)建隊(duì)列。只有在隊(duì)列命名和創(chuàng)建之后,才可以發(fā)布或消費(fèi)消息。相比之下,MQTT 主題非常靈活,可以即時(shí)創(chuàng)建。
三、MQTT 重要概念
3.1 MQTT Client
publisher 和 subscriber 都屬于 MQTT Client,之所以有發(fā)布者和訂閱者這個(gè)概念,其實(shí)是一種相對(duì)的概念,就是指當(dāng)前客戶端是在發(fā)布消息還是在接收消息,發(fā)布和訂閱的功能也可以由同一個(gè) MQTT Client 實(shí)現(xiàn)。
MQTT 客戶端是運(yùn)行 MQTT 庫(kù)并通過(guò)網(wǎng)絡(luò)連接到 MQTT 代理的任何設(shè)備(從微控制器到成熟的服務(wù)器)。例如,MQTT 客戶端可以是一個(gè)非常小的、資源受限的設(shè)備,它通過(guò)無(wú)線網(wǎng)絡(luò)進(jìn)行連接并具有一個(gè)最低限度的庫(kù)?;旧?,任何使用 TCP/IP 協(xié)議使用 MQTT 設(shè)備的都可以稱之為 MQTT Client。MQTT 協(xié)議的客戶端實(shí)現(xiàn)非常簡(jiǎn)單直接,易于實(shí)施是 MQTT 非常適合小型設(shè)備的原因之一。MQTT 客戶端庫(kù)可用于多種編程語(yǔ)言。例如,Android、Arduino、C、C++、C#、Go、iOS、Java、JavaScript 和 .NET。
3.2 MQTT Broker
與 MQTT Client 對(duì)應(yīng)的就是 MQTT Broker,Broker 是任何發(fā)布/訂閱協(xié)議的核心,根據(jù)實(shí)現(xiàn)的不同,代理可以處理多達(dá)數(shù)百萬(wàn)連接的 MQTT Client。
Broker 負(fù)責(zé)接收所有消息,過(guò)濾消息,確定是哪個(gè)Client 訂閱了每條消息,并將消息發(fā)送給對(duì)應(yīng)的 Client,Broker 還負(fù)責(zé)保存會(huì)話數(shù)據(jù),這些數(shù)據(jù)包括訂閱的和錯(cuò)過(guò)的消息。Broker 還負(fù)責(zé)客戶端的身份驗(yàn)證和授權(quán)。
3.3 MQTT Connection
MQTT 協(xié)議基于 TCP/IP??蛻舳撕痛矶夹枰幸粋€(gè) TCP/IP 協(xié)議支持。

MQTT 連接始終位于一個(gè)客戶端和代理之間。客戶端從不直接相互連接。要發(fā)起連接,客戶端向代理發(fā)送 CONNECT 消息。代理使用 CONNACK 消息和狀態(tài)代碼進(jìn)行響應(yīng)。建立連接后,代理將保持打開(kāi)狀態(tài),直到客戶端發(fā)送斷開(kāi)連接命令或連接中斷。

四、消息列表
4.1 CONNECT
為了創(chuàng)建連接,客戶端向代理發(fā)送命令消息。如果此 CONNECT 消息格式錯(cuò)誤(根據(jù) MQTT 規(guī)范)或打開(kāi)網(wǎng)絡(luò)套接字和發(fā)送連接消息之間的時(shí)間過(guò)長(zhǎng),代理將關(guān)閉連接。
一個(gè) MQTT 客戶端發(fā)送一條 CONNECT 連接,這條 CONNECT 連接可能會(huì)包含下面這些信息:

我們將重點(diǎn)關(guān)注以下選項(xiàng):
ClientId:ClientId的長(zhǎng)度可以是 1-23 個(gè)字符,在一個(gè)服務(wù)器上 ClientId 不能重復(fù)。如果超過(guò) 23 個(gè)字符,則服務(wù)器返回 CONNACK 消息中的返回碼為 Identifier Rejected。在 MQTT 3.1.1 中,如果您不需要代理持有狀態(tài),您可以發(fā)送一個(gè)空的 ClientId??盏?ClientId 導(dǎo)致連接沒(méi)有任何狀態(tài)。在這種情況下,clean session 標(biāo)志必須設(shè)置為 true,否則代理將拒絕連接。Clean Session:Clean Session 標(biāo)志告訴代理客戶端是否要建立持久會(huì)話。在持久會(huì)話 (CleanSession = false) 中,代理存儲(chǔ)客戶端的所有訂閱以及以服務(wù)質(zhì)量(QoS)級(jí)別 1 或 2 訂閱的客戶端的所有丟失消息。如果會(huì)話不是持久的 (CleanSession = true ),代理不為客戶端存儲(chǔ)任何內(nèi)容,并清除任何先前持久會(huì)話中的所有信息。Username/Password:MQTT 可以發(fā)送用戶名和密碼進(jìn)行客戶端認(rèn)證和授權(quán)。但是,如果此信息未加密或散列,則密碼將以純文本形式發(fā)送。我們強(qiáng)烈建議將用戶名和密碼與安全傳輸一起使用。像 HiveMQ 這樣的代理可以使用 SSL 證書(shū)對(duì)客戶端進(jìn)行身份驗(yàn)證,因此不需要用戶名和密碼。Will Message:LastWillxxx 表示的是遺愿,client 在連接 broker 的時(shí)候?qū)?huì)設(shè)立一個(gè)遺愿,這個(gè)遺愿會(huì)保存在 broker 中,當(dāng) client 因?yàn)榉钦T驍嚅_(kāi)與 broker 的連接時(shí),broker 會(huì)將遺愿發(fā)送給訂閱了這個(gè) topic(訂閱遺愿的 topic)的 client。KeepAlive:keepAlive 是 client 在連接建立時(shí)與 broker 通信的時(shí)間間隔,通常以秒為單位。這個(gè)時(shí)間指的是 client 與 broker 在不發(fā)送消息下所能承受的最大時(shí)長(zhǎng)。
4.2 CONNACK
當(dāng) broker 收到 CONNECT 消息時(shí),它有義務(wù)回復(fù) CONNACK 消息進(jìn)行響應(yīng)。CONNACK 消息包括兩部分內(nèi)容:
The session present flag:會(huì)話當(dāng)前標(biāo)志A connect return code:連接返回碼

Session Present flag會(huì)話當(dāng)前標(biāo)志,這個(gè)標(biāo)志會(huì)告訴 client 當(dāng)前 broker 是否有一個(gè)持久性會(huì)話與 client 進(jìn)行交互。SessionPresent 標(biāo)志和 CleanSession 標(biāo)志有關(guān),當(dāng) client 在 CleanSession 設(shè)置為 true 的情況下連接時(shí),SessionPresent 始終為 false,因?yàn)闆](méi)有持久性會(huì)話可以使用。如果 CleanSession 設(shè)置為 false,則有兩種可能性,如果 ClientId 的會(huì)話信息可用,并且 broker 已經(jīng)存儲(chǔ)了會(huì)話信息,那么 SessionPresent 為 true,否則如果沒(méi)有 ClientId 的任何會(huì)話信息,那么 SessionPresent 為 false。

Connect return codeCONNACK 消息中的第二個(gè)標(biāo)志是連接確認(rèn)標(biāo)志。這個(gè)標(biāo)志包含一個(gè)返回碼,告訴客戶端連接嘗試是否成功。連接確認(rèn)標(biāo)志有下面這些選項(xiàng):

4.3 PUBLISH
MQTT 客戶端可以在連接到 broker 后立即發(fā)布消息,MQTT 使用的是基于 topic 主題的過(guò)濾。每條消息都必須包含一個(gè)主題,broker 可以使用該主題將消息轉(zhuǎn)發(fā)給感興趣的客戶端。通常,每條消息都有一個(gè)負(fù)載(Payload),其中包含要以字節(jié)格式傳輸?shù)臄?shù)據(jù)。MQTT 是數(shù)據(jù)無(wú)關(guān)性的,也就是說(shuō)數(shù)據(jù)是由發(fā)布者 - publisher 決定要發(fā)送的是 XML 、JSON 還是二進(jìn)制數(shù)據(jù)、文本數(shù)據(jù)。
MQTT 中的 PUBLISH 消息有幾個(gè)我們想要詳細(xì)討論的屬性:

Topic Name:主題名稱是一個(gè)簡(jiǎn)單的字符串,它以正斜杠作為分隔符進(jìn)行分層結(jié)構(gòu)。例如,“我的家/客廳/溫度”或“德國(guó)/慕尼黑/十月節(jié)/人”。QoS:此數(shù)字表示消息的服務(wù)質(zhì)量 (QoS)。有三個(gè)級(jí)別:0、1 和 2。服務(wù)級(jí)別決定了消息到達(dá)預(yù)期接收者(客戶端或代理)的保證類型。Retain Flag:此標(biāo)志表示 broker 將最近收到的一條 RETAIN 標(biāo)志位為 true 的消息保存在服務(wù)器端(內(nèi)存或者文件)。Payload:這個(gè)是每條消息的實(shí)際內(nèi)容。MQTT 是數(shù)據(jù)無(wú)關(guān)性的??梢园l(fā)送任何文本、圖像、加密數(shù)據(jù)以及二進(jìn)制數(shù)據(jù)。Packet Identifier:這個(gè) packetId 標(biāo)識(shí)在 client 和 broker 之間唯一的消息標(biāo)識(shí)。packetId 僅與大于零的 QoS 級(jí)別相關(guān)。DUP flag:該標(biāo)志表明該消息是重復(fù)的并且由于預(yù)期的接收者(客戶端或代理)沒(méi)有確認(rèn)原始消息而被重新發(fā)送。這僅與 QoS 大于 0 相關(guān)。
當(dāng)客戶端向 MQTT broker 發(fā)送消息進(jìn)行發(fā)布時(shí),broker 讀取消息、確認(rèn)消息(根據(jù) QoS 級(jí)別)并處理消息。broker 的處理包括確定哪些客戶端訂閱了主題并將消息發(fā)送給他們。

最初發(fā)布消息的客戶端只關(guān)心將 PUBLISH 消息傳遞給 broker。一旦 broker 收到 PUBLISH 消息,broker 就有責(zé)任將消息傳遞給所有訂閱者。發(fā)布客戶端不會(huì)得到關(guān)于是否有人對(duì)發(fā)布的消息感興趣或有多少客戶端從 broker 收到消息的任何反饋。
4.4 Subscribe
client 會(huì)向 broker 發(fā)送 SUBSCRIBE 消息來(lái)接收有關(guān)感興趣的 topic,這個(gè) SUBSCRIBE 消息非常簡(jiǎn)單,它包含了一個(gè)唯一的數(shù)據(jù)包標(biāo)識(shí)和一個(gè)訂閱列表。

Packet Identifier:這個(gè) PacketId 和上面的 PacketId 一樣,都表示消息的唯一標(biāo)識(shí)符。
List of Subscriptions:一個(gè) SUBSCRIBE 消息可以包含一個(gè)客戶端的多個(gè)訂閱。每個(gè)訂閱由一個(gè)主題和一個(gè) QoS 級(jí)別組成。訂閱消息中的主題可以包含通配符,使訂閱主題模式而不是特定主題成為可能。如果一個(gè)客戶端存在重疊訂閱,則代理會(huì)傳送該主題具有最高 QoS 級(jí)別的消息。
4.5 Suback
為了確認(rèn)每個(gè)訂閱,broker 向客戶端發(fā)送一個(gè) SUBACK 確認(rèn)消息。該消息包含原始 Subscribe 消息的數(shù)據(jù)包標(biāo)識(shí)符(以明確標(biāo)識(shí)該消息)和返回碼列表。

Packet Identifier:包標(biāo)識(shí)符是用于標(biāo)識(shí)消息的唯一標(biāo)識(shí)符。它與 SUBSCRIBE 消息中的相同。Return Code:broker 為它在 SUBSCRIBE 消息中收到的每個(gè)主題/QoS 對(duì)發(fā)送一個(gè)返回代碼。例如,如果 SUBSCRIBE 消息有五個(gè)訂閱,則 SUBACK 消息包含五個(gè)返回碼。返回碼確認(rèn)每個(gè)主題并顯示 broker 授予的 QoS 級(jí)別。如果 broker 拒絕訂閱,則 SUBACK 消息包含該特定主題的失敗返回代碼。例如,如果客戶端沒(méi)有足夠的權(quán)限訂閱主題或主題格式錯(cuò)誤。

客戶端成功發(fā)送 SUBSCRIBE 消息并收到 SUBACK 消息后,它會(huì)獲取與 SUBSCRIBE 消息包含的訂閱中的主題匹配的每條已發(fā)布消息。
4.6 Unsubscribe
SUBSCRIBE 消息的對(duì)應(yīng)是 UNSUBSCRIBE 消息。此消息刪除 broker 上客戶端的現(xiàn)有訂閱。UNSUBSCRIBE 消息與 SUBSCRIBE 消息類似,具有數(shù)據(jù)包標(biāo)識(shí)符和主題列表。

4.7 Unsuback
為了確認(rèn)取消訂閱,broker 向客戶端發(fā)送一個(gè) UNSUBACK 確認(rèn)消息。此消息僅包含原始 UNSUBSCRIBE 消息的數(shù)據(jù)包標(biāo)識(shí)符(以明確標(biāo)識(shí)該消息)。


客戶端收到來(lái)自 broker 的 UNSUBACK 后,可以認(rèn)為 UNSUBSCRIBE 消息中的訂閱被刪除了。
五、Topics
前面我們說(shuō)了很多 MQTT 協(xié)議的格式以及消息列表,這一節(jié)我們來(lái)說(shuō)下 Topics 主題。主題在 MQTT 中很重要,因?yàn)槲覀儗?xiě)代碼的時(shí)候往往都是需要先確認(rèn)好 MQTT 的 Topics。
在 MQTT 中,主題一詞是指 broker 用于為每個(gè)連接的客戶端過(guò)濾消息的 UTF-8 字符串。主題由一個(gè)或多個(gè)主題級(jí)別組成。每個(gè)主題級(jí)別由正斜杠(主題級(jí)別分隔符)分隔。

與消息隊(duì)列相比,MQTT 主題非常輕量級(jí)。客戶端在發(fā)布或訂閱它之前不需要?jiǎng)?chuàng)建所需的主題。broker 接受每個(gè)有效主題而無(wú)需任何事先初始化。
5.1 通配符
當(dāng)客戶端訂閱主題時(shí),它可以訂閱已發(fā)布消息的確切主題,也可以使用通配符同時(shí)訂閱多個(gè)主題。通配符只能用于訂閱主題,不能用于發(fā)布消息。有兩種不同類型的通配符:?jiǎn)渭?jí)和多級(jí)。
單級(jí):+
????顧名思義,單級(jí)通配符替換一個(gè)主題級(jí)別。加號(hào)代表主題中的單級(jí)通配符。
? ? ? ?

? ? ?如果主題包含任意字符串而不是通配符,則任何主題都與具有單級(jí)通配符的主? ? ? ?題匹配。例如,訂閱 myhome/groundfloor/+/temperature 可以產(chǎn)生以下結(jié)果:

多級(jí):#多級(jí)通配符涵蓋多個(gè)主題級(jí)別。哈希符號(hào)代表主題中的多級(jí)通配符。為了讓代理確定哪些主題匹配,多級(jí)通配符必須作為主題中的最后一個(gè)字符放置,并以正斜杠開(kāi)頭。

當(dāng)客戶端訂閱帶有多級(jí)通配符的主題時(shí),無(wú)論主題多長(zhǎng)或多深,它都會(huì)收到以通配符之前的模式開(kāi)頭的主題的所有消息。如果您僅將多級(jí)通配符指定為主題 ( # ),您將收到發(fā)送到 MQTT 代理的所有消息。如果您期望高吞吐量,單獨(dú)使用多級(jí)通配符訂閱是一種反模式(請(qǐng)參閱下面的最佳實(shí)踐)。
5.2 以?$ 開(kāi)頭的主題
通常,您可以根據(jù)需要命名MQTT主題。但是,有一個(gè)例外:以 符號(hào)開(kāi)頭的主題具有不同的目的。當(dāng)您將多級(jí)通配符作為主題 (#) 訂閱時(shí),這些主題不是訂閱的一部分。$-symbol 主題保留用于 MQTT 代理的內(nèi)部統(tǒng)計(jì)信息。客戶端無(wú)法向這些主題發(fā)布消息。目前,此類主題尚無(wú)官方標(biāo)準(zhǔn)化。通常,$SYS/用于所有以下信息,但代理實(shí)現(xiàn)各不相同。MQTT GitHub wiki 中提供了對(duì) $SYS-topics 的一項(xiàng)建議 。這里有些例子:
$SYS/broker/clients/connected
?$SYS/broker/clients/disconnected
?$SYS/broker/clients/total
?$SYS/broker/messages/sent
?$SYS/broker/uptime
可以呀,看到了最后面。授人以魚(yú)不如授人以漁,下面是一個(gè)關(guān)于 MQTT Version 3.1.1 的介紹,有些協(xié)議格式詳細(xì)的可以前往查看。
https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035
本文分基礎(chǔ)特性篇與實(shí)戰(zhàn)篇來(lái)講,下一篇老周會(huì)帶你搭建一個(gè) MQTT 服務(wù)器,讓其他廠商的設(shè)備接入進(jìn)來(lái),盡情期待~
歡迎大家關(guān)注我的公眾號(hào)【老周聊架構(gòu)】,Java后端主流技術(shù)棧的原理、源碼分析、架構(gòu)以及各種互聯(lián)網(wǎng)高并發(fā)、高性能、高可用的解決方案。
喜歡的話,點(diǎn)贊、再看、分享三連。

點(diǎn)個(gè)在看你最好看
