天天看點

微服務跨語言調用

微服務架構已成為目前網際網路架構的趨勢,關于微服務的讨論,幾乎占據了各種技術大會的絕大多數版面。國内使用最多的服務治理架構非阿裡開源的 dubbo 莫屬,千米網也選擇了 dubbo 作為微服務治理架構。另一方面,和大多數網際網路公司一樣,千米的開發語言是多樣的,大多數後端業務由 java 支撐,而每個業務線有各自開發語言的選擇權,便出現了 nodejs,python,go 多語言調用的問題。

跨語言調用是一個很大的話題,也是一個很有挑戰的技術活,目前業界經常被提及的解決方案有如下幾種,不妨拿出來老生常談一番:

spring cloud。spring cloud 提供了一整套微服務開發元件,它主要面向 java 開發,但由于其使用的協定是基于 restful 風格的 http 協定,這使得其天然具備跨語言能力,異構語言隻需要提供 http 用戶端,便可以實作跨語言調用。 service mesh。号稱下一代微服務架構的 service mesh,其解決跨語言問題的核心在于 SideCar ,SideCar 在 service mesh 的發展過程中概念不斷的遷移,但本質都是完成了一件事:處理服務間通信,負責實作請求的可靠傳遞。 motan。motan 是新浪微網誌開源的一款跨語言服務治理架構,在其早期版本中僅支援 motan-java,随着版本演進,在目前最新版本(1.1.0)中,提供了 motan-go,motan-php,motan-openresty 等跨語言特性。類似于 service mesh 中的 SideCar,motan 借助于 motan-go 作為 agent 完成協定的轉發,并且依賴于定制協定:motan2,實作跨語言調用。

當我們再聊跨語言調用時我們在聊什麼?縱觀上述幾個較為通用,成熟的解決方案,可以得出結論:解決跨語言調用的思路無非是兩種:

尋找一個通用的協定 使用 agent 完成協定的适配

如果一個新型的團隊面臨技術選型,我認為上述的方案都可以納入參考,可考慮到遺留系統的相容性問題

舊系統的遷移成本

這也關鍵的選型因素。我們做出的第一個嘗試,便是在 RPC 協定上下功夫。

通用協定的跨語言支援

springmvc的美好時代

微服務跨語言調用

springmvc

在沒有實作真正的跨語言調用之前,想要實作“跨語言”大多數方案是使用 http 協定做一層轉換,最常見的手段莫過于借助 springmvc 提供的 controller/restController,間接調用 dubbo provider。這種方案的優勢和劣勢顯而易見

優勢是簡單,是最通俗的解決方案。 劣勢是使得調用鍊路變長,tcp 通信之上又多了一層 http 通信;開發體驗差,為了将 rpc 接口暴露出去,需要額外編寫一份 controller 層的代碼。

通用協定的支援

事實上,大多數服務治理架構都支援多種協定,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 協定被設計用來滿足跨語言的需求主要展現在兩個細節中—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 方案中異構語言并不直接互動。

dubbo協定的跨語言支援

dubbo 協定設計之初隻考慮到了正常的 rpc 調用場景,它并不是為跨語言而設計,但跨語言支援從來不是隻有支援、不支援兩種選擇,而是要按難易程度來劃分。是的,dubbo 協定的跨語言調用可能并不好做,但并非無法實作。千米網便實作了這一點,nodejs 建構的前端業務是異構語言的主戰場,最終實作了 dubbo2.js,打通了 nodejs 和原生 dubbo 協定。作為本文第二部分的核心内容,重點介紹下我們使用 dubbo2.js 幹了什麼事。

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 來說,主要有兩個難點:

異構語言如何表示 java 中的資料類型,特别是動态語言,可能不存在嚴格的資料類型

序列化方案如何做到跨語言