Dubbo
基础
- Dubbo 提供了从服务定义、服务发现、服务通信到流量管控等几乎所有的服务治理能力,并且尝试从使用上对用户屏蔽底层细节,以提供更好的易用性。Dubbo 中,我们提到服务时,通常是指 RPC 粒度的、提供某个具体业务增删改功能的接口或方法
- Dubbo是一个SpringBoot项目(因此也像Spring一样作了拆分、系统耦合度非常低且提供了很多SPI和扩展接口)、通过Netty搭建线程模型;
- 点对点的服务通信是 Dubbo 提供的另一项基本能力,
- 同步的 Request-Response 是默认的通信模型
- 消费端异步请求(Client Side Asynchronous Request-Response)
- 提供端异步执行(Server Side Asynchronous Request-Response)
- 消费端请求流(Request Streaming)
- 提供端响应流(Response Streaming)
- 双向流式通信(Bidirectional Streaming)
- Dubbo通过不同的注册中心可以根据需要选择AP或者CP;Dubbo提供了很多高级特性(由于其高可扩展性,可以不断添加自己的特性)
-
非完整的系统容错支持:服务限流、服务降级、服务熔断(需要借助第三方组件)、超时服务、重试;
-
服务降级:通过mock机制进行服务降级控制;
- 可以通过设置本地存根或者自定义返回数据,决定降级的服务(熔断的服务)的熔断处理策略
-
服务熔断:可以借助Hystrix 实现,本身不支持服务熔断(因为dubbo的服务级别是方法,所以其熔断支持级别也是方法);如果使用mock机制实现,则不能区分服务的优先级,导致所有服务均会熔断;
- 通过Hystrix的熔断处理策略进行处理
-
服务限流:通过控制连接数(或者处理数),控制服务的最大并发;
-
超时服务:允许对于服务的消费者和提供者进行超时设置(超时的服务将抛出异常,一般消费者设置的时间应该略大于生产者,因为还有网络传输时间);
-
重试:**Dubbo默认重试次数为2次,当前服务调用失败后会进行两次次数;**后续处理策略就是会重试调用其它机器上的服务提供者,加上第一次调用默认是重试2次,总共调用3次。
-
-
完整的高可用支持:负载均衡、根据注册中心进行服务可用性管理;
-
负载均衡算法:
- 轮询(roundrobin);
- 一致性hash(consistenthash):相同参数的请求总是发到同一提供者。
- 当前最少并发处理的服务(LeastActive);
-
服务的可用性监控通过和注册中心实现
-
-
灰度发布:版本控制(允许给服务追加版本号,消费者根据版本号调取服务)
-
泛化机制:GenericService是Dubbo提高的全局服务接口,通常进行服务测试,在泛化调用机制下,全球和响应均为Map格式
-
支持异步调用:由于其线程模型是netty,通过netty可以实现异步处理,异步响应;
-
Dubbo 提供的基础能力包括:
- 版本控制
- 服务发现
- 流式通信
- 负载均衡
- 流量治理
- ……
![//imgs/architecture.png](https://i-blog.csdnimg.cn/blog_migrate/0c16a909ace7718ba2cbf8c9a2aed352.png)
- 正如上图:dubbo有四个基本角色:注册中心(zookeeper等)、服务消费者(通过dubbo获取注册的服务)、服务提供者(向注册中心注册)、监控(dubbo的一部分)
使用
SpringBoot
- 俩个注解:@Service、@Reference;可以通过俩者进行服务容错和高可用及其他高级特性配置
- @Service标记为dubbo服务(向注册中心注册);
- @Reference标记为获取注册中心服务;
- **yaml核心配置(**也可以使用官方文档的xml):
- application:应用级别的配置,一般配置外网地址、服务名、多注册中心配置等
-
- module:模块消息配置(没用过)
-
- registry:注册中心配置(注册中心协议、地址、端口、账号密码等)
-
- consumer:消费者配置
- provider:服务提供者配置
- monitor:监控中心配置
- protocol:协议配置(dubbo允许通过rest、dubbo、multilateral等协议进行通讯)
- application:应用级别的配置,一般配置外网地址、服务名、多注册中心配置等
dubbo特性原理
- dubbo通过SPI机制支持序列化扩展、多种协议(自定义协议)、多种注册中心(自定义注册中心)、集群扩展;
- dubbo通过动态代理+Spring的Schema机制实现:直接进行远程调用(无入侵的使用远程调用);
- dubbo通过分层:将集群容错、注册、监控、协议、传输、序列化进行解耦合,使得可以对任意一个层级进行扩展
核心原理
结构图
![image-20220402143556635](https://i-blog.csdnimg.cn/blog_migrate/c2fc45dd13a416182402761dc6adde09.png)
服务注册和发现
- Proxy:实现透明的调用
- Registry:自动的服务注册和发现;Registry和Monitor实际上不算一层,而是一个独立的节点。
- Cluster:集群的负载均衡
- Monitor:服务监视器
- Protocol:根据协议进行RPC远程过程调用
- Remoting的三层:主要为各种协议扩展的层,在JVM的RMI协议不需要Remoting
- Exchange:封装请求响应模式,同步转异步,以 Request 和 Response 为中心
- Transport:Netty网络传输层
- Serialize:序列化和反序列化
服务注册的过程
- 首先获取DubboBeanDefinition、生成Bean对象;
- 然后是将 Bean 对象转换 URL 格式,所有 Bean 属性转成 URL 的参数。
- 根据配置的协议、暴露到的注册中心的位置进行服务暴露(无配置中心,例如广播方式,则进行相应端口暴露)
服务发现的过程
- 首先根据配置解析出注册中心地址(没有则直接解析出服务的地址)
- 有注册中心到注册中心获取连接:registry://registry-host/org.apache.dubbo.registry.RegistryService?refer=URL.encode(“consumer://consumer-host/com.foo.FooService?version=1.0.0”)
- 无注册中心直接拼接得到service-host/com.foo.FooService?version=1.0.0,再和下面的协议组成完整的连接
- 然后根据协议生成URL,通过代理进行远程调用;
- 通过Netty和服务提供者建立相应的连接(如果是dubbo协议默认使用长连接)
Dubbo内核
- Dubbo的RPC主要是通过Protocol实现、Protocol是Dubbo的核心层
- Dubbo是根据调用的URL进行功能的选择实现的,根据不同的URL可以使得Dubbo动态的实现各种功能;
- Dubbo的核心除了不变的两个API层,其他均通过SPI实现高度的可扩展性;Dubbo的内核由SPI的四种机制组成:DubboSPI机制、Adaptive机制、Activate机制、Wrapper机制;
- SPI机制:Dubbo除了在Service和Config没有也不需要SPI外,其在RPC和Retoming都运行通过SPI机制自主根据URL进行扩展;
- 例如自定义注册中心Registry、则需要重新对于Registry层进行SPI扩展即可;【同理还有线程模型、序列化方式、集群的负载方式等】
- Adaptive机制:自适应扩展,由于Dubbo各层都提供了很多实现、很多时候并不需要都用到,所以Dubbo提出了自适应扩展(即在URL中需要的时候才进行类加载和初始化)
- @Adapative 修饰的 SPI 接口扩展类称为 Adaptive 类(自适应类),表示该 SPI 扩展类会按照该类中指定的方式获取,即用于固定实现方式。其是装饰者设计模式的应用。
- 被@Adapative 修饰 SPI 接口中的方法称为 Adaptive 方法。在 SPI 扩展类中若没有找到Adaptive 类,但系统却发现了 Adapative 方法,就会根据 Adaptive 方法自动为该 SPI 接口动态生成一个 Adaptive 扩展类,并自动将其编译。
- Activate机制:主动多个激活扩展机制;主要使用在有多个扩展点实现、需要同时根据不同条件被激活的场景中,如Filter需要多个同时激活,因为每个Filter实现的是不同的功能。【最常用到也是在Filter机制中】
- Wrapper机制:类似于静态代理,每一个wrapper类:
- 该类要实现 SPI 接口
- 该类中要有 SPI 接口的引用
- 必须有有参构造器,有参构造器有且只有一个参数:SPI接口
- 在接口实现方法中要调用 SPI 接口引用对象的相应方法
- 该类名称以 Wrapper 结尾
Protocol层
Protocol层主要实现类似Web的俩个功能:服务的暴露和引用、服务的过滤;这样就使得Dubbo尽管没有通过web容器提供服务,服务的暴露和引用是RPC的核心,同时尽管没有web容器但是通过Filter实现过滤和安全机制;
服务暴露和引用
-
Protocol层是Dubbo的核心层,是实现RPC的关键(服务的暴露和服务引用);
-
@SPI("dubbo") public interface Protocol { /** * 未配置端口默认端口 */ int getDefaultPort(); /** * 用于远程调用的暴露接口,也就是Invoker转化成Exporter的接口 * 1.协议在收到请求后要记录请求源地址RpcContext.getContext().setRemoteAddress(); * 2.export()必须是幂等的,即导出同一个URL时调用一次和调用两次没有区别 * 3.Invoker 实例是框架传入的,协议不用管 * @param <T> Service type 接口类型 * @param invoker Service invoker 接口类型转成URL再转换成的Invoker */ @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; /** * 引用远程服务 * 1.当用户调用从`refer()`调用返回的`Invoker`对象的`invoke()`方法时,协议需要对应执行`Invoker`对象的`invoke()`方法 * 2. 协议的责任是实现从 `refer()` 返回的 `Invoker`。 一般来说,协议在 `Invoker` 实现中发送远程请求。 * 3、当URL中设置了check=false时,实现一定不要抛出异常,而是在连接失败时尝试恢复。 */ @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; /** * 销毁协议: * 1.取消本协议导出和引用的所有服务 * 2.释放所有占用的资源,例如:连接、端口等。 * 3. 协议被销毁后仍可以继续导出和引用新服务 */ void destroy(); }
-
Filter机制
- Filter接口是Dubbo的核心过滤器接口,实现该接口的类通过加入Dubbo的SPI文件;
- Dubbo运行通过yaml配置文件、xml配置文件或者@Adaptive方式使得配置生效;
- 在<dubbo:service filter=“”/>或<dubbo:reference filter=“”/>标签中使用
filter
属性来指定具体的filter名称,这种使用方式的作用级别只针对于所指定的某个provider或consumer; - 在<dubbo:provider filter=“”/>或<dubbo:consumer filter=“”/>标签中使用
filter
属性来指定具体的filter名称,这种使用方式的作用级别针对于所有的provider或consumer; - 在所声明的实现了
Filter
接口的类上使用@Activate
注解来标注,并且注解中的group
属性指定为provider
或consumer
。
- Dubbo在
ProtocolFilterWrapper::buildInvokerChain
将生效的过滤器链组织起来; - 在Filter实现类中可以通过Setter方式注入bean;
SPI&Adaptive机制
SPI机制
- 在Spring的时候我们知道JAVA的SPI机制是通过ServerLoad调用线程的类加载器进行类加载,而且一次加载在全部SPI注入的实现类(META-INF/services/所有文件【文件名为接口全限定名、内容为各个实现全限定类名】)
- Spring对于SPI的改进则是通过spring.factories进行SPI配置不再需要么个接口一个文件,同时实现了通过SpringFactoriesLoader获取指定类型的实现类全限定类名,调用方进行类加载,不需要全部类进行类加载;
- dubbo的SPI机制和Spring的类似,可以按需加载,而且支持AOP和IOC、缓存机制等,实现了自适应扩展;
- 提供者配置文件路径:在依次查找的目录为
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
- 自适应扩展:Dubbo中存在很多的扩展类,这些扩展类不可能一开始就全部初始化,那样非常的耗费资源,所以我们应该在使用到该类的时候再进行初始化,在java语言中,拓展(类\方法)未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载,所以这种思路基于直接创建对象的角度是矛盾的,所以需要一种机制实现(自适应扩展)
- 通过**@SPI、@Adaptive、@Activate对于URL进行自适应扩展**
-
- 提供者配置文件路径:在依次查找的目录为
@Adaptive使用
@Adaptive
:可以标记在方法上、也可以标记在类上;- 标记在类上的时候,将使得扩展点的实现类只能是标记类
- 标记在方法上时,将根据URL传入的参数选择实现类、只有URL没有传入该参数的时候选择
@SPI
标记的默认实现类
例子
- 扩展点及其实现类
//扩展类
@SPI("default")
public interface AdaptiveTest {
//自适应扩展点,如果未指定参数名则使用 驼峰命名的点分形式
//可以通过@Adaptive的value指定参数命名
@Adaptive
String adp(String msg, URL url);
}
//@SPI标记的默认实现类
public class DefaultAdaptiveTest implements AdaptiveTest {
@Override
public String adp(String msg, URL url) {
return "default";
}
}
//通过url选择的扩展点实现类
public class OneAdaptiveTest implements AdaptiveTest {
@Override
public String adp(String msg, URL url) {
return "11111";
}
}
public class TwoAdaptiveTest implements AdaptiveTest {
@Override
public String adp(String msg, URL url) {
return "22222";
}
}
//指定的扩展点实现类
@Adaptive
@Setter
@Getter
public class MyAdaptiveTest implements AdaptiveTest {
String urlChoose;
@Override
public String adp(String msg, URL url) {
//为了遵循修饰器模式的理念,@Adaptive实现类一般用于选择真正的实现类
//下面为了演示,直接返回"指定"
ExtensionLoader<AdaptiveTest> loader =
ExtensionLoader.getExtensionLoader(AdaptiveTest.class);
AdaptiveTest choose;
if(StringUtils.isEmpty(urlChoose)) {
choose = loader.getDefaultExtension();
} else {
choose = loader.getExtension(urlChoose);
}
//增强
System.out.print("指定");
return choose.apt(msg,url);
}
}
- 注册扩展点实现类
- 文件名:接口全限定名
#在没有@Adaptive类标注类前提下
#该类被扩展点@SPI(default)标记,所以没有参数或者传入参数为adaptive.test=default就会选择该实现类
default=XXXXXXX.DefaultAdaptiveTest
#如果url中带有参数:adaptive.test=one 则选择这个实现类
one=XXXXXXX.OneAdaptiveTest
#如果url中带有参数:adaptive.test=two 则选择这个实现类
two=XXXXXXX.TwoAdaptiveTest
#因为my是一个@Adaptive类,所以主要该类注册,就只会选择这个实现类
my=XXXXXXX.MyAdaptiveTest
@Activate使用
核心组件
- 在开始深入学习各个组件前,我们在使用Dubbo时仅仅只需要一个接口即可,显然需要一个代理帮助我们调用进行远程调用、数据封装和解析;
下面就通过JAVA的API注册和获取服务的角度学习各个组件: