一、什么是dubbo
dubbo是基于java的开源的一个rpc框架。具有以下特性:
(1)面向接口代理的高性能rpc调用。
提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。
(2)服务自动注册与发布。
支持多种注册中心服务,服务实例上下线实时感知。
(3)运行期流量调度。
内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布、同机房优先等功能。
(4)智能负载均衡。
内置多种负载均衡策略,智能感知下游节点监控状况,显著减少调用延迟,提高系统吞吐量。
(5)高度可扩展能力。
遵循微内核 插件的设计思想,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。
(6)可视化的服务治理与运维。
提供丰富服务治理、运维工具,随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。
二、dubbo核心组件
(1)Service层
业务层。包括业务代码的接口与实现,即开发者实现的业务代码。
(2)proxy层
服务代理层。在Dubbo中,无论生产者还是消费者,框架都会生成一个代理类,整个过程对上层是透明的。当调用一个远程接口时,看起来就像是调用了一个本地的接口一样,代理层会自动做远程调用并返回结果,即让业务层对远程调用无感知。
(3)registry层
注册层。负责Dubbo框架的服务注册与发现。当有新的服务加入或旧服务下线时,注册中心都会感知并通知给所有订阅方,整个过程不需要人工参与。
(4)cluster层
集群容错层。该层主要负责:远程调用失败时的容错策略(如失败重试、快速失败)、选择具体调用节点时的负载均衡(如随机、一致性hash等)、特殊调用路径的路由策略(如某个消费者只会调用某个IP的生成者)。
(5)monitor层
监控层。这一层主要负责监控统计调用次数和调用时间等。
(6)protocol层
远程调用层。封装RPC调用具体过程,Protocol是Invoker暴露(发布一个服务让别人可以调用)和引用(引用一个远程服务到本地)的主功能入口,它负责管理invoker的整个生命周期。
(7)exchange层
信息交互层,建立request-response模型,封装请求响应模式,如把同步请求转化为异步请求。
(8)transport层
网络传输层。把网络传输抽象为统一的接口,如Mina和Netty虽然接口不一样,但是Dubbo在他们上面又封装了统一的接口。用户也可以根据其扩展接口添加更多的网络传输方式。
(9)Serialize层
序列化层。如果数据要通过网络进行发送,则需要先做序列化,变成二进制流。序列化层负责管理整个框架传输时的序列化/反序列化工作。
三、rpc和http的区别
rpc是一种远程过程调用的协议,在dubbo中使用的话主要可以使用这种协议由一台服务向另一台服务进行请求,不需要了解底层忘了技术的协议。调用都在内网。
http是一种超文本传输协议,主要是由浏览器或app客户端之间的应用层通讯协议。可以支持外网调用。
严格意义上来讲,rpc和http并不是一个层级上的东西,没有可比性。
四、dubbo服务的启动流程
(1)启动provider服务时,会把所有需要暴露的接口注册到注册中心,并会订阅动态配置configurations。
(2)启动consumers服务时,会订阅服务端接口、动态配置、负载均衡集群(providers、configurations、routes)。
(3)收到订阅后的处理。
providers:订阅的动态配置信息更改时,注册中心会推送动态配置信息给provider服务。
consumers:订阅内容变更时,注册中心会推送订阅的内容(providers、configurations、routes)给consumer服务。
(4)客户端与服务端建立长连接,进行数据通信(接口调用)。
(5)客户端与服务端启动后,后台会启动定时器,每隔一定时间发送接口调用次数、时间等统计数据给监控中心monitor。
五、JDK-SPI机制和Dubbo-SPI机制
(1)JDK-SPI机制
为了实现不对接口或实现类进行硬编码,即不在代码中写死代码,可以使用SPI机制,将代码实现移到外面。当外部加载这个模块时,可以通过jar包中META-INF/services里的配置文件得到具体的实现类名,并加载与实例化。
缺点:JDK的SPI机制会一次性加载与实例化配置文件中的所有实现,因此不管是否用到这些实现类都会加载META-INF/services中配置的所有实现类,比较耗时。
(2)Dubbo-SPI机制
本质上与JDK-SPI机制区别不大,dubbo-spi实现类文件被存储在META-INF/dubbo/internal目录下,并且内容定义为扩展名=具体的实现类名。dubbo加载时做了优化,会根据需要找到对应的扩展名,按需来加载这些需要加载的SPI实现类。
六、@Adaptive注解
(1)可以注解在方法上
dubbo会为该方法生成代理类,表示扩展的加载逻辑需由框架自动生成。
(2)可以注解在类上
dubbo不会为该方法生成代理类,表示扩展的加载逻辑由人工编码完成。
七、Dubbo为何默认使用javassist进行动态编译
Java中动态编译的方式有jdk、cglib、ASM、javassist等。dubbo默认使用javassist进行动态编译,主要是快,并且生成字节码方便。其余生成字节码的工具成本较高,比较麻烦,比如使用ASM的方式。
八、服务暴露流程
服务暴露会从spring ioc容器刷新完成之后开始进行暴露。正式暴露前会将需要暴露的服务组装成URL对象,该对象存储了服务的ip、端口号、全路径名、parameters参数(路由、分组、版本、超时时间、应用名等配置信息)等配置信息。
通过proxyFactory.getInvoker方法,并利用javassist来进行动态代理,将服务暴露接口封装成invoker对象。然后开始正式暴露服务。
首先会将该invoker对象封装成export对象放入到exportedMap中供之后的远程调用查找。
然后会启动注册中心,将提供者信息注册到注册中心。
最后对configurations节点进行订阅。
以上就是服务暴露的总体流程。
九、Exporter和Invoker
Invoker是通过ProxyFactory.getInvoker方法,并利用javassist方式来进行动态代理,将服务暴露接口封装成invoker对象。而invoker实际上就是一个服务对象实例,主要是通过invoker向他发起调用。
Exporter是根据不同协议暴露invoker进行封装的类,会根据不同的协议头进行识别,调用对应的XXXProtocol的export()方法。
实际上这两个对象的区别在于服务暴露中,首先通过动态代理来将服务暴露接口封装成invoker对象。第二步则会将invoker根据具体的协议转换成exporter。
十、服务引用流程
服务引用的时机有两种,一种是饿汉式即加载完毕就会引入,另一种是懒汉式即只有当这个服务被注入到其他类中时启动引入流程,默认是懒汉式。
服务引用首先会根据需要引入的配置信息组装成url对象,并根据提供者的协议进入Dubbo协议的引入即XXXProtocol.refer。然后获取注册中心,如果注册中心不存在会初始化注册中心。接下来会向注册中心注册消费者信息,并且订阅提供者、配置、路由等节点。
最后通过Cluster.join来包装invoker,默认是failoverCluster,最终通过proxyFactory.getProxy返回代理类,代理类中包含了NettyClient来进行远程通信。
十一、Dubbo调用过程
Dubbo的调用过程是从Proxy开始,Proxy持有一个invoker对象。然后触发invoke调用。在invoke调用过程中,需要使用cluster,cluster负责容错,如调用失败的重试。cluster在调用之前会通过directory获取所有可调用的远程服务列表(一个接口可能有多个节点提供服务)。由于可以调用的远程服务有很多,此时如果用户配置了路由规则(router-如指定某些方法只能调用某个节点),那么还会根据路由规则将invoker列表过滤一遍。
然后存活下来的invoker可能还会有很多个,会继续通过LoadBalance方法做负载均衡,最终选出一个可以调用的invoker。这个invoker在真正调用之前又会经过一个过滤器链,这个过滤器链通常是处理上下文、限流、计数等。
接着会使用Client进行数据传输,如netty Client等。传输之前会做私有协议的构造,会用到codec接口,构造完成后,就会对数据包做序列化然后传输到服务提供者。服务提供者接收到服务也会使用Codec处理协议头以及一些半包黏包等。处理完成后再对完整的数据报文做反序列化处理。
到了服务提供者端,这个请求会被分配到线程池中进行处理。Server会处理这些Request,根据请求查找对应的Export(内部封装了invoker对象)。调用服务端方法之前又会经过一次服务提供者的过滤器链。
最终,得到具体接口的真实实现并调用,原路返回结果。
十二、如何设计一个rpc框架
可以参照dubbo框架的思路。
(1)首先要有一个注册中心,需要将你的服务注册到注册中心,可以保留各个服务的信息,这里的注册中心就可以用zookeeper。
有了注册中心,消费者获取服务信息也可以从注册中心去拿。
(2)其次需要高性能的网络传输,可以使用netty来实现,底层是基于NIO并且对NIO的使用有了很好的封装,不需要重复造轮子。
(3)网络传输时需要指定啥格式传输,可以使用hessian序列化协议、json序列化协议等方式。
(4)对于客户端引用服务端接口时,由于不需要关心底层的远程调用,所以在引用服务端接口时需要基于动态代理设计该引用,在引用的时候通过动态代理的方式生成包含底层网络传输和服务接口的代理类。
(5)客户端如何指定对应的服务器进行调用,则需要采用路由或负载均衡算法,比如随机轮询等。
以上就是我设计的一个基本的rpc框架的一个思路。