Dubbo是阿里SOA服务化治理方案的核心框架,被广泛的应用到阿里的各成员站点。Dubbo是Java类项目中卓越的框架之一。他提供了注册中心机制,解耦了消费和服务方动态问题,并提供了可靠性。采用了微内核+富插件设计思想,包括框架本身都是基于扩展点实现的。
单体架构和垂直架构无法满足需求,分布式服务框架及流动计算框架势在必行。
Dubbo解决了几个基本的问题:
Dubbo的总体分为业务层(Biz)、RPC层、Remote层。Service和Config可以认为是API层,主要是对外提供给API的使用者。后面的所有层级可以认为是SPI层,主要是提供给扩展者使用的,也就是基于Dubbo做二次开发,扩展其功能。
首先是服务端在框架启动时,初始化服务实例,通过Proxy组件的调用具体协议,把服务端暴露的接口封装成Invoker,然后转成Exporter,这个时候框架会打开服务端口等并记录服务实例到内存中,最后通过Registry把服务元数据注册到注册中心。这就是服务端整个接口暴露的过程。
组件:
调用的过程是通过Proxy开始的,Proxy持有一个Invoker对象,然后出发Invoke调用。在Invoke调用的过程中,需要使用Cluster,Cluster负责容错,如调用失败的重试。Cluster在调用之前会通过Directory获取所有可以调用的远程服务Invoker列表。由于调用的远程服务很多,所以要配置路由规则,还会根据路由规则过滤一遍Invoker列表。
存活下来的Invoker还有很多,所以通过LoadBalance来做负载均衡,最终选出一个可以调用的Invoker,这个Invoker在调用之前会经过一个过滤链,处理上下文、限流、计数。
之后,Client会做一个传输,如Netty Client等。传输之前做一些私有协议的构造,这时候会用到Codec接口。构造完成后,对数据进行序列化,然后传输到服务端,服务端收到数据包后,也使用Codec处理协议、半包、粘包等。处理完成后在对完整的数据包做反序列化处理。
随后,Request会被分配到ThreadPool中进行处理,Sever会处理这些Request,根据请求查找对应的Exporter。Invoker是被装饰器模式一层一层套了非常多的Filter的,因此在调用最终的实现类之前,又会经过一个服务端的过滤器链。
最终,得到了具体接口的真实实现,并原路返回结果。
为了规范开发,制定了大量的规范与标准,大多是以接口的形式提供出来。例如JDBC、JNDI、Java XML Processing API、NIO Channel Provider。
Java SPI 采用的是策略模式,一个接口多种实现,只声明接口,具体的实现并不在程序中直接确定,而是由程序之外的配置掌控,用于实现装配。
package com.test.spi;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/8/27 20:18
*/
public interface PrintService {
void printInfo() ;
}
package com.test.spi;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/8/27 20:18
*/
public class PrintServiceImpl implements PrintService {
@Override
public void printInfo() {
System.out.println("PrintServiceImpl spi");
}
}
package com.test.spi;
import java.util.ServiceLoader;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/8/27 20:20
*/
public class PrintServiceSPIDemo {
public static void main(String[] args) {
ServiceLoader<PrintService> serviceServiceLoader = ServiceLoader.load(PrintService.class) ;
for (PrintService service : serviceServiceLoader) {
service.printInfo() ;
}
}
}
resources包下 创建 META-INF/services/com.test.spi.PrintService文件,内容如下:
test.spi.PrintServiceImpl
运行结果:
PrintServiceImpl spi
和 Java SPI 相比,Dubbo SPI 做了一些改进和优化,Java SPI 会一次性加载扩展点的所有实现,如果有扩展实现则初始化很耗时,如果没用上也加载,则浪费资源。
Dubbo的可扩展性:自动包装、自动加载、自动适应、自动激活。
Dubbo的核心概念
@SPI 注解可以使用在类、接口、枚举类上,Dubbo框架中都是使用在接口上。主要作用就是标记为扩展点,可以有多个不同的内置或者用户自定义实现。
Dubbo很多地方都是通过getExtension(Class type , String name)来获取扩展点接口的具体实现,此时会对传入的类做效验,判断是否是接口,以及是否有@SPI注解,两者缺一不可。
扩展点自适应注解,@Adaptive可以标记到类、接口、枚举类、方法上。
扩展点自动激活注解,@Activate标记的位置和@Adaptive一样,主要使用在多个扩展点实现、需要根据不同条件被激活的场景中。如Filter需要同时被激活多个,因为每个Filter实现的是不同的功能。
作为整个SPI的核心,ExtensionLoader起着无可替代的作用,到时候单独去谈。
http://dubbo.apache.org/zh-cn/blog/dubbo-cluster-error-handling.html
官网中:
在分布式系统中,集群某个某些节点出现问题是大概率事件,因此在设计分布式RPC框架的过程中,必须要把失败作为设计的一等公民来对待。一次调用失败之后,应该如何选择对失败的选择策略,这是一个见仁见智的问题,每种策略可能都有自己独特的应用场景。因此,作为框架来说,应当针对不同场景提供多种策略,供用户进行选择。
在Dubbo设计中,通过Cluster这个接口的抽象,把一组可供调用的Provider信息组合成为一个统一的
Invoker
供调用方进行调用。经过路由规则过滤,负载均衡选址后,选中一个具体地址进行调用,如果调用失败,则会按照集群配置的容错策略进行容错处理。Dubbo默认内置了若干容错策略,如果不能满足用户需求,则可以通过自定义容错策略进行配置。
Failover
是高可用系统中的一个常用概念,服务器通常拥有主备两套机器配置,如果主服务器出现故障,则自动切换到备服务器中,从而保证了整体的高可用性。Dubbo也借鉴了这个思想,并且把它作为Dubbo默认的容错策略
。当调用出现失败的时候,根据配置的重试次数,会自动从其他可用地址中重新选择一个可用的地址进行调用,直到调用成功,或者是达到重试的上限位置。
Dubbo里默认配置的重试次数是2,也就是说,算上第一次调用,最多会调用3次。
其配置方法,容错策略既可以在服务提供方配置,也可以服务调用方进行配置。而重试次数的配置则更为灵活,既可以在服务级别进行配置,也可以在方法级别进行配置。具体优先顺序为:
服务调用方方法级配置 > 服务调用方服务级配置 > 服务提供方方法级配置 > 服务提供方服务级配置
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="failover" retries="2" />
<dubbo:method name="sayHello" retries="2" />
dubbo:reference>
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="failover" retries="1"/>
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="failover">
<dubbo:method name="sayHello" retries="3" />
dubbo:reference>
Failover可以自动对失败进行重试,对调用者屏蔽了失败的细节,但是Failover策略也会带来一些副作用:
失败安全策略的核心是即使失败了也不会影响整个调用流程。通常情况下用于旁路系统或流程中,它的失败不影响核心业务的正确性。在实现上,当出现调用失败时,会忽略此错误,并记录一条日志,同时返回一个空结果,在上游看来调用是成功的。
应用场景,可以用于写入审计日志等操作。
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="failsafe" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="failsafe"/>
其中服务调用方配置优先于服务提供方配置。
某些业务场景中,某些操作可能是非幂等的,如果重复发起调用,可能会导致出现脏数据等。例如调用某个服务,其中包含一个数据库的写操作,如果写操作完成,但是在发送结果给调用方的过程中出错了,那么在调用发看来这次调用失败了,但其实数据写入已经完成。这种情况下,重试可能并不是一个好策略,这时候就需要使用到Failfast
策略,调用失败立即报错。让调用方来决定下一步的操作并保证业务的幂等性。
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="failfast" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="failfast"/>
Failback
通常和Failover
两个概念联系在一起。在高可用系统中,当主机发生故障,通过Failover
进行主备切换后,待故障恢复后,系统应该具备自动恢复原始配置的能力。
Dubbo中的Failback
策略中,如果调用失败,则此次失败相当于Failsafe
,将返回一个空结果。而与Failsafe
不同的是,Failback策略会将这次调用加入内存中的失败列表中,对于这个列表中的失败调用,会在另一个线程中进行异步重试,重试如果再发生失败,则会忽略,即使重试调用成功,原来的调用方也感知不到了。因此它通常适合于,对于实时性要求不高,且不需要返回值的一些异步操作。
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="failsafe" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="failsafe"/>
按照目前的实现,Failback策略还有一些局限,例如内存中的失败调用列表没有上限,可能导致堆积,异步重试的执行间隔无法调整,默认是5秒。
Forking
策略则跟上述几种策略不同,是一种典型的用成本换时间的思路。即第一次调用的时候就同时发起多个调用,只要其中一个调用成功,就认为成功。在资源充足,且对于失败的容忍度较低的场景下,可以采用此策略。
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="forking" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="forking"/>
其中服务调用方配置优先于服务提供方配置。
在某些场景下,可能需要对服务的所有提供者进行操作,此时可以使用广播调用策略。此策略会逐个调用所有提供者,只要任意有一个提供者出错,则认为此次调用出错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="broadcast" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="broadcast"/>
其中服务调用方配置优先于服务提供方配置。
Directory来获取所有Invoker列表,子类可以提供静态的Invoker列表,也可以提供动态的Invoker列表。静态列表是用户自己配置的,而动态列表根据注册中心的数据动态变化。
总体的设计是模板模式,RegistryDirectory
和StaticDirectory
分别是动态列表实现和静态列表实现。
Directory获取列表的时候,会调用路由接口,路由接口会根据用户配置的不同路由策略对Invoker列表进行过滤,只返回符合规则的Invoker。
路由整体结构
分为文件路由、条件路由、脚本路由,对应dubbo-admin中三种不同的规则配置方式。
Directory获取所有Invoker列表,Router根据路由规则过滤Invoker,最后幸存的Invoker还需要经过负载均衡。
特性:
大体步骤:
负载的算法
http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html
配置可以达到方法级别。