Dubbo笔记
Dubbo 概念
Apache Dubbo是一款高性能的Java RPC框架,其前身是阿里巴巴公司开源的一个高性能,轻量级的开源Java RPC框架。面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现
RPC
Remote Procedure Call Protocol
远程过程调用协议
RPC解析:客户端通过互联网调用远程服务器,不知道远程服务器具体实现,
只知道远程服务器提供了什么功能
优点
Java中RPC框架比较多常见的有RMI、Hessian、GRPC、bRPC 、Dubbo
Dubbo的核心点
- Provider 提供者,服务发布方
- Consumer 消费者,调用服务方
- Container Dubbo容器,依赖于Spring容器
- Register 注册中心,当Container启动时把所有可以提供的服务列表上Registry中进行注册 [作用:告诉Consumer提供了什么服务和服务方在哪里]
Dubbo高可用
- 集群容错
- 负责均衡
- RandomLoadBalance 按照权重设置随机概率,无状态
- RoundRobinLoadBalance 轮询,有状态
- LeastActiveLoadBalance 最小活跃数随机,方法维度的统一服务调用数
- ConsistenHashLoadBalance 一致性Hash
Dubbo的IO模型
NIO
Dubbo的原理
- config配置层:对外配置接口,以ServiceConfig,ReferenceConfig为中心,可以直接初始化配置类,也可以通过Spring解析配置生成配置类
- proxy服务代理层:服务接口透明代理,生产服务的客户端Stub和服务端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory
- registry注册中心层:封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory,Registry,RegistryService
Dubbo SPI
SPI Service Provider Interface 是一种服务发现机制。SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件
加载实现类。这样就可以在运行时,动态为接口替换实现类。
Java SPI的问题
- 接口的所有实现类全部都需要加载并实例化
- 无法根据参数来获取对应的实现类
- 不能解决AOP、IOC的问题
Dubbo服务引入
Dubbo服务引用的时机有两个,第一个是在Spring容器中调用ReferenceBean的afterProoertiesSet方法时引用服务,第二个是在ReferenceBean对应的服务被注入到其他类中时引用。这两个引用服务的时机在于第一个饿汉式,第二个懒汉式。默认情况需要使用饿汉式,可通过配置dubbo:reference的init属性开启
Dubbo Monitor监听器
- 1、启动容器,相当于在启动Dubbo的Provider
- 2、启动后会去注册中心进行注册,注册所有可以提供的服务列表
- 3、在Consumer启动后会去Register中获取服务列表和Provider的地址,进行订阅
- 4、当Provider有修改后,注册中心会把消息推送给Consumer,用了观察者设计模式(又叫发布/订阅设计模式)
- 5、根据获取到的Provider地址,真实调用Provider中功能
- 在Consumer方使用了代理设计模式。创建一个Provider方类的一个代理对象。
- 通过代理对象获取Provider中真实功能,起到Provider真实功能的作用。
- 6、Consumer和Provider每隔1分钟向Monitor 发送统计信息,统计信息包含访问次数
频率等。
Dubbo支持的注册中心
- 1、Zookeeper 支持网络集群,但是依赖于Zookeeper的稳定性
- 2、Redis注册中心 性能高,但是对服务器环境要求较高
- 3、Multicast注册中心 去中心化,不需要额外安装,但是建议同机房(局域网)
- 4、Simple注册中心 适用于测试,不支持集群
Dubbo支持的协议
- Dubbo协议 官方推荐协议
- 本质:使用NIO和线程池进行处理
- 缺点:大文件传输时可能出现文件传输失败的问题
- RMI协议 JDK提供的协议,远程调用协议
- 缺点:偶尔连接失败
- 优点:JDK原生,不需要进行额外配置
- Hessian协议
- 优点:基于Http协议,http请求支持
- 缺点:需要额外导入jar,并在短链接时性能低
Dubbo面试题
Dubbo的容错机制
- 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数
- 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。
- 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
Dubbo框架设计结构
- 服务接口层:该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
- 配置层:对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。
- 服务代理层:服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。
- 服务注册层:封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。
- 集群层:封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。
- 监控层:RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
- 远程调用层:封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
- 信息交换层:封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
- 网络传输层:抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
- 数据序列化层:可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。
Dubbo提供的线程池
- fixed:固定大小线程池,启动时建立线程,不关闭,一直持有。
- cached:缓存线程池,空闲一分钟自动删除,需要时重建。
- limited:可伸缩线程池,但池中的线程数只会增长不会收缩。(为避免收缩时突然来了大流量引起的性能问题)。
Dubbo注册中心挂了还可以继续通信么
可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信。
Dubbo 的分层
从大的范围来说,Dubbo 分为三层:Business 业务逻辑层由我们自己来提供接口和实现,还有一些配置信息。RPC 层就是真正的 RPC 调用的核心层,封装整个 RPC 的调用过程、负载均衡、集群容错、代理。Remoting 则是对网络传输协议和数据转换的封装。
划分到更细的层面,就是图中的10层模式,整个分层依赖由上至下,除 Business业务逻辑之外,其他的几层都是 SPI 机制。
分层 |
具体 |
描述 |
Business |
service |
业务逻辑层,提供接口实现 |
RPC |
config |
配置层,用于初始化配置信息 |
RPC |
proxy |
代理层,提供consumer和provider的代理 |
RPC |
register |
服务注册层,封装服务地址的注册和发现 |
RPC |
cluster |
路由层,封装provider路由和负载均衡 |
RPC |
monitor |
监控层,提供RPC调用时间和次数监控 |
RPC |
protocol |
远程调用层,封装RPC调用 |
Remoting |
exchange |
信息交换层,用于封装请求响应模式,同步转异步 |
Remoting |
transport |
网络传输层,对netty和mina的封装 |
Remoting |
serialize |
序列化层,提供数据的序列化和反序列化 |
Dubbo的工作原理
1、服务启动的时候,provider和consumer根据配置信息,连接到注册中心register,分别向注册中心注册和订阅服务;
2、register 根据服务订阅关系,返回 provider 信息到 consumer,同时 consumer 会把 provider 信息缓存到本地。如果信息有变更,consumer 会收到来自 register 的推送;
3、consumer 生成代理对象,同时根据负载均衡策略,选择一台provider,同时定时向 monitor 记录接口的调用次数和时间信息;
4、拿到代理对象之后,consumer 通过代理对象发起接口调用;
5、provider 收到请求后对数据进行反序列化,然后通过代理调用具体的接口实现。
为什么要通过代理对象通信
主要是为了实现接口的透明代理,封装调用细节,让用户可以像调用本地方法一样调用远程方法,同时还可以通过代理实现一些其他的策略,比如:
- 调用的负载均衡策略;
- 调用失败、超时、降级和容错机制;
- 做一些过滤操作,比如加入缓存、mock 数据;
- 接口调用数据统计。
说说服务暴露的流程
- 在容器启动的时候,通过 ServiceConfig 解析标签,创建 dubbo 标签解析器来解析 dubbo 的标签。容器创建完成之后,触发 ContextRefreshEvent 事件回调开始暴露服务;
- 通过 ProxyFactory 获取到 invoker。invoker 包含了需要执行的方法的对象信息和具体的 URL 地址;
- 再通过 DubboProtocol 的实现把包装后的 invoker 转换成 exporter,然后启动服务器 server,监听端口;
- 最后 RegistryProtocol 保存 URL 地址和 invoker 的映射关系,同时注册到服务中心。
容器启动 —> 解析配置文件 —> 触发ContextRefreshEvent —> 创建invoker —> 转换exporter —> 开启server —> 服务中心注册
服务引用的流程
服务暴露之后,客户端就要引用服务,然后才是调用的过程。
- 首先,客户端根据配置文件信息从注册中心订阅服务;
- 之后,DubboProtocol 根据订阅的得到 provider 地址和接口信息连接到服务端 server,开启客户端 client,然后创建 invoker;
- invoker 创建完成之后,通过 invoker 为服务接口生成代理对象。这个代理对象用于远程调用 provider,服务的引用就完成了。
容器启动 —> 订阅服务 —> 开启client —> 创建invoker —> 创建代理对象
有哪些负载均衡策略
- 加权随机:假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上就可以了;
- 最小活跃数:每个服务提供者对应一个活跃数 active,初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求;
- 一致性 hash:通过 hash 算法,把 provider 的 invoke 和随机节点生成 hash,并将这个 hash 投射到 [0, 2^32 - 1] 的圆环上。查询的时候根据 key 进行 md5 然后进行 hash。得到第一个节点的值大于等于当前 hash 的 invoker。
- 加权轮询:比如服务器 A、B、C 权重比为 5:2:1,那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。
集群容错方式有哪些
- Failover Cluster 失败自动切换:Dubbo 的默认容错方案,当调用失败时自动切换到其他可用的节点。具体的重试次数和间隔时间可用通过引用服务的时候配置,默认重试次数为1也就是只调用一次;
- Failback Cluster 快速失败:在调用失败,记录日志和调用信息,然后返回空结果给 consumer,并且通过定时任务每隔5秒对失败的调用进行重试;
- Failfast Cluster 失败自动恢复:只会调用一次,失败后立刻抛出异常;
- Failsafe Cluster 失败安全:调用出现异常,记录日志不抛出,返回空结果;
- Forking Cluster 并行调用多个服务提供者:通过线程池创建多个线程,并发调用多个 provider,结果保存到阻塞队列,只要有一个 provider 成功返回了结果,就会立刻返回结果;
- Broadcast Cluster 广播模式:逐个调用每个 provider,如果其中一台报错,在循环调用结束后,抛出异常。
了解 Dubbo SPI 机制吗
SPI 全称为 Service Provider Interface,是一种服务发现机制。本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类,这样可以在运行时,动态为接口替换实现类。
Dubbo 也正是通过 SPI 机制实现了众多的扩展功能,而且 Dubbo 没有使用 Java 原生的 SPI 机制,而是对其进行了增强和改进。
SPI 在 Dubbo 应用很多,包括协议扩展、集群扩展、路由扩展、序列化扩展等等。
使用方式可以在 META-INF/dubbo 目录下配置:
key=com.xxx.value
然后通过 Dubbo 的 ExtensionLoader 按照指定的 key 加载对应的实现类,这样做的好处就是可以按需加载,性能上得到优化。