本文来自OPPO互联网基础技术团队,转载请注名做者。同时欢迎关注咱们的公众号:OPPO_tech,与你分享OPPO前沿互联网技术及活动。
Dubbo是一款高性能、轻量级的开源Java RPC框架,诞生于2012年,2015年中止研发,后来重启并发布了2.7及连续多个版本。Dubbo自开源以来,许多大公司都以此为微服务架构基石,甚至在官方中止维护的几年中,热度依然不减。java
但最近几年云原生技术开始成为主流,与Dubbo框架的核心设计理念有不相容之处,再加上公司安全治理的需求,OPPO互联网技术团队开发了面向云原生、 Mesh友好的ESA RPC框架。segmentfault
协议是两个网络实体进行通讯的基础,数据在网络上从一个实体传输到另外一个实体,以字节流的形式传递到对端。Dubbo协议由服务提供者与消费者双端约定,须要肯定的是一次有意义的传输内容在读到什么时候结束,由于一个一个byte传输过来,须要有一个结束。并且数据在网络上的传输,存在粘包和半包的状况,可以应对这个问题的办法就是协议可以准确的识别,当粘包发生时不会多读,当半包发生时会继续读取。安全
Dubbo header的长度总共16字节,128位,以下图所示:服务器
Dubbo 数据包的body 部份内容,分为请求包与响应包。网络
若是是请求包,则包含的部分有:数据结构
附件(Attachment):架构
若是是响应包,则包含的内容有:并发
返回值类型(byte):app
经过对dubbo协议的解析,咱们能够知道,dubbo协议是一个Header定长的变长协议。这也在咱们ESA RPC实践过程当中提供了一些思路。框架
Dubbo协议的设计很是紧凑、简单,尽量的减小传输包大小,能用一个bit表示的字段,不会用一个byte。
Dubbo自开源以来,在业内形成了巨大的影响,许多公司甚至大厂都以此为微服务架构基石,甚至在Dubbo官方中止维护的几年中,热度依然不减,足以证实其自己的优秀。
在这过程当中,Dubbo协议的内容一直没有太大变化,主要是为了兼容性考虑,但其余内容,随着Dubbo的发展变化倒是很大。这里咱们主要聊一聊dubbo从2.7.0版本之后的状况。
这是dubbo自2.7.0版本以来,各个版本的简要功能说明,以及升级建议。能够看到dubbo官方推荐生产使用的只有2.7.3 和2.7.4.1两个版本。但这两个推荐版本,也有不能知足需求的地方。
因为dubbo在2.7.3 和2.7.4.1 这两个版本中改动巨大,使得这两个版本没法向下兼容,这让基于其余版本作的一些dubbo扩展几乎没法使用。升级dubbo的同时,还须要将之前的扩展所有检查修改一遍,这带来很大工做量。并且除了咱们自身团队的一些公共扩展外,全公司其余业务团队极可能还有本身的一些扩展,这无疑增大了咱们升级dubbo的成本。
最近几年云原生技术开始成为主流,与Dubbo框架的核心设计理念也有不相容之处,再加上公司安全治理的需求,咱们须要一款面向云原生、 Mesh友好的RPC框架。
在这个背景下,OPPO互联网技术团队从2019年下半年开始动手设计开发ESA RPC,到2020年一季度,ESA RPC 初版成功发布。下面咱们简单介绍下ESA RPC的一些主要功能。
ESA RPC经过深度整合发布平台,实现实例级服务注册与发现,如图所示:
应用发布时,相应的发布平台会将实例信息注册到OPPO自研的注册中心ESA Registry(应用自己则再也不进行注册),注册信息包含应用名、ip、端口、实例编号等等,消费者启动时只需经过应用编号订阅相关提供者便可。
既然服务注册部分是由发布平台完成,开发者在发布应用时,就须要填写相关信息,即相关的暴露协议以及对应的端口,这样发布平台才能够正确注册提供者信息。
ESA RPC全面拥抱java8的CompletableFuture ,咱们将同步和异步的请求统一处理,认为同步是特殊的异步。而Dubbo,因为历史缘由,最初dubbo使用的jdk版本仍是1.7,因此在客户端的线程模型中,为了避免阻塞IO线程,dubbo增长了一个Cached线程池,全部的IO消息统一都通知到这个Cached线程池中,而后再切换回相应的业务线程,这样可能会形成当请求并发较高时,客户端线程暴涨问题,进而致使客户端性能低下。
因此咱们在ESA RPC客户端优化了线程模型,将原有的dubbo客户端cached线程池取消,改成以下图模型:
具体作法:
对于一些高并发的服务,可能会因传统Failover 中的重试而致使服务雪崩。ESA RPC对此进行优化,采用基于请求失败率的Failover ,即当请求失败率低于相应阈值时,执行正常的failover重试策略,而当失败率超过阈值时,则中止进行重试,直到失败率低于阈值再恢复重试功能。
ESA RPC采用RingBuffer 的数据结构记录请求状态,成功为0,失败为1。用户可经过配置的方式指定该RingBuffer 的长度,以及请求失败率阈值。
ESA ServiceKeeper (如下简称ServiceKeeper ),属于OPPO自研的基础框架技术栈ESA Stack系列的一员。ServiceKeeper 是一款轻量级的服务治理框架,经过拦截并代理原始方法的方式织入限流、并发数限制、熔断、降级等功能。
ServiceKeeper 支持方法和参数级的服务治理以及动态动态更新配置等功能,包括:
ESA RPC中默认使用ServiceKeeper 来实现相关服务治理内容,使用起来也相对简单。
application.properties 文件中开启ServiceKeeper 功能。
# 开启服务端 esa.rpc.provider.parameter.enable-service-keeper=true # 开启客户端 esa.rpc.consumer.parameter.enable-service-keeper=true
新增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} ...
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(); //执行真正的建连操做 } }
因为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;
这里仅举一例,展现ESA RPC性能。
因为历史缘由,现公司内部大量使用的是Dubbo做为RPC框架,以及zookeeper注册中心,如何可以保证业务的平滑迁移,一直是咱们在思考的问题。这个问题想要解答,主要分为如下两点。
在代码层面,ESA RPC考虑到这个历史缘由,尽量的兼容dubbo,尽量下降迁移成本。但ESA RPC毕竟做为一款新的RPC框架,想要零成本零改动迁移是不可能的,但在没有dubbo扩展的状况下,改动很小。
这一点咱们举例说明,当业务方迁移某一应用至ESA RPC框架时,该应用中消费ABCD四个接口,但这些接口的服务提供者应用并未升级至ESA RPC,接口元数据信息均保存至zookeeper注册中心当中,而ESA RPC推荐使用的ESA Registry注册中心中没有这些提供者信息,这就致使了消费者没法消费这些老的提供者信息。
针对这一问题,后续咱们ESA Stack系列会提供相应的数据同步工具,将原zookeeper注册中心中的服务元数据信息同步到咱们ESA Registry中,而zookeeper中的这些信息暂时不删除(以便老的接口消费者可以消费),等待均升级完成后,便可停用zookeeper注册中心。
在上面Dubbo协议解析过程当中,咱们分析了Dubbo协议的优缺点,了解了Dubbo协议的不足。因此后续的版本升级过程当中,自研RPC协议是一个不可忽视的内容。自研RPC协议须要充分考虑安全、性能、Mesh支持、可扩展、兼容性等因素,相信经过自研RPC协议可使咱们的ESA RPC更上一层楼。
在这篇文章中,咱们主要分享了Dubbo协议的分析以及ESA RPC的实践内容,后续OPPO互联网技术团队会继续分享更多ESA RPC的动态。