前言

在单体应用时,不同业务模块部署在同一个JVM进程内,这时通过本地调用就可以解决不同业务模块之间的相互引用;但在多体应用时,不同业务模块大多部署到不同的机器上,这时一个高效、稳定的RPC框架就显得特别重要了。Apache Dubbo作为阿里巴巴开源的分布式RPC框架,是众多RPC框架中比较优秀的一个,在进入Apache孵化器项目后现已毕业,相信在开源社区的不断贡献下,它会成为RPC框架中的佼佼者。

为何要研究Apache Dubbo的实现原理

引用笔者在《Java并发编程之美》一书前言中的论述:研究开源框架,特别是优秀的开源框架的实现原理,可以开拓我们的技术视野,提高我们的架构能力,减少由于使用不当导致的线上故障的发生。而在微服务大行其道的今天,RPC框架作为微服务之间通信的一种手段,其在微服务架构中占有一席之地,Apache Dubbo(后面简称Dubbo)则是RPC框架中比较优秀的代表,为了更好地使用它,其实现原理自然值得我们去探究。

下面我们具体谈谈通过研究Dubbo框架的实现原理,到底能学到什么。

我们首先可以学习和深刻体会到分层架构带来的好处。Dubbo框架从整体上分为了业务(Business)层、RPC层和远程调用(Remoting)层,其中业务层提供API,让使用者方便地发布与引用服务;RPC层则是对服务注册与发现、服务代理、路由、负载均衡等功能的封装,该层又可以被划分为很多层;远程调用层则是对网络传输与请求数据序列/反序列化等的抽象。使用分层架构可以保证下层的改变对上层不可见,并且可以实现关注点分离,比如使用者使用Dubbo时只关心如何使用业务层的API来发布与引用服务,而不需要关心RPC层的实现,当新版本Dubbo升级了RPC层的逻辑时,使用者只需要升级Dubbo的版本就可以了,这是因为RPC层的修改对业务层使用者来说是透明的。

我们也可以学习到好的框架应该具有可扩展性。Dubbo就是一个扩展性极强的框架,其RPC层中的所有组件都是基于SPI扩展接口实现的,每个组件都可以被替换;Dubbo增强了JDK中提供的标准SPI功能,并且增加了对扩展接口的IoC(一个扩展接口可以直接使用setter()方法注入其他扩展接口)和AOP的支持(可以使用Wrapper类对扩展接口进行功能增强);增强SPI不会一次性实例化扩展点的所有实现类,从而避免了当扩展点实现类初始化很耗时但当前还没用上它的功能时,仍进行加载实例化这种浪费资源的情况;增强的SPI是在具体用到某个实现类时才对具体实现类进行实例化。

作为高可用分布式RPC框架,其自身必须具有容错能力,以便提高系统的可用性。Dubbo框架则提供了分布式系统中常见的集群容错策略,并且提供了扩展接口,让使用方方便地定制自己的集群容错策略,通过研究Dubbo框架提供的集群容错策略,可以让我们对分布式系统中的容错技术有深入的理解。

在分布式系统中,每个微服务都是以集群的方式部署的,那么当我们访问一个具体服务时到底访问哪一台机器提供的服务呢?这就是分布式系统中负载均衡器与路由规则要做的事情,作为分布式RPC框架,其自身也必须具有负载均衡的能力。Dubbo框架提供了分布式系统中常见的负载均衡策略,并且提供了扩展接口,让使用者方便地定制自己的负载均衡策略;另外,路由规则提供了服务治理的一种策略,在Dubbo中我们可以通过管理控制台来配置路由规则,让消费者只可访问那些服务提供者。通过研究Dubbo框架提供的负载均衡与路由策略,可以让我们对分布式系统中的负载均衡技术与路由规则有深入的理解。

