本文目錄一覽:
- 1、小白也能看懂的dubbo3應用級服務發現詳解
- 2、dubbo協議層 jsonrpc協議遷移到http協議
- 3、Dubbo的底層實現原理和機制
- 4、Dubbo——HTTP 協議 + JSON-RPC
- 5、微服務跨語言調用(摘選)
小白也能看懂的dubbo3應用級服務發現詳解
dubbo 是一款開源的 RPC 框架,主要有3個角色: 提供者(provider) 、 消費者(consumer) 、 註冊中心(registry)
提供者啟動時向註冊中心註冊服務地址,消費者啟動時訂閱服務,並通過獲取到的提供者地址發起調用,當提供者地址變更時,通過註冊中心向消費者推送變更。這就是 dubbo 主要的工作流程。
在2.7.5之前,dubbo 只支持接口級服務發現模型,=2.7.5的版本提供了接口級與應用級兩種服務發現模型,3.0之後的版本應用級服務發現更是非常重要的一個功能。
本文將從為什麼需要引入應用級服務發現,dubbo 實現應用級服務發現的難點以及dubbo3 是如何解決這些問題這三個部分進行講解。
開始前,我們先了解下 dubbo 最初提供的接口級服務發現是怎樣的。
dubbo 服務的註冊發現是以 接口 為最小粒度的,在 dubbo 中將其抽象為一個 URL ,大概長這樣:
看着很亂?捋一捋:
無論是存儲還是變更推送壓力都可能遇到瓶頸,數據多表現在這兩個方面:
這個問題好解決: 拆!
dubbo 在 2.7 之後的版本支持了 元數據中心 與 配置中心 ,對於URL的參數進行分類存儲。持久不變的(如application、method等)參數存儲到元數據中心中,可能在運行時變化(timeout、tag)的存儲到配置中心中
無論是增加一台機器還是增加一個接口,其增長都是線性的,這個問題比單條數據大更嚴重。
當抹去註冊信息中的 interface 信息,這樣數據量就大大減少
只用過 dubbo 的同學可能覺得這很主流。
但從服務發現的角度來看:
無論是用的最多的服務註冊發現系統 DNS ,又或者是 SpringCloud 體系、 K8S 體系,都是以應用為維度進行服務註冊發現的,只有和這些體系對齊,才能更好地與之進行打通。
在我了解的範圍里,目前只有 dubbo 、 SOFARPC 、 HSF 三個阿里系的 RPC 框架支持了接口級的服務發現。
provider端暴露服務:
consumer端引用服務:
本地調用遠程的方法時,只需要配置一個 reference ,然後直接使用 interface 來調用,我們不必去實現這個 interaface,dubbo 自動幫我們生成了一個代理進行 RPC 調用,屏蔽了通信的細節,讓我們有種 像調用本地方法一樣調用遠程方法的感覺 ,這也是 dubbo 的優勢。
從這裡我們能看出為什麼 dubbo 要設計成接口級服務發現,因為要為每一個 interface 生成一個代理,就必須定位到該 interface 對應服務暴露的服務地址,為了方便,dubbo 就這麼設計了。
如果讓我來設計應用級服務發現,註冊不必多說,按應用名註冊即可。
至於訂閱,在目前 dubbo 機制下,必須得告訴消費者消費的每個接口是屬於哪個應用,這樣才能定位到接口部署在哪裡。
實現 dubbo 應用級服務發現,難點在於
保留接口級服務發現,且默認採取雙註冊方式,可配置使用哪種服務發現模型,如下配置使用應用級服務發現
名詞有點高大上,但道理很簡單,讓 dubbo 自己去匹配,提供者註冊的時候把接口和應用名的映射關係存儲起來,消費者消費時根據接口名獲取到部署的應用名,再去做服務發現。
數據存儲在哪裡?顯然元數據中心非常合適。該方案用戶使用起來和之前接口級沒有任何不同,但需要增加一個元數據中心,架構變得複雜。
且有一個問題是,如果接口在多個應用下部署了,dubbo 查找的策略是都去訂閱,這可能在某些場景下不太合適。
本文從接口級服務發現講到應用級服務發現,包含了為什麼 dubbo 設計成接口級服務發現,接口級服務發現有什麼痛點?基於 dubbo 現狀如何設計應用級服務發現,應用級服務發現實現有什麼難點等等問題進行解答,相信看完的小夥伴一定有所收穫。
dubbo協議層 jsonrpc協議遷移到http協議
周末看到社區的協議遷移開始被提交了pr,還沒merge,打算拜讀一下
看到 HttpRemoteInvocation 被更改了,這裡是要把 json-rpc 協議轉換為 http 協議
HTTP 請求本身也可以看做是 RPC 的一種具體形式。HTTP 請求也一樣是可以從本地發一個信號到服務器,服務器上執行某個函數,然後返回一些信息給客戶端。
經常用的一些數據通過HTTP協議來傳輸,thrift,grpc,xml-rpc,json-rpc都是通過HTTP傳輸的。也就說json-rpc是依賴http協議傳輸的,所以新建一個公共的代理協議,凡是用http協議傳輸的數據格式都設置這個協議,是比較好的方案。
根據dubbo的類機構繼承圖,是沿用SPI機制實現的 AbstractProxyProtocol
這裡比較重要的是 protocolBindingRefer 方法。,構建一個DubboInvoker對象,根據dubbo的動態代理不斷找到調用方的目標對象,添加到invoke當中,但這裡沒有指定要傳輸的格式,所以在dubbo序列化的時候還是會走默認的hessian協議,需要對協議中傳入的格式進行校驗
基本思路:將參數中的service(傳輸格式傳入)設置到dubbo的代理,拿到代理之後添加到invoke的責任鏈當中,返回一個dubbo的代理對象給調用方
Dubbo的底層實現原理和機制
Dubbo :是一個rpc框架,soa框架
作為RPC:支持各種傳輸協議,如dubbo,hession,json,fastjson,底層採用mina,netty長連接進行傳輸!典型的provider和cusomer模式!
作為SOA:具有服務治理功能,提供服務的註冊和發現!用zookeeper實現註冊中心!啟動時候服務端會把所有接口註冊到註冊中心,並且訂閱configurators,服務消費端訂閱provide,configurators,routers,訂閱變更時,zk會推送providers,configuators,routers,啟動時註冊長連接,進行通訊!proveider和provider啟動後,後台啟動定時器,發送統計數據到monitor!提供各種容錯機制和負載均衡策略!!
描述一個服務從發佈到被消費的詳細過程:
一個服務的發佈暴露過程:
首先設置一個項目的別名,然後是定義註冊中心和設定傳輸協議,之後定義服務名!服務接口以jar形式導入到provider!
一個服務發佈暴露首先由spring的spacehander 把相關的xml或者註解全部轉化為springBean,之後通過ServiceConfig.exerp()方法把bean傳化為傳輸所需的url和參數註冊到註冊中心,發佈後provder端的ref(helloImpl)通過protocl(傳輸協議,如dubboprotocl,hessionprotocl)轉化為Invoker對象,即調用信息,包括類,方法,參數等等,再通過proxy操作(代理)如jdkproxy代理轉為為Exporter對象,這就是整個的服務暴露過程!
消費過程:
一個Renfence類,通過RenfenceConfig的init 調用proxy的refer方法生產一個invoker,invoker再通過proctol轉化成具體的ref(hello),進行消費
首先 ReferenceConfig 類的 init 方法調用 Protocol 的 refer 方法生成 Invoker 實例(如上圖中的紅色部分),這是服務消費的關鍵。接下來把 Invoker 轉換為客戶端需要的接口(如:HelloWorld)
具體參見
Exporter接口提供Invoker的調用和destroy()
public interface ExporterT {
}
#Dubbo的實現
Dubbo協議的Invoker轉為Exporter發生在DubboProtocol類的export方法,它主要是打開socket偵聽服務,並接收客戶端發來的各種請求,通訊細節由Dubbo自己實現。
Dubbo——HTTP 協議 + JSON-RPC
Protocol 還有一個實現分支是 AbstractProxyProtocol,如下圖所示:
從圖中我們可以看到:gRPC、HTTP、WebService、Hessian、Thrift 等協議對應的 Protocol 實現,都是繼承自 AbstractProxyProtocol 抽象類。
目前互聯網的技術棧百花齊放,很多公司會使用 Node.js、Python、Rails、Go 等語言來開發 一些 Web 端應用,同時又有很多服務會使用 Java 技術棧實現,這就出現了大量的跨語言調用的需求。Dubbo 作為一個 RPC 框架,自然也希望能實現這種跨語言的調用,目前 Dubbo 中使用「HTTP 協議 + JSON-RPC」的方式來達到這一目的,其中 HTTP 協議和 JSON 都是天然跨語言的標準,在各種語言中都有成熟的類庫。
下面就重點來分析 Dubbo 對 HTTP 協議的支持。首先,會介紹 JSON-RPC 的基礎,並通過一個示例,快速入門,然後介紹 Dubbo 中 HttpProtocol 的具體實現,也就是如何將 HTTP 協議與 JSON-RPC 結合使用,實現跨語言調用的效果。
Dubbo 中支持的 HTTP 協議實際上使用的是 JSON-RPC 協議。
JSON-RPC 是基於 JSON 的跨語言遠程調用協議。Dubbo 中的 dubbo-rpc-xml、dubbo-rpc-webservice 等模塊支持的 XML-RPC、WebService 等協議與 JSON-RPC 一樣,都是基於文本的協議,只不過 JSON 的格式比 XML、WebService 等格式更加簡潔、緊湊。與 Dubbo 協議、Hessian 協議等二進制協議相比,JSON-RPC 更便於調試和實現,可見 JSON-RPC 協議還是一款非常優秀的遠程調用協議。
在 Java 體系中,有很多成熟的 JSON-RPC 框架,例如 jsonrpc4j、jpoxy 等,其中,jsonrpc4j 本身體積小巧,使用方便,既可以獨立使用,也可以與 Spring 無縫集合,非常適合基於 Spring 的項目。
下面先來看看 JSON-RPC 協議中請求的基本格式:
JSON-RPC請求中各個字段的含義如下:
在 JSON-RPC 的服務端收到調用請求之後,會查找到相應的方法並進行調用,然後將方法的返回值整理成如下格式,返回給客戶端:
JSON-RPC響應中各個字段的含義如下:
Dubbo 使用 jsonrpc4j 庫來實現 JSON-RPC 協議,下面使用 jsonrpc4j 編寫一個簡單的 JSON-RPC 服務端示例程序和客戶端示例程序,並通過這兩個示例程序說明 jsonrpc4j 最基本的使用方式。
首先,需要創建服務端和客戶端都需要的 domain 類以及服務接口。先來創建一個 User 類,作為最基礎的數據對象:
接下來創建一個 UserService 接口作為服務接口,其中定義了 5 個方法,分別用來創建 User、查詢 User 以及相關信息、刪除 User:
UserServiceImpl 是 UserService 接口的實現類,其中使用一個 ArrayList 集合管理 User 對象,具體實現如下:
整個用戶管理業務的核心大致如此。下面我們來看服務端如何將 UserService 與 JSON-RPC 關聯起來。
首先,創建 RpcServlet 類,它是 HttpServlet 的子類,並覆蓋了 HttpServlet 的 service() 方法。我們知道,HttpServlet 在收到 GET 和 POST 請求的時候,最終會調用其 service() 方法進行處理;HttpServlet 還會將 HTTP 請求和響應封裝成 HttpServletRequest 和 HttpServletResponse 傳入 service() 方法之中。這裡的 RpcServlet 實現之中會創建一個 JsonRpcServer,並在 service() 方法中將 HTTP 請求委託給 JsonRpcServer 進行處理:
最後,創建一個 JsonRpcServer 作為服務端的入口類,在其 main() 方法中會啟動 Jetty 作為 Web 容器,具體實現如下:
這裡使用到的 web.xml 配置文件如下:
完成服務端的編寫之後,下面再繼續編寫 JSON-RPC 的客戶端。在 JsonRpcClient 中會創建 JsonRpcHttpClient,並通過 JsonRpcHttpClient 請求服務端:
在 AbstractProxyProtocol 的 export() 方法中,首先會根據 URL 檢查 exporterMap 緩存,如果查詢失敗,則會調用 ProxyFactory.getProxy() 方法將 Invoker 封裝成業務接口的代理類,然後通過子類實現的 doExport() 方法啟動底層的 ProxyProtocolServer,並初始化 serverMap 集合。具體實現如下:
在 HttpProtocol 的 doExport() 方法中,與前面介紹的 DubboProtocol 的實現類似,也要啟動一個 RemotingServer。為了適配各種 HTTP 服務器,例如,Tomcat、Jetty 等,Dubbo 在 Transporter 層抽象出了一個 HttpServer 的接口。
dubbo-remoting-http 模塊的入口是 HttpBinder 接口,它被 @SPI 註解修飾,是一個擴展接口,有三個擴展實現,默認使用的是 JettyHttpBinder 實現,如下圖所示:
HttpBinder 接口中的 bind() 方法被 @Adaptive 註解修飾,會根據 URL 的 server 參數選擇相應的 HttpBinder 擴展實現,不同 HttpBinder 實現返回相應的 HttpServer 實現。HttpServer 的繼承關係如下圖所示:
這裡以 JettyHttpServer 為例簡單介紹 HttpServer 的實現,在 JettyHttpServer 中會初始化 Jetty Server,其中會配置 Jetty Server 使用到的線程池以及處理請求 Handler:
可以看到 JettyHttpServer 收到的全部請求將委託給 DispatcherServlet 這個 HttpServlet 實現,而 DispatcherServlet 的 service() 方法會把請求委託給對應接端口的 HttpHandler 處理:
了解了 Dubbo 對 HttpServer 的抽象以及 JettyHttpServer 的核心之後,回到 HttpProtocol 中的 doExport() 方法繼續分析。
在 HttpProtocol.doExport() 方法中會通過 HttpBinder 創建前面介紹的 HttpServer 對象,並記錄到 serverMap 中用來接收 HTTP 請求。這裡初始化 HttpServer 以及處理請求用到的 HttpHandler 是 HttpProtocol 中的內部類,在其他使用 HTTP 協議作為基礎的 RPC 協議實現中也有類似的 HttpHandler 實現類,如下圖所示:
在 HttpProtocol.InternalHandler 中的 handle() 實現中,會將請求委託給 skeletonMap 集合中記錄的 JsonRpcServer 對象進行處理:
skeletonMap 集合中的 JsonRpcServer 是與 HttpServer 對象一同在 doExport() 方法中初始化的。最後,我們來看 HttpProtocol.doExport() 方法的實現:
介紹完 HttpProtocol 暴露服務的相關實現之後,下面再來看 HttpProtocol 中引用服務相關的方法實現,即 protocolBindinRefer() 方法實現。該方法首先通過 doRefer() 方法創建業務接口的代理,這裡會使用到 jsonrpc4j 庫中的 JsonProxyFactoryBean 與 Spring 進行集成,在其 afterPropertiesSet() 方法中會創建 JsonRpcHttpClient 對象:
下面來看 doRefer() 方法的具體實現:
在 AbstractProxyProtocol.protocolBindingRefer() 方法中,會通過 ProxyFactory.getInvoker() 方法將 doRefer() 方法返回的代理對象轉換成 Invoker 對象,並記錄到 Invokers 集合中,具體實現如下:
本文重點介紹了在 Dubbo 中如何通過「HTTP 協議 + JSON-RPC」的方案實現跨語言調用。首先介紹了 JSON-RPC 中請求和響應的基本格式,以及其實現庫 jsonrpc4j 的基本使用;接下來我們還詳細介紹了 Dubbo 中 AbstractProxyProtocol、HttpProtocol 等核心類,剖析了 Dubbo 中「HTTP 協議 + JSON-RPC」方案的落地實現。
微服務跨語言調用(摘選)
微服務架構已成為目前互聯網架構的趨勢,關於微服務的討論,幾乎佔據了各種技術大會的絕大多數版面。國內使用最多的服務治理框架非阿里開源的 dubbo 莫屬,千米網也選擇了 dubbo 作為微服務治理框架。另一方面,和大多數互聯網公司一樣,千米的開發語言是多樣的,大多數後端業務由 java 支撐,而每個業務線有各自開發語言的選擇權,便出現了 nodejs,python,go 多語言調用的問題。
跨語言調用是一個很大的話題,也是一個很有挑戰的技術活,目前業界經常被提及的解決方案有如下幾種,不妨拿出來老生常談一番:
當我們再聊跨語言調用時我們在聊什麼?縱觀上述幾個較為通用,成熟的解決方案,可以得出結論:解決跨語言調用的思路無非是兩種:
如果一個新型的團隊面臨技術選型,我認為上述的方案都可以納入參考,可考慮到遺留系統的兼容性問題
舊系統的遷移成本
這也關鍵的選型因素。我們做出的第一個嘗試,便是在 RPC 協議上下功夫。
通用協議的跨語言支持
springmvc的美好時代
springmvc
springmvc
在沒有實現真正的跨語言調用之前,想要實現「跨語言」大多數方案是使用 http 協議做一層轉換,最常見的手段莫過於藉助 springmvc 提供的 controller/restController,間接調用 dubbo provider。這種方案的優勢和劣勢顯而易見
通用協議的支持
事實上,大多數服務治理框架都支持多種協議,dubbo 框架除默認的 dubbo 協議之外,還有噹噹網擴展的 rest協議和千米網擴展的 json-rpc 協議可供選擇。這兩者都是通用的跨語言協議。
rest 協議為滿足 JAX-RS 2.0 標準規範,在開發過程中引入了 @Path,@POST,@GET 等註解,習慣於編寫傳統 rpc 接口的人可能不太習慣 rest 風格的 rpc 接口。一方面這樣會影響開發體驗,另一方面,獨樹一幟的接口風格使得它與其他協議不太兼容,舊接口的共生和遷移都無法實現。如果沒有遺留系統,rest 協議無疑是跨語言方案最簡易的實現,絕大多數語言支持 rest 協議。
和 rest 協議類似,json-rpc 的實現也是文本序列化http 協議。dubbox 在 restful 接口上已經做出了嘗試,但是 rest 架構和 dubbo 原有的 rpc 架構是有區別的,rest 架構需要對資源(Resources)進行定義, 需要用到 http 協議的基本操作 GET、POST、PUT、DELETE。在我們看來,restful 更合適互聯網系統之間的調用,而 rpc 更適合一個系統內的調用。使用 json-rpc 協議使得舊接口得以兼顧,開發習慣仍舊保留,同時獲得了跨語言的能力。
千米網在早期實踐中採用了 json-rpc 作為 dubbo 的跨語言協議實現,並開源了基於 json-rpc 協議下的 python 客戶端 dubbo-client-py 和 node 客戶端 dubbo-node-client,使用 python 和 nodejs 的小夥伴可以藉助於它們直接調用 dubbo-provider-java 提供的 rpc 服務。系統中大多數 java 服務之間的互相調用還是以 dubbo 協議為主,考慮到新舊協議的適配,在不影響原有服務的基礎上,我們配置了雙協議。
dubbo 協議主要支持 java 間的相互調用,適配老接口;json-rpc 協議主要支持異構語言的調用。
定製協議的跨語言支持
微服務框架所謂的協議(protocol)可以簡單理解為:報文格式和序列化方案。服務治理框架一般都提供了眾多的協議配置項供使用者選擇,除去上述兩種通用協議,還存在一些定製化的協議,如 dubbo 框架的默認協議:dubbo 協議以及 motan 框架提供的跨語言協議:motan2。
motan2協議的跨語言支持
motan2
motan2
motan2 協議被設計用來滿足跨語言的需求主要體現在兩個細節中—MetaData 和 motan-go。在最初的 motan 協議中,協議報文僅由 Header+Body 組成,這樣導致 path,param,group 等存儲在 Body 中的數據需要反序列得到,這對異構語言來說是很不友好的,所以在 motan2 中修改了協議的組成;weibo 開源了 motan-go ,motan-php ,motan-openresty ,並藉助於 motan-go 充當了 agent 這一翻譯官的角色,使用 simple 序列化方案來序列化協議報文的 Body 部分(simple 序列化是一種較弱的序列化方案)。
agent
agent
仔細揣摩下可以發現這麼做和雙協議的配置區別並不是大,只不過這裡的 agent 是隱式存在的,與主服務共生。明顯的區別在於 agent 方案中異構語言並不直接交互。
dubbo協議的跨語言支持
dubbo 協議設計之初只考慮到了常規的 rpc 調用場景,它並不是為跨語言而設計,但跨語言支持從來不是只有支持、不支持兩種選擇,而是要按難易程度來劃分。是的,dubbo 協議的跨語言調用可能並不好做,但並非無法實現。千米網便實現了這一點,nodejs 構建的前端業務是異構語言的主戰場,最終實現了 dubbo2.js,打通了 nodejs 和原生 dubbo 協議。作為本文第二部分的核心內容,重點介紹下我們使用 dubbo2.js 幹了什麼事。
Dubbo協議報文格式
dubbo協議
dubbo協議
dubbo協議報文消息頭詳解:
magic:類似java位元組碼文件里的魔數,用來判斷是不是 dubbo 協議的數據包。魔數是常量 0xdabb
flag:標誌位, 一共8個地址位。低四位用來表示消息體數據用的序列化工具的類型(默認 hessian),高四位中,第一位為 1 表示是 request 請求,第二位為 1 表示雙向傳輸(即有返回 response),第三位為 1 表示是心跳 ping 事件。
status:狀態位, 設置請求響應狀態,dubbo 定義了一些響應的類型。具體類型見com.alibaba.dubbo.remoting.exchange.Response
invoke id:消息 id, long 類型。每一個請求的唯一識別 id(由於採用異步通訊的方式,用來把請求 request 和返回的 response 對應上)
body length:消息體 body 長度, int 類型,即記錄 Body Content 有多少個位元組
body content:請求參數,響應參數的抽象序列化之後存儲於此。
協議報文最終都會變成位元組,使用 tcp 傳輸,任何語言只要支持網絡模塊,有類似 Socket 之類的封裝,那麼通信就不成問題。那,跨語言難在哪兒?以其他語言調用 java 來說,主要有兩個難點:
ps:dubbo 協議通訊demo( )
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/198665.html