Dubbo協議解析與OPPO自研ESA RPC框架實踐

本文來自OPPO互聯網基礎技術團隊,轉載請註名做者。同時歡迎關注咱們的公衆號:OPPO_tech,與你分享OPPO前沿互聯網技術及活動。

1. 背景

Dubbo是一款高性能、輕量級的開源Java RPC框架,誕生於2012年,2015年中止研發,後來重啓併發布了2.7及連續多個版本。Dubbo自開源以來,許多大公司都以此爲微服務架構基石,甚至在官方中止維護的幾年中,熱度依然不減。java

但最近幾年雲原生技術開始成爲主流,與Dubbo框架的核心設計理念有不相容之處,再加上公司安全治理的需求,OPPO互聯網技術團隊開發了面向雲原生、 Mesh友好的ESA RPC框架。segmentfault

2.Dubbo協議解析

協議是兩個網絡實體進行通訊的基礎,數據在網絡上從一個實體傳輸到另外一個實體,以字節流的形式傳遞到對端。Dubbo協議由服務提供者與消費者雙端約定,須要肯定的是一次有意義的傳輸內容在讀到什麼時候結束,由於一個一個byte傳輸過來,須要有一個結束。並且數據在網絡上的傳輸,存在粘包和半包的狀況,可以應對這個問題的辦法就是協議可以準確的識別,當粘包發生時不會多讀,當半包發生時會繼續讀取。安全

2.1 Dubbo Header內容

Dubbo header的長度總共16字節,128位,以下圖所示:服務器

  • Magic(16 bits) : 協議魔數,標識Dubbo 數據包。
  • Req/Res(1 bit) : 標識請求或者相應。請求:1,相應:0。
  • Two Way(1 bit) : 僅在 Req/Res 爲1(請求)時纔有用,標記是否指望從服務器返回值。若是須要來自服務器的返回值,則設置爲1。
  • Event(1 bit) : 標識是不是事件消息,例如,心跳事件。若是這是一個事件,則設置爲1。
  • SerializationId(5 bits) : 序列化id。
  • Status(8 bits) : 僅在 Req/Res 爲0(響應)時有用,用於標識響應的狀態。
  • RequstId(64 bits) : 標識惟一請求。類型爲long。
  • Data length(32 bits) : 序列化後的內容長度(變長部分,即不包含header),按字節計數。經過payload參數指定,默認爲8M。

2.2 Dubbo body內容

Dubbo 數據包的body 部份內容,分爲請求包與響應包。網絡

若是是請求包,則包含的部分有:數據結構

  • dubbo協議版本號(2.0.2);
  • 接口名;
  • 接口版本號;
  • 方法名;
  • 方法參數類型;
  • 方法參數;
  • 附件(Attachment):架構

    • 接口分組(group);
    • 接口版本號(version);
    • 接口名;
    • 自定義附件參數;

若是是響應包,則包含的內容有:併發

  • 返回值類型(byte):app

    • 返回空值(2);
    • 正常返回值(1);
    • 異常(0);
  • 返回值;

經過對dubbo協議的解析,咱們能夠知道,dubbo協議是一個Header定長的變長協議。這也在咱們ESA RPC實踐過程當中提供了一些思路。框架

2.3 Dubbo協議優缺點

2.3.1 優勢

Dubbo協議的設計很是緊湊、簡單,儘量的減小傳輸包大小,能用一個bit表示的字段,不會用一個byte。

2.3.2 不足

  • 請求body中某些字段重複傳遞(如接口名,接口版本號),即body內容與附件attachment 中存在重複字段,增大傳輸數據包大小;
  • 對於ServiceMesh 場景很不友好。在ServiceMesh 場景中,會將原sdk中的大部分功能遷移至SideCar 中實現,這裏以服務發現爲例。Dubbo 中的服務發現,是經過接口名
    (interfaceName)、接口分組(group)、接口版本號(version)三者定位一個惟一服務,也是服務發現的關鍵要素,可是咱們從dubbo body內容可知,必需要將完整的數據包所有解析(attachment位於body末),才能獲取到這三個要素,這是徹底不必的。
  • 沒有預留字段,擴展性不足。

3. Dubbo的現狀

Dubbo自開源以來,在業內形成了巨大的影響,許多公司甚至大廠都以此爲微服務架構基石,甚至在Dubbo官方中止維護的幾年中,熱度依然不減,足以證實其自己的優秀。

在這過程當中,Dubbo協議的內容一直沒有太大變化,主要是爲了兼容性考慮,但其餘內容,隨着Dubbo的發展變化倒是很大。這裏咱們主要聊一聊dubbo從2.7.0版本之後的狀況。

3.1 Dubbo 2.7.x版本總覽

這是dubbo自2.7.0版本以來,各個版本的簡要功能說明,以及升級建議。能夠看到dubbo官方推薦生產使用的只有2.7.3 和2.7.4.1兩個版本。但這兩個推薦版本,也有不能知足需求的地方。