在分布式系统中,当我们要消费某个服务时,如何找到其地址是一个要解决的问题。在分布式RPC中,一个通用解决方案是引入服务注册中心,当服务提供者启动时,会自动把自己的服务注册到服务注册中心;当消费者启动时,会去服务注册中心订阅自己感兴趣的服务的地址列表。在Dubbo框架中,提供了扩展接口来方便地让我们使用ZooKeeper、Redis等作为服务注册中心,通过研究Dubbo原理,我们可以深刻理解服务提供方到底是如何把服务注册到服务注册中心的,以及服务消费端是如何动态地感知服务提供方地址列表变化的。

所有RPC框架要解决的一个问题是,如何让使用者无感知地发起远程过程调用,也就是让使用者在发起远程调用时有和本地调用一样的体验。Dubbo框架和其他RPC框架一样,采用代理来实现该功能。在Dubbo框架中扩展接口Proxy就是专门来做代理使用的,并且其提供了扩展接口的JDK动态代理与Cglib的实现。研究Dubbo的原理,我们可以学习到消费端如何对服务接口进行代理以实现透明调用,服务提供端如何使用代理与JavaAssist技术来减少反射调用开销。

在Dubbo的分层架构中,Transport网络传输层把Mina和Netty抽象为统一接口,并且在默认情况下使用Netty作为底层网络通信。通过研究Dubbo,我们可以学习到Dubbo的网络协议帧是如何设计的;服务消费端是如何启动Netty客户端的,是如何把RPC请求封装为协议帧并序列化然后通过Netty客户端发起网络请求的;服务提供端又是如何启动Netty服务器进行服务监听的,是如何处理经典的半包、粘包问题的,是如何把接收到的二进制包转换为Dubbo协议帧并反序列化为POJO对象的。另外,使用Netty时都说不要在ChannelHandler中做阻塞的事情,以免阻塞了I/O线程,使其他请求得不到及时处理,那么这到底是什么意思呢?研究完Dubbo的线程模型后,你就会明白了。

对于网络请求来说,同步调用是比较直截了当的,但是同步调用意味着当前发起请求的调用线程在远端机器返回结果前必须阻塞等待,这显然很浪费资源。好的做法是发起请求的调用线程发起请求后,注册一个回调函数,然后马上返回去做其他事情,当远端把结果返回后再使用I/O线程执行回调函数,也就是发起方实现了异步调用,调用线程不会被阻塞。Dubbo则基于Netty的异步非阻塞能力和JDK 8中的CompletableFuture轻松地实现RPC请求的异步调用,提高了资源利用率。通过研究Dubbo的实现原理,我们可以对异步编程带来的好处以及实现原理有深刻的体会。

……

总之,研究透彻Dubbo框架原理实现后,你会对分布式系统中的很多技术点有深入的理解。而笔者坚信分布式系统是应用的发展方向,因为随着业务规模的增大,为了保障系统的可伸缩性、高可用性,系统必然朝着分布式方向发展。所以,掌握一些分布式系统中的优秀RPC框架的原理及实现细节,无论现在还是将来都将成为区别于他人的核心竞争力。

如何阅读本书

本书分为三部分:第一部分为基础篇,首先从整体上讲解使用Dubbo搭建的系统由哪些模块组成,各模块相互之间的调用关系是怎么样的,然后基于本书的Demo讲解如何使用Dubbo;第二部分为高级篇,主要讲解Dubbo框架内部的实现原理,包含支撑Dubbo框架的适配器类原理、动态编译原理、增强SPI原理、消费端的泛化调用实现原理、消费端异步调用与服务提供端的异步执行、Dubbo框架的线程模型、消费端负载均衡策略、消费端集群容错策略、并发控制原理、Dubbo网络协议等;第三部分为实践篇,主要探讨如何使用Arthas和一些Demo来为研究Dubbo框架原理提供方便,并且讲解如何基于CompletableFuture和Netty模拟RPC同步与纯异步调用。

读者可以在博文视点官方网站(http://www.broadview.com.cn)下载本书的Demo源码。