本文来自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=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的动静。