因爲dubbo在2.7.3 和2.7.4.1 這兩個版本中改動巨大,使得這兩個版本沒法向下兼容,這讓基於其餘版本作的一些dubbo擴展幾乎沒法使用。升級dubbo的同時,還須要將之前的擴展所有檢查修改一遍,這帶來很大工做量。並且除了咱們自身團隊的一些公共擴展外,全公司其餘業務團隊極可能還有本身的一些擴展,這無疑增大了咱們升級dubbo的成本。

4. ESA RPC最佳實踐

最近幾年雲原生技術開始成爲主流,與Dubbo框架的核心設計理念也有不相容之處,再加上公司安全治理的需求,咱們須要一款面向雲原生、 Mesh友好的RPC框架。

在這個背景下,OPPO互聯網技術團隊從2019年下半年開始動手設計開發ESA RPC,到2020年一季度,ESA RPC 初版成功發佈。下面咱們簡單介紹下ESA RPC的一些主要功能。

4.1 實例級服務註冊與發現

ESA RPC經過深度整合發佈平臺,實現實例級服務註冊與發現,如圖所示:

應用發佈時,相應的發佈平臺會將實例信息註冊到OPPO自研的註冊中心ESA Registry(應用自己則再也不進行註冊),註冊信息包含應用名、ip、端口、實例編號等等,消費者啓動時只需經過應用編號訂閱相關提供者便可。

既然服務註冊部分是由發佈平臺完成,開發者在發佈應用時,就須要填寫相關信息,即相關的暴露協議以及對應的端口,這樣發佈平臺才能夠正確註冊提供者信息。

4.2 客戶端線程模型優化

ESA RPC全面擁抱java8的CompletableFuture ,咱們將同步和異步的請求統一處理,認爲同步是特殊的異步。而Dubbo,因爲歷史緣由,最初dubbo使用的jdk版本仍是1.7,因此在客戶端的線程模型中,爲了避免阻塞IO線程,dubbo增長了一個Cached線程池,全部的IO消息統一都通知到這個Cached線程池中,而後再切換回相應的業務線程,這樣可能會形成當請求併發較高時,客戶端線程暴漲問題,進而致使客戶端性能低下。

因此咱們在ESA RPC客戶端優化了線程模型,將原有的dubbo客戶端cached線程池取消,改成以下圖模型:

具體作法:

  • 當前業務線程發出遠程調用請求後,生成CompletableFuture 對象,並傳遞至IO線程,等待返
    回;
  • IO線程收到返回內容後,找到與之對應的CompletableFuture 對象,直接賦予其返回內容;
  • 業務線程經過本身生成的CompletableFuture 對象獲取返回值;

4.3 智能Failover

對於一些高併發的服務,可能會因傳統Failover 中的重試而致使服務雪崩。ESA RPC對此進行優化,採用基於請求失敗率的Failover ,即當請求失敗率低於相應閾值時,執行正常的failover重試策略,而當失敗率超過閾值時,則中止進行重試,直到失敗率低於閾值再恢復重試功能。

ESA RPC採用RingBuffer 的數據結構記錄請求狀態,成功爲0,失敗爲1。用戶可經過配置的方式指定該RingBuffer 的長度,以及請求失敗率閾值。

4.4 ServiceKeeper

ESA ServiceKeeper (如下簡稱ServiceKeeper ),屬於OPPO自研的基礎框架技術棧ESA Stack系列的一員。ServiceKeeper 是一款輕量級的服務治理框架,經過攔截並代理原始方法的方式織入限流、併發數限制、熔斷、降級等功能。

ServiceKeeper 支持方法和參數級的服務治理以及動態動態更新配置等功能,包括:

  • 方法隔離
  • 方法限流
  • 方法熔斷
  • 方法降級
  • 參數級隔離、限流、熔斷
  • 方法重試
  • 接口分組
  • 動態更新配置,實時生效

ESA RPC中默認使用ServiceKeeper 來實現相關服務治理內容,使用起來也相對簡單。

Step 1

application.properties 文件中開啓ServiceKeeper 功能。

# 開啓服務端
esa.rpc.provider.parameter.enable-service-keeper=true

# 開啓客戶端
esa.rpc.consumer.parameter.enable-service-keeper=true

Step 2

新增service-keeper.properties 配置文件,並按照以下規則進行配置:

# 接口級配置規則:{interfaceName}/{version}/{group}.{serviceKeeper params},示例:
com.oppo.dubbo.demo.DemoService/0.0.1/group1.maxConcurrentLimit=20
com.oppo.dubbo.demo.DemoService/0.0.1/group1.failureRateThreshold=55.5
com.oppo.dubbo.demo.DemoService/0.0.1/group1.forcedOpen=55.5
...

#方法級動態配置規則:{interfaceName}/{version}/{group}.{methodName}.{serviceKeeper params},示例:
com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.maxConcurrentLimit=20
com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.maxConcurrentLimit=20
com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.failureRateThreshold=55.5
com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.forcedOpen=false
com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.limitForPeriod=600
...

#參數級動態配置規則:{interfaceName}/{version}/{group}.{methodName}.參數別名.配置名稱=配置值列表,示例:
com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.arg0.limitForPeriod={LiSi:20,ZhangSan:50}
...

