Dubbo协议解析与ESA RPC实践

点击关注“OPPO互联网技术”,阅读更多技术干货css

1. 背景

Dubbo是一款高性能、轻量级的开源Java RPC框架,诞生于2012年,2015年中止研发,后来重启并发布了2.7及连续多个版本。Dubbo自开源以来,许多大公司都以此为微服务架构基石,甚至在官方中止维护的几年中,热度依然不减。java

但最近几年云原生技术开始成为主流,与Dubbo框架的核心设计理念有不相容之处,再加上公司安全治理的需求,OPPO互联网技术团队开发了面向云原生、 Mesh友好的ESA RPC框架。web

2.Dubbo协议解析

协议是两个网络实体进行通讯的基础,数据在网络上从一个实体传输到另外一个实体,以字节流的形式传递到对端。Dubbo协议由服务提供者与消费者双端约定,须要肯定的是一次有意义的传输内容在读到什么时候结束,由于一个一个byte传输过来,须要有一个结束。并且数据在网络上的传输,存在粘包和半包的状况,可以应对这个问题的办法就是协议可以准确的识别,当粘包发生时不会多读,当半包发生时会继续读取。缓存

2.1 Dubbo Header内容

Dubbo header的长度总共16字节,128位,以下图所示:安全

  • Magic(16 bits) : 协议魔数,标识Dubbo 数据包。bash

  • 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=20com.oppo.dubbo.demo.DemoService/0.0.1/group1.failureRateThreshold=55.5com.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=20com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.maxConcurrentLimit=20com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.failureRateThreshold=55.5com.oppo.dubbo.demo.DemoService/0.0.1/group1.sayHello.forcedOpen=falsecom.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 greetingsmessage 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的动态。


☆ END ☆


招聘信息

OPPO互联网基础技术团队招聘一大波岗位,涵盖C++、Go、OpenJDK、Java、DevOps、Android、ElasticSearch等多个方向,请点击这里查看详细信息及JD


你可能还喜欢

OPPO自研ESA DataFlow架构与实践

自研代码审查系统火眼Code Review实践

OPPO异地多活实践——缓存篇

更多技术干货

扫码关注

OPPO互联网技术

 

我就知道你“在看”

本文分享自微信公众号 - OPPO互联网技术(OPPO_tech)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索