摘要:但是只不過都是以二進(jìn)制的形式編碼的。這其實(shí)相當(dāng)于綜合了和二進(jìn)制共同優(yōu)勢的一個(gè)協(xié)議。在上面的架構(gòu)中,如果使用二進(jìn)制的方式進(jìn)行序列化,雖然不用協(xié)議文件來生成,但是對于接口的定義,以及傳的對象,還是需要共享。
????前面我們認(rèn)識了兩個(gè)常用文本類的 RPC 協(xié)議,對于陌生人之間的溝通,用 NBA、CBA 這樣的縮略語,會使得協(xié)議約定非常不方便。
????在講 CDN 和 DNS 的時(shí)候,我們講過接入層的設(shè)計(jì),對于靜態(tài)資源或者動(dòng)態(tài)資源靜態(tài)化的部分都可以做緩存。但是對于下單、支付等交易場景,還是需要調(diào)用 API。
????對于微服務(wù)的架構(gòu),API 需要一個(gè) API 網(wǎng)關(guān)統(tǒng)一的管理。API 網(wǎng)關(guān)有多種實(shí)現(xiàn)方式,用 Nginx 或者 OpenResty 結(jié)合 Lua 腳本是常用的方式。在上一節(jié)講過的 Spring Cloud 體系中,有個(gè)組件 Zuul 也是干這個(gè)的。
數(shù)據(jù)中心內(nèi)部是如何相互調(diào)用的?????API 網(wǎng)關(guān)用來管理 API,但是 API 的實(shí)現(xiàn)一般在一個(gè)叫作Controller 層的地方。這一層對外提供 API。由于是讓陌生人訪問的,我們能看到目前業(yè)界主流的,基本都是 RESTful 的 API,是面向大規(guī)模互聯(lián)網(wǎng)應(yīng)用的。
????在 Controller 之內(nèi),就是咱們互聯(lián)網(wǎng)應(yīng)用的業(yè)務(wù)邏輯實(shí)現(xiàn)。上節(jié)講 RESTful 的時(shí)候,說過業(yè)務(wù)邏輯的實(shí)現(xiàn)最好是無狀態(tài)的,從而可以橫向擴(kuò)展,但是資源的狀態(tài)還需要服務(wù)端去維護(hù)。資源的狀態(tài)不應(yīng)該維護(hù)在業(yè)務(wù)邏輯層,而是在最底層的持久化層,一般會使用分布式數(shù)據(jù)庫和 ElasticSearch。
????這些服務(wù)端的狀態(tài),例如訂單、庫存、商品等,都是重中之重,都需要持久化到硬盤上,數(shù)據(jù)不能丟,但是由于硬盤讀寫性能差,因而持久化層往往吞吐量不能達(dá)到互聯(lián)網(wǎng)應(yīng)用要求的吞吐量,因而前面要有一層緩存層,使用 Redis 或者 memcached 將請求攔截一道,不能讓所有的請求都進(jìn)入數(shù)據(jù)庫“中軍大營”。
????緩存和持久化層之上一般是基礎(chǔ)服務(wù)層,這里面提供一些原子化的接口。例如,對于用戶、商品、訂單、庫存的增刪查改,將緩存和數(shù)據(jù)庫對再上層的業(yè)務(wù)邏輯屏蔽一道。有了這一層,上層業(yè)務(wù)邏輯看到的都是接口,而不會調(diào)用數(shù)據(jù)庫和緩存。因而對于緩存層的擴(kuò)容,數(shù)據(jù)庫的分庫分表,所有的改變,都截止到這一層,這樣有利于將來對于緩存和數(shù)據(jù)庫的運(yùn)維。
????再往上就是組合層。因?yàn)榛A(chǔ)服務(wù)層只是提供簡單的接口,實(shí)現(xiàn)簡單的業(yè)務(wù)邏輯,而復(fù)雜的業(yè)務(wù)邏輯,比如下單,要扣優(yōu)惠券,扣減庫存等,就要在組合服務(wù)層實(shí)現(xiàn)。
????這樣,Controller 層、組合服務(wù)層、基礎(chǔ)服務(wù)層就會相互調(diào)用,這個(gè)調(diào)用是在數(shù)據(jù)中心內(nèi)部的,量也會比較大,還是使用 RPC 的機(jī)制實(shí)現(xiàn)的。
????由于服務(wù)比較多,需要一個(gè)多帶帶的注冊中心來做服務(wù)發(fā)現(xiàn)。服務(wù)提供方會將自己提供哪些服務(wù)注冊到注冊中心中去,同時(shí)服務(wù)消費(fèi)方訂閱這個(gè)服務(wù),從而可以對這個(gè)服務(wù)進(jìn)行調(diào)用。
????調(diào)用的時(shí)候有一個(gè)問題,這里的 RPC 調(diào)用,應(yīng)該用二進(jìn)制還是文本類?其實(shí)文本的最大問題是,占用字節(jié)數(shù)目比較多。比如數(shù)字 123,其實(shí)本來二進(jìn)制 8 位就夠了,但是如果變成文本,就成了字符串 123。如果是 UTF-8 編碼的話,就是三個(gè)字節(jié);如果是 UTF-16,就是六個(gè)字節(jié)。同樣的信息,要多費(fèi)好多的空間,傳輸起來也更加占帶寬,時(shí)延也高。
????因而對于數(shù)據(jù)中心內(nèi)部的相互調(diào)用,很多公司選型的時(shí)候,還是希望采用更加省空間和帶寬的二進(jìn)制的方案。
????這里一個(gè)著名的例子就是 Dubbo 服務(wù)化框架二進(jìn)制的 RPC 方式。
????Dubbo 會在客戶端的本地啟動(dòng)一個(gè) Proxy,其實(shí)就是客戶端的 Stub,對于遠(yuǎn)程的調(diào)用都通過這個(gè) Stub 進(jìn)行封裝。
????接下來,Dubbo 會從注冊中心獲取服務(wù)端的列表,根據(jù)路由規(guī)則和負(fù)載均衡規(guī)則,在多個(gè)服務(wù)端中選擇一個(gè)最合適的服務(wù)端進(jìn)行調(diào)用。
????調(diào)用服務(wù)端的時(shí)候,首先要進(jìn)行編碼和序列化,形成 Dubbo 頭和序列化的方法和參數(shù)。將編碼好的數(shù)據(jù),交給網(wǎng)絡(luò)客戶端進(jìn)行發(fā)送,網(wǎng)絡(luò)服務(wù)端收到消息后,進(jìn)行解碼。然后將任務(wù)分發(fā)給某個(gè)線程進(jìn)行處理,在線程中會調(diào)用服務(wù)端的代碼邏輯,然后返回結(jié)果。
????這個(gè)過程和經(jīng)典的 RPC 模式何其相似啊!
如何解決協(xié)議約定問題?????接下來我們還是來看 RPC 的三大問題,其中注冊發(fā)現(xiàn)問題已經(jīng)通過注冊中心解決了。我們下面就來看協(xié)議約定問題。
????Dubbo 中默認(rèn)的 RPC 協(xié)議是 Hessian2。為了保證傳輸?shù)男剩琀essian2 將遠(yuǎn)程調(diào)用序列化為二進(jìn)制進(jìn)行傳輸,并且可以進(jìn)行一定的壓縮。這個(gè)時(shí)候你可能會疑惑,同為二進(jìn)制的序列化協(xié)議,Hessian2 和前面的二進(jìn)制的 RPC 有什么區(qū)別呢?這不繞了一圈又回來了嗎?
????Hessian2 是解決了一些問題的。例如,原來要定義一個(gè)協(xié)議文件,然后通過這個(gè)文件生成客戶端和服務(wù)端的 Stub,才能進(jìn)行相互調(diào)用,這樣使得修改就會不方便。Hessian2 不需要定義這個(gè)協(xié)議文件,而是自描述的。什么是自描述呢?
????所謂自描述就是,關(guān)于調(diào)用哪個(gè)函數(shù),參數(shù)是什么,另一方不需要拿到某個(gè)協(xié)議文件、拿到二進(jìn)制,靠它本身根據(jù) Hessian2 的規(guī)則,就能解析出來。
????原來有協(xié)議文件的場景,有點(diǎn)兒像兩個(gè)人事先約定好,0 表示方法 add,然后后面會傳兩個(gè)數(shù)。服務(wù)端把兩個(gè)數(shù)加起來,這樣一方發(fā)送 012,另一方知道是將 1 和 2 加起來,但是不知道協(xié)議文件的,當(dāng)它收到 012 的時(shí)候,完全不知道代表什么意思。
????而自描述的場景,就像兩個(gè)人說的每句話都帶前因后果。例如,傳遞的是“函數(shù):add,第一個(gè)參數(shù) 1,第二個(gè)參數(shù) 2”。這樣無論誰拿到這個(gè)表述,都知道是什么意思。但是只不過都是以二進(jìn)制的形式編碼的。這其實(shí)相當(dāng)于綜合了 XML 和二進(jìn)制共同優(yōu)勢的一個(gè)協(xié)議。
????Hessian2 是如何做到這一點(diǎn)的呢?這就需要去看 Hessian2 的序列化的語法描述文件。
????看起來很復(fù)雜,編譯原理里面是有這樣的語法規(guī)則的。
????我們從 Top 看起,下一層是 value,直到形成一棵樹。這里面的有個(gè)思想,為了防止歧義,每一個(gè)類型的起始數(shù)字都設(shè)置成為獨(dú)一無二的。這樣,解析的時(shí)候,看到這個(gè)數(shù)字,就知道后面跟的是什么了。
????這里還是以加法為例子,“add(2,3)”被序列化之后是什么樣的呢?
H x02 x00 # Hessian 2.0 C # RPC call x03 add # method "add" x92 # two arguments x92 # 2 - argument 1 x93 # 3 - argument 2
H 開頭,表示使用的協(xié)議是 Hession,H 的二進(jìn)制是 0x48
C 開頭,表示這是一個(gè) RPC 調(diào)用
0x03,表示方法名是三個(gè)字符
0x92,表示有兩個(gè)參數(shù)。其實(shí)這里存的應(yīng)該是 2,之所以加上 0x90,就是為了防止歧義,表示這里一定是一個(gè) int
第一個(gè)參數(shù)是 2,編碼為 0x92,第二個(gè)參數(shù)是 3,編碼為 0x93
????這個(gè)就叫作自描述。
????另外,Hessian2 是面向?qū)ο蟮模梢詡鬏斠粋€(gè)對象。
class Car { String color; String model; } out.writeObject(new Car("red", "corvette")); out.writeObject(new Car("green", "civic")); --- C # object definition (#0) x0b example.Car # type is example.Car x92 # two fields x05 color # color field name x05 model # model field name O # object def (long form) x90 # object definition #0 x03 red # color field value x08 corvette # model field value x60 # object def #0 (short form) x05 green # color field value x05 civic # model field value
????首先,定義這個(gè)類。對于類型的定義也傳過去,因而也是自描述的。類名為 example.Car,字符長 11 位,因而前面長度為 0x0b。有兩個(gè)成員變量,一個(gè)是 color,一個(gè)是 model,字符長 5 位,因而前面長度 0x05,。
????然后,傳輸?shù)膶ο笠眠@個(gè)類。由于類定義在位置 0,因而對象會指向這個(gè)位置 0,編碼為 0x90。后面 red 和 corvette 是兩個(gè)成員變量的值,字符長分別為 3 和 8。
????接著又傳輸一個(gè)屬于相同類的對象。這時(shí)候就不保存對于類的引用了,只保存一個(gè) 0x60,表示同上就可以了。
????可以看出,Hessian2 真的是能壓縮盡量壓縮,多一個(gè) Byte 都不傳。
如何解決 RPC 傳輸問題?????接下來,我們再來看 Dubbo 的 RPC 傳輸問題。前面我們也說了,基于 Socket 實(shí)現(xiàn)一個(gè)高性能的服務(wù)端,是很復(fù)雜的一件事情,在 Dubbo 里面,使用了 Netty 的網(wǎng)絡(luò)傳輸框架。
????Netty 是一個(gè)非阻塞的基于事件的網(wǎng)絡(luò)傳輸框架,在服務(wù)端啟動(dòng)的時(shí)候,會監(jiān)聽一個(gè)端口,并注冊以下的事件。
連接事件:當(dāng)收到客戶端的連接事件時(shí),會調(diào)用 void connected(Channel channel) 方法
當(dāng)可寫事件觸發(fā)時(shí),會調(diào)用 void sent(Channel channel, Object message),服務(wù)端向客戶端返回響應(yīng)數(shù)據(jù)
當(dāng)可讀事件觸發(fā)時(shí),會調(diào)用 void received(Channel channel, Object message) ,服務(wù)端在收到客戶端的請求數(shù)據(jù)
當(dāng)發(fā)生異常時(shí),會調(diào)用 void caught(Channel channel, Throwable exception)
????當(dāng)事件觸發(fā)之后,服務(wù)端在這些函數(shù)中的邏輯,可以選擇直接在這個(gè)函數(shù)里面進(jìn)行操作,還是將請求分發(fā)到線程池去處理。一般異步的數(shù)據(jù)讀寫都需要另外的線程池參與,在線程池中會調(diào)用真正的服務(wù)端業(yè)務(wù)代碼邏輯,返回結(jié)果。
????Hessian2 是 Dubbo 默認(rèn)的 RPC 序列化方式,當(dāng)然還有其他選擇。例如,Dubbox 從 Spark 那里借鑒 Kryo,實(shí)現(xiàn)高性能的序列化。
????到這里,我們說了數(shù)據(jù)中心里面的相互調(diào)用。為了高性能,大家都愿意用二進(jìn)制,但是為什么后期 Spring Cloud 又興起了呢?這是因?yàn)椋l(fā)量越來越大,已經(jīng)到了微服務(wù)的階段。同原來的 SOA 不同,微服務(wù)粒度更細(xì),模塊之間的關(guān)系更加復(fù)雜。
????在上面的架構(gòu)中,如果使用二進(jìn)制的方式進(jìn)行序列化,雖然不用協(xié)議文件來生成 Stub,但是對于接口的定義,以及傳的對象 DTO,還是需要共享 JAR。因?yàn)橹挥锌蛻舳撕头?wù)端都有這個(gè) JAR,才能成功地序列化和反序列化。
????但當(dāng)關(guān)系復(fù)雜的時(shí)候,JAR 的依賴也變得異常復(fù)雜,難以維護(hù),而且如果在 DTO 里加一個(gè)字段,雙方的 JAR 沒有匹配好,也會導(dǎo)致序列化不成功,而且還有可能循環(huán)依賴。這個(gè)時(shí)候,一般有兩種選擇。
第一種,建立嚴(yán)格的項(xiàng)目管理流程。
不允許循環(huán)調(diào)用,不允許跨層調(diào)用,只準(zhǔn)上層調(diào)用下層,不允許下層調(diào)用上層
接口要保持兼容性,不兼容的接口新添加而非改原來的,當(dāng)接口通過監(jiān)控,發(fā)現(xiàn)不用的時(shí)候,再下掉
升級的時(shí)候,先升級服務(wù)提供端,再升級服務(wù)消費(fèi)端。
第二種,改用 RESTful 的方式。
使用 Spring Cloud,消費(fèi)端和提供端不用共享 JAR,各聲明各的,只要能變成 JSON 就行,而且 JSON 也是比較靈活的
使用 RESTful 的方式,性能會降低,所以需要通過橫向擴(kuò)展來抵消單機(jī)的性能損耗
小結(jié)RESTful API 對于接入層和 Controller 層之外的調(diào)用,已基本形成事實(shí)標(biāo)準(zhǔn),但是隨著內(nèi)部服務(wù)之間的調(diào)用越來越多,性能也越來越重要,于是 Dubbo 的 RPC 框架有了用武之地
Dubbo 通過注冊中心解決服務(wù)發(fā)現(xiàn)問題,通過 Hessian2 序列化解決協(xié)議約定的問題,通過 Netty 解決網(wǎng)絡(luò)傳輸?shù)膯栴}
在更加復(fù)雜的微服務(wù)場景下,Spring Cloud 的 RESTful 方式在內(nèi)部調(diào)用也會被考慮,主要是 JAR 包的依賴和管理問題
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/29968.html
摘要:但是只不過都是以二進(jìn)制的形式編碼的。這其實(shí)相當(dāng)于綜合了和二進(jìn)制共同優(yōu)勢的一個(gè)協(xié)議。在上面的架構(gòu)中,如果使用二進(jìn)制的方式進(jìn)行序列化,雖然不用協(xié)議文件來生成,但是對于接口的定義,以及傳的對象,還是需要共享。 ????前面我們認(rèn)識了兩個(gè)常用文本類的 RPC 協(xié)議,對于陌生人之間的溝通,用 NBA、CBA 這樣的縮略語,會使得協(xié)議約定非常不方便。 ????在講 CDN 和 DNS 的時(shí)候,我們...
摘要:微軟的雖然引入了事件機(jī)制,可以在隊(duì)列收到消息時(shí)觸發(fā)事件,通知訂閱者。由微軟作為主要貢獻(xiàn)者的,則對以及做了進(jìn)一層包裝,并能夠很好地實(shí)現(xiàn)這一模式。 在分布式服務(wù)框架中,一個(gè)最基礎(chǔ)的問題就是遠(yuǎn)程服務(wù)是怎么通訊的,在Java領(lǐng)域中有很多可實(shí)現(xiàn)遠(yuǎn)程通訊的技術(shù),例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS等,這些名詞之間到底是些什么關(guān)系呢,它們背后到底是基...
摘要:微軟的雖然引入了事件機(jī)制,可以在隊(duì)列收到消息時(shí)觸發(fā)事件,通知訂閱者。由微軟作為主要貢獻(xiàn)者的,則對以及做了進(jìn)一層包裝,并能夠很好地實(shí)現(xiàn)這一模式。 在分布式服務(wù)框架中,一個(gè)最基礎(chǔ)的問題就是遠(yuǎn)程服務(wù)是怎么通訊的,在Java領(lǐng)域中有很多可實(shí)現(xiàn)遠(yuǎn)程通訊的技術(shù),例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS等,這些名詞之間到底是些什么關(guān)系呢,它們背后到底是基...
摘要:對于與而言,則可以看做是消息傳遞技術(shù)的一種衍生或封裝。在生產(chǎn)者通知消費(fèi)者時(shí),傳遞的往往是消息或事件,而非生產(chǎn)者自身。通過消息路由,我們可以配置路由規(guī)則指定消息傳遞的路徑,以及指定具體的消費(fèi)者消費(fèi)對應(yīng)的生產(chǎn)者。采用和來進(jìn)行遠(yuǎn)程對象的通訊。 消息模式 歸根結(jié)底,企業(yè)應(yīng)用系統(tǒng)就是對數(shù)據(jù)的處理,而對于一個(gè)擁有多個(gè)子系統(tǒng)的企業(yè)應(yīng)用系統(tǒng)而言,它的基礎(chǔ)支撐無疑就是對消息的處理。與對象不同,消息本質(zhì)上...
閱讀 4744·2021-10-13 09:39
閱讀 1956·2019-08-29 11:12
閱讀 1150·2019-08-28 18:16
閱讀 1863·2019-08-26 12:16
閱讀 1249·2019-08-26 12:13
閱讀 2996·2019-08-26 10:59
閱讀 2302·2019-08-23 18:27
閱讀 2996·2019-08-23 18:02