4.5 鏈接管理

ESA RPC中,一個消費者與一個提供者,默認只會建立一個鏈接,可是容許用戶經過配置建立多個,配置項爲connections (與dubbo保持一致)。ESA RPC的鏈接池經過公司內部一個全異步對象池管理庫commons pool來達到對鏈接的管理,其中鏈接的建立、銷燬等操做均爲異步執行,避免阻塞線程,提高框架總體性能。

須要注意的是,這裏的建連過程,有一個併發問題要解決: 當客戶端在高併發的調用建連方法時,如何保證創建的鏈接恰好是所設定的個數呢?爲了配合 Netty 的無鎖理念,咱們也採用一個無鎖化的建連過程來實現,利用 ConcurrentHashMap 的putIfAbsent 方法:

AcquireTask acquireTask = this.pool.get(idx);
if (acquireTask == null) {
    acquireTask = new AcquireTask();
    AcquireTask tmpTask = this.pool.putIfAbsent(idx, acquireTask);
    if (tmpTask == null) {
        acquireTask.create(); //執行真正的建連操做
    }
}

4.6 gRPC協議支持

因爲ESA RPC默認使用ESA Regsitry 做爲註冊中心,由上述實例註冊部分可知,服務註冊經過發佈平
臺來完成,因此ESA RPC對於gRPC協議的支持具備自然的優點,即服務的提供者能夠不接入任何sdk,甚至能夠是其餘非java語言,只須要經過公司發佈平臺發佈應用後,就能夠註冊至註冊中心,消費者也就能夠進行訂閱消費。

這裏咱們以消費端爲例,來介紹ESA RPC客戶端如何請求gRPC服務端。

proto文件定義:

syntax = "proto3";

option java_multiple_files = false;
option java_outer_classname = "HelloWorld";
option objc_class_prefix = "HLW";

package esa.rpc.grpc.test.service;

// The greeting service definition.
service GreeterService {
    // Sends a greeting
    rpc sayHello (HelloRequest) returns (HelloReply) {
    }
}

service DemoService {
    // Sends a greeting
    rpc sayHello (HelloRequest) returns (HelloReply) {
    }
}

// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

而後maven中添加proto代碼生成插件:

<build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.5.0.Final</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.0</version>
                <configuration>

<protocArtifact>com.google.protobuf:protoc:3.11.0:exe:${os.detected.classifier}
</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>esa.rpc:protoc-gen-grpc-java:1.0.0-
SNAPSHOT:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

如上proto定義文件,經過protobuf:compile和protobuf:compile-custom則會生成以下代碼:

能夠看到,自動生成的代碼中咱們額外生成了相應的java接口。

在dubbo客戶端咱們就能夠直接使用這個接口進行遠程調用,使用方式:

@Reference(...,protocol="grpc")
private DemoService demoService;

4.7 ESA RPC性能

這裏僅舉一例,展現ESA RPC性能。

5. ESA RPC將來規劃

5.1 ESA RPC如何進行平滑遷移?

因爲歷史緣由,現公司內部大量使用的是Dubbo做爲RPC框架,以及zookeeper註冊中心,如何可以保證業務的平滑遷移,一直是咱們在思考的問題。這個問題想要解答,主要分爲如下兩點。

5.1.1 代碼層面

在代碼層面,ESA RPC考慮到這個歷史緣由,儘量的兼容dubbo,儘量下降遷移成本。但ESA RPC畢竟做爲一款新的RPC框架,想要零成本零改動遷移是不可能的,但在沒有dubbo擴展的狀況下,改動很小。

5.1.2 總體架構

這一點咱們舉例說明,當業務方遷移某一應用至ESA RPC框架時,該應用中消費ABCD四個接口,但這些接口的服務提供者應用並未升級至ESA RPC,接口元數據信息均保存至zookeeper註冊中心當中,而ESA RPC推薦使用的ESA Registry註冊中心中沒有這些提供者信息,這就致使了消費者沒法消費這些老的提供者信息。

針對這一問題,後續咱們ESA Stack系列會提供相應的數據同步工具,將原zookeeper註冊中心中的服務元數據信息同步到咱們ESA Registry中,而zookeeper中的這些信息暫時不刪除(以便老的接口消費者可以消費),等待均升級完成後,便可停用zookeeper註冊中心。

5.2 自研RPC協議

在上面Dubbo協議解析過程當中,咱們分析了Dubbo協議的優缺點,瞭解了Dubbo協議的不足。因此後續的版本升級過程當中,自研RPC協議是一個不可忽視的內容。自研RPC協議須要充分考慮安全、性能、Mesh支持、可擴展、兼容性等因素,相信經過自研RPC協議可使咱們的ESA RPC更上一層樓。

5.3 其餘

  • 多協議暴露
  • 同機房優先路由
  • 類隔離
  • ...

在這篇文章中,咱們主要分享了Dubbo協議的分析以及ESA RPC的實踐內容,後續OPPO互聯網技術團隊會繼續分享更多ESA RPC的動態。

相關文章
相關標籤/搜索