1. 背景介紹
我們公司是杭州的一家電商公司,公司內的技術體系較多,主要語言有了JAVA/PHP/Node,其中在19年的時候,公司制定了去PHP化的計劃,將後端邏輯沉澱到Java服務化當中,而部分服務化呼叫相關業務則需要Node扛起,而與Java進行通訊則需要經過Dubbo,由此我們以Consumer的角色來探索與研究如何用Node呼叫Dubbo.
2.Dubbo簡介
2.1 什麼是dubbo
Dubbo是一款高效能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向介面的遠端方法呼叫,智慧容錯和負載均衡,以及服務自動註冊和發現。
2.2 流程圖
- Provider : 暴露服務的服務提供方。
- Consumer : 呼叫遠端服務的服務消費方。
- Registry: 服務註冊與發現的註冊中心。
- Monitor: 統計服務的呼叫次調和呼叫時間的監控中心。
- Container: 服務執行容器。
3. 具體實現
3.1 協議選擇
官方雖然支援了很多種協議,但是真正適合於我們的協議並不是很多,譬如rmi協議主要是面向Java工程之間的呼叫,並不適合用於異構語言RPC場景,所以我們接下來只針對部分協議進行分析,並結合我們的實際的業務場景,最後來篩選出適合我們的協議.
連線個數 | 連線方式 | 傳輸協議 | 傳輸方式 | 序列化 | 適用範圍 | 適用場景 | |
---|---|---|---|---|---|---|---|
dubbo | 單連線 | 長連線 | TCP | NIO 非同步傳輸 | Hessian 二進位制序列化 | 傳入傳出引數資料包較小,消費者比提供者個數多,單一消費者無法壓滿提供者 | 常規遠端服務方法呼叫 |
rmi | 多連線 | 短連線 | TCP | 同步傳輸 | Java 標準二進位制序列化 | 傳入傳出引數資料包大小混合,消費者與提供者個數差不多,可傳檔案。 | 常規遠端服務方法呼叫,與原生RMI服務互操作 |
hessian | 多連線 | 短連線 | HTTP | 同步傳輸 | Hessian二進位制序列化 | 傳入傳出引數資料包較大,提供者比消費者個數多,提供者壓力較大,可傳檔案。 頁面傳輸,檔案傳輸,或與原生hessian服務互操作 | |
http | 多連線 | 短連線 | HTTP | 同步傳輸 | 表單序列化 | 傳入傳出引數資料包大小混合,提供者比消費者個數多,可用瀏覽器檢視,可用表單或URL傳入引數 需同時給應用程式和瀏覽器 JS 使用的服務。 | |
rest | 多連線 | 短連線 | HTTP | 同步傳輸 | 表單序列化 | 同http,適用於更加符合rest規範的服務 | 同http |
3.1.1 協議選擇 - dubbo
從使用場景上來看,dubbo協議是比較符合我們實際業務需求的,由於其資料包相較於Http協議體積小很多,傳輸速度也會更快,另外我們可以通過socket與provider建立長連線,可以減少反覆建連帶來的不必要的網路開銷.使用此協議,我們需要注意的幾個點是
- 本地負載均衡策略
- 與Provider建立TCP長連線
- Hessian協議解析
小結 : 如果想要採用此協議連線Provider,可以使用dubbo官方推薦dubbo2.js
3.1.2 協議選擇 - rest
此協議是基於http,所以對consumer端就基本上沒有了限制.而且此協議我們在拼接好引數之後,可以直接通過瀏覽器或者是HTTP請求工具即可檢視結果,使用友好程度上來講,會優於dubbo. consumer端可以直接使用request庫請求,也不存在協議解析以及socket狀態維護的問題,消費端的程式碼實現難度也會比較小.使用此協議,我們需要注意的幾個點 :
- 需要使用keep-alive來與provider保持建連,否則http反覆斷開重連會帶來很多不必要的網路開銷
- 本地負載均衡策略
- 此協議在dubbo 2.6.x+ 才支援
小結 : 效能上雖不及dubbo.但對開發人員相對友好,可以結合業務自身場景進行選擇.
3.1.3 補充
dubbo是支援一個服務以多種協議註冊,比如一個服務可以同時註冊dubbo://
和rest://
,如果你想用http,但是目前公司所暴露出來的協議只支援dubbo,可以和提供服務的同學商量一下,額外再新增一個http協議成本也是能在接收範圍內的.
3.2 如何引用服務
目前引用服務有兩個方案,分別是
- 直接引用
- 通過註冊中心引用服務
3.2.1 直接引用服務
直接引用服務,顧名思義就是繞開註冊中心獲取我們所想要的服務提供者,由於繞開了註冊中心,自然也無法做到服務發現,而且由於單點問題,無法做到負載均衡以及高可用,所以生產環境不推薦使用此模式的
.
但是由於其開發上的便利性,在開發環境/測試環境仍可以嘗試使用此模式.
由上圖所示,開發同學聯調過程中,需要在專案工程中對指定服務開發同學的機器進行直連,而其他沒有指定的服務將會預設走註冊中心.為了避免對工程程式碼的侵入性,我們會在工程中建立應對不同環境的dubbo.properies,而dubbo.properies不會加入到工程的版本控制當中,主要用於解決不同環境下的服務直連問題.其中服務的控制粒度可以精確到具體的服務.
3.2.2 通過註冊中心引用服務
通過註冊中心發現引用服務,Dubbo常用的引用服務方式,可以做到服務自動發現,負載均衡.正式環境呼叫基本基於此模式.其中註冊中心實現有很多種,例如Zookeeper/Redis/Multicast.官方推薦Zookeeper.
3.3 服務請求結構的定義
服務請求體結構,是在對dubbo在註冊中心上註冊資訊的抽象之後的一層封裝,一方面可以提升開發人員的開發效率,另外降低開發人員自身手動拼接請求的錯誤率.
3.3.1 服務的構成
我們基於dubbo/rest兩種協議,來分析一下這兩種協議在註冊中心註冊包含哪些資訊.
- dubbo :
dubbo://192.168.1.2:10880/com.service.ProductService?dubbo=2.8&methods=getById,getByName
- rest :
rest://192.168.1.2:10081/service/com.service.ProductService?dubbo=2.8&methods=getById,getByName
我們對這兩個協議公共部分進行提取一下
- protocol : 協議型別.例如
dubbo://
. - host : provider主機地址
- port : provider對外暴露服務的埠
- interface : 對外暴露服務名稱,在java中是採用包名 + 服務名稱構成,例如
com.service.ProductService
- dubboVersion: dubbo版本
- method:服務對外暴露的方法,一個服務會同時包含多個方法.
- query:還有一個就是請求引數列表,此項是在java服務定義的,在註冊資訊中無法體現.
3.3.2 請求體的定義
基於上述服務結構構成的分析,dubbo和rest服務請求結構構成大體類似,我們對不同的協議請求的可以做如下定義.
// 1. dubbo協議的請求體定義
services.ProductService = (dubbo) => dubbo.proxyService({
dubboInterface: 'com.service.ProductService',
methods: {
getById(id) {
return [java.Long(id)];
},
getByName(name) {
return [java.String(name)];
}
},
});
複製程式碼
// rest 請求體定義
services.ProductService = (dubbo) => dubbo.proxyService({
dubboInterface: 'com.service.ProductService',
methods: {
getById(id) {
return {
method: 'get',
query: [parseInt(id)]
};
},
getByName(name) {
return [String(name)];
}
},
});
複製程式碼
兩者最大不同點在於引數定義上的不同,dubbo需要強制轉換為強型別,而rest不需要.
3.4 服務定義的維護
我們在對服務定義完成之後,接下來就會面臨一個使用上的問題,最直接的方法就是為每個工程每個服務新建一個服務檔案,但是一用就會發現一個問題請求定義的檔案分散在不同工程,無法進行統一維護升級,維護成本較高.
我們第一個反應是每個服務抽象出來,各自成為一個獨立的NPM包,譬如MemberService我們可以抽象成為@dubbo-service/member-service
,這樣就可以解決檔案分散在不同工程導致的維護問題.
3.4.1 後續問題
事情到這裡,我們已經解決了服務如何統一定義的問題,但是仍然沒有解決統一管理與維護的問題.如 :
- 維護人員職責劃分問題.NPM包的維護工作該交給服務提供方還是服務呼叫方?
- 專案遷移成本問題.如果涉及到專案遷移問題,可能會涉及到很多現有的Java服務初始化的問題,而手動去定義服務工程量巨大,如何降低遷移成本問題是我們不得不要面臨的問題.
4 最後
如果你覺得此篇文章對你有幫助,就順手點個贊吧~ 非常感謝
有什麼疑問可以直接評論回覆或者私信我,我會盡我所能回覆你~