关于dubbo:Dubbo协议解析与OPPO自研ESA-RPC框架实践

3次阅读

共计 7375 个字符,预计需要花费 19 分钟才能阅读完成。

本文来自 OPPO 互联网根底技术团队,转载请注名作者。同时欢送关注咱们的公众号:OPPO_tech,与你分享 OPPO 前沿互联网技术及流动。

1. 背景

Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,诞生于 2012 年,2015 年进行研发,起初重启并公布了 2.7 及间断多个版本。Dubbo 自开源以来,许多大公司都以此为微服务架构基石,甚至在官网进行保护的几年中,热度仍然不减。

但最近几年云原生技术开始成为支流,与 Dubbo 框架的外围设计理念有不相容之处,再加上公司平安治理的需要,OPPO 互联网技术团队开发了面向云原生、Mesh 敌对的 ESA RPC 框架。

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):

    • 返回空值(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 的动静。

正文完
 0