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