Dubbo框架原理及使用
一、简介
1.1 概念及特性
简介:Apache Dubbo是一款高性能的Java RPC框架。其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。
特性:①面向接口代理的高性能RPC调用:提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程过程调用的底层细节。②智能负载均衡:内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量。③服务自动注册与发现:支持多种注册中心服务,服务实例上下线实时感知。④高度可扩展能力:遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。⑤运行期流量调度:内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能。⑥可视化的服务治理与运维:提供丰富服务治理、运维工具(监控):随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。
1.2 Dubbo 处理流程

调用流程:服务提供者在服务容器启动时,向注册中心注册自己提供的服务。服务消费者在启动时,向注册中心订阅自己所需的服务。注册中心返回服务提供者地址列表给消费,如果有变更,注册中心会基于长连接推送变更数据给消费者。服务消费者从提供者地址列表中基于负载均衡算法选一台提供者进行调用,如果调用失败,则重新选择一台。服务提供者和消费者在内存中的调用次数和调用时间定时每分钟发送给监控中心。
二、高级使用
2.1 SPI
简介:JDK SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用SPI机制的优势是实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。
SPI遵循如下约定:1、当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;2、接口实现类所在的jar包放在主程序的classpath中;3、主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;4、SPI的实现类必须携带一个无参构造方法;
dubbo自己做SPI的目的:
1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源 2. 如果有扩展点加载失败,则所有扩展点无法使用。3. 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其他的扩展点进行注入。
2.2 Filter机制
简介:Dubbo的Filter机制,是专门为服务提供方和服务消费方调用过程进行拦截设计的,每次远程方法执行,该拦截都会被执行。这样就为开发者提供了非常方便的扩展性,比如为dubbo接口实现ip白名单功能、监控功能 、日志记录。
步骤如下:①实现org.apache.dubbo.rpc.Filter 接口。②使用org.apache.dubbo.common.extension.Activate 接口进行对类进行注册通过group可以指定生产端 消费端 如:
@Activate(group = {CommonConstants.CONSUMER)。③计算方法运行时间的代码实现④在META-INF.dubbo 中新建org.apache.dubbo.rpc.Filter 文件,并将当前类的全名写入: timerFilter=包名.过滤器的名字。
2.3 负载均衡
在集群负载均衡时,Dubbo 提供了多种均衡策略(包括随机、轮询、最少活跃调用数、一致性Hash),缺省为random随机调用。
//在服务消费者一方配置负载均衡策略 @Reference(check = false, loadbalance = "random") //在服务提供者一方配置负载均衡 @Service(loadbalance = "random")
自定义负载均衡器:负载均衡器在Dubbo中的SPI接口是org.apache.dubbo.rpc.cluster.LoadBalance , 可以通过实现这个接口来实现自定义的负载均衡规则。在dubbo-spi-loadbalance工程的META-INF/dubbo 目录下新建org.apache.dubbo.rpc.cluster.LoadBalance文件,并将当前类的全名写入: onlyFirst=包名.负载均衡器。
2.4 异步调用
<dubbo:reference id="helloService" interface="com.lagou.service.HelloService">
<dubbo:method name="sayHello" async="true" />
</dubbo:reference>
通过RpcContext.getContext().getFuture() 来进行获取Future对象来进行后续的结果等待操作。
2.5 线程池
dubbo在使用时,都是通过创建真实的业务线程池进行操作的。目前已知的线程池模型有两个和java中的相互对应: fix: 表示创建固定大小的线程池。也是Dubbo默认的使用方式,默认创建的执行线程数为200,并
且是没有任何等待队列的。所以再极端的情况下可能会存在问题,比如某个操作大量执行时,可能存在堵塞的情况。后面也会讲相关的处理办法。
cache: 创建非固定大小的线程池,当线程不足时,会自动创建新的线程。但是使用这种的时候需要注意,如果突然有高TPS的请求过来,方法没有及时完成,则会造成大量的线程创建,对系统的CPU和负载都是压力,执行越多反而会拖慢整个系统。
自定义线程池:1. extends FixedThreadPool implements Runnable 2. SPI声明,创建文件META-INF/dubbo/org.apache.dubbo.common.threadpool.ThreadPool: watching=包名.线程池名。3. 在服务提供方项目中设置使用该线程池生成器: dubbo.provider.threadpool=watching。
2.6 路由规则
路由是决定一次请求中需要发往目标机器的重要判断,通过对其控制可以决定请求的目标机器。我们可以通过创建这样的规则来决定一个请求会交给哪些服务器去处理。
public class DubboRouterMain { public static void main(String[] args) { RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://127.0.0.1:2181")); registry.register(URL.valueOf("condition://0.0.0.0/com.lagou.service.HelloService?category=routers&force=true&dynamic=true&rule=" + URL.encode("=> host != 你的 机器ip不能是127.0.0.1"))); } }
2.7 服务动态降级
在 dubbo 管理控制台配置服务降级屏蔽和容错。
mock=force:return+null 表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
mock=fail:return+null表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
三、源码剖析
3.1 架构整体设计

Provider: 暴露服务的服务提供方 Protocol 负责提供者和消费者之间协议交互数据;Service 真实的业务服务信息 ,可以理解成接口和实现;Container Dubbo的运行环境。
Consumer: 调用远程服务的服务消费方 Protocol 负责提供者和消费者之间协议交互数据 ;Cluster 感知提供者端的列表信息 ;Proxy 可以理解成提供者的服务调用代理类,由它接管Consumer中的接口调用逻辑。
Registry: 注册中心,用于作为服务发现和路由配置等工作,提供者和消费者都会在这里进行注册。
Monitor: 用于提供者和消费者中的数据统计,比如调用频次,成功失败次数等信息。
启动和执行流程说明: 提供者端启动,容器负责把Service信息加载并通过Protocol注册到注册中心。消费者端启动通过监听提供者列表来感知提供者信息,并在提供者发生改变时通过注册中心及时通知消费端。消费方发起请求通过Proxy模块,利用Cluster模块来选择真实的要发送给的提供者信息 。交由Consumer中的Protocol把信息发送给提供者 ,提供者同样需要通过 Protocol 模块来处理消费者的信息 。最后由真正的服务提供者Service来进行处理。
3.2 整体的调用链路

说明 :淡绿色代表了服务生产者的范围 , 淡蓝色代表了服务消费者的范围 ,红色箭头代表了调用的方向。
三层:业务逻辑层 , RPC层(远程过程调用) ,Remoting (远程数据传输)。
1. 消费者通过Interface进行方法调用 ,统一交由消费者端的Proxy。通过ProxyFactory来进行代理对象的创建,使用到了jdk 、javassist技术。
2. 交给Filter这个模块 做一个统一的过滤请求。
3. 接下来会进入最主要的Invoker调用逻辑:通过Directory去配置中读取信息,最终通过list方法获取所有的Invoker。通过Cluster模块根据选择的具体路由规则来选取Invoker列表 。通过LoadBalance模块根据负载均衡策略选择一个具体的Invoker来处理我们的请求,如果执行中出现错误并且Consumer阶段配置了重试机制则会重新尝试执行。
4. 继续经过Filter进行执行功能的前后封装, Invoker选择具体的执行协议。
5. 客户端进行编码和序列化,然后发送数据。
6. 到达Consumer中的Server,在这里进行反编码和反序列化的接收数据。
7. 使用Exporter选择执行器。
8. 交给Filter,进行一个提供者端的过滤,到达invoker执行器。
9. 通过Invoker调用接口的具体实现 ,然后返回。
3.3 Dubbo源码整体设计

说明:图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
分层介绍:
Business 业务逻辑层
service 业务层,包括我们的业务代码,比如接口、实现类、直接面向开发者。
RPC层 远程过程调用层
config 配置层 对外提供配置,以ServiceConfig ReferenceConfig为核心,可以直接初始化配置类,也可以解析配置文件生成 。
proxy 服务代理层 无论是生产者,还是消费者,框架都会产生一个代理类,整个过程对上层透明,就是业务层对远程调用无感 。
registry 注册中心层 封装服务地址的注册与发现,以服务的URL为中心
cluster 路由层 (集群容错层) 提供了多个提供者的路由和负载均衡,并且它桥接注册中心以Invoker为核心。
monitor 监控层 RPC调用相关的信息,如调用次数、成功失败的情况、调用时间等。在这一层完成protocol远程调用层,封装RPC调用,无论是服务的暴露还是服务的引用都是在Protocol中作为主功能入口。负责Invoker的整个生命周期,Dubbo中所有的模型都向Invoker靠拢。
Remoting层 远程数据传输层
exchange 信息交换层 封装请求和响应的模式,如把请求由同步转换成异步。
transport 网络传输层 统一网络传输的接口,比如netty和mina统一为一个网络传输接口 serialize 数据序列化层 负责管理整个框架中的数据传输的序列化 和反序列化。
浙公网安备 33010602011771号