深入理解Apache Dubbo 笔记(一)

深入理解Apache Dubbo 笔记(一)

1. 什么是Dubbo

Dubbo是阿里SOA服务化治理方案的核心框架,被广泛的应用到阿里的各成员站点。Dubbo是Java类项目中卓越的框架之一。他提供了注册中心机制,解耦了消费和服务方动态问题,并提供了可靠性。采用了微内核+富插件设计思想,包括框架本身都是基于扩展点实现的。

2. Dubbo解决的问题

单体架构和垂直架构无法满足需求,分布式服务框架及流动计算框架势在必行。

Dubbo解决了几个基本的问题:

  • 高性能、透明的RPC调用。
  • 服务的自动注册和发现。
  • 自动负载和容错。
  • 动态流量调度。
  • 依赖分析和调用统计。

3. Dubbo整体框架

Dubbo的总体分为业务层(Biz)、RPC层、Remote层。Service和Config可以认为是API层,主要是对外提供给API的使用者。后面的所有层级可以认为是SPI层,主要是提供给扩展者使用的,也就是基于Dubbo做二次开发,扩展其功能。

4. Dubbo总体调用过程

4.1. 服务端

​ 首先是服务端在框架启动时,初始化服务实例,通过Proxy组件的调用具体协议,把服务端暴露的接口封装成Invoker,然后转成Exporter,这个时候框架会打开服务端口等并记录服务实例到内存中,最后通过Registry把服务元数据注册到注册中心。这就是服务端整个接口暴露的过程。

组件

  • Proxy:使用过Dubbo就知道,只需要引用一个接口就可以调用远端的服务。原理是Dubbo生成了代理类,调用的方法就是调用Proxy生成的代理方法,会自动发起远程调用,并返回结果。整个过程对用户是透明的。
  • Protocol:协议就是对数据格式的一种约定,根据不同的协议转换成不同的Invoker对象。
  • Exporter:用于暴露在注册中心的对象,内部维护着一个Invoker对象,可以理解为在Invoker上包装了一层。
  • Registry:把Exporter注册到注册中心。

4.2. 客户端

​ 调用的过程是通过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的,因此在调用最终的实现类之前,又会经过一个服务端的过滤器链。

​ 最终,得到了具体接口的真实实现,并原路返回结果。

5. Java SPI 和 Dubbo SPI

为了规范开发,制定了大量的规范标准,大多是以接口的形式提供出来。例如JDBC、JNDI、Java XML Processing API、NIO Channel Provider。

5.1. Java SPI

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

5.2. Dubbo SPI

和 Java SPI 相比,Dubbo SPI 做了一些改进和优化,Java SPI 会一次性加载扩展点的所有实现,如果有扩展实现则初始化很耗时,如果没用上也加载,则浪费资源。

Dubbo的可扩展性:自动包装、自动加载、自动适应、自动激活。

Dubbo的核心概念

  • 扩展点:Dubbo作用灵活的框架,并不会强制所有用户都一定使用Dubbo提供的某些架构。例如注册中心(Registry),Dubbo提供了zk和redis,但是如果我们更倾向于其他的注册中心的话,我们可以替换掉Dubbo提供的注册中心。针对这种可被替换的技术实现点我们称之为扩展点,类似的扩展点有很多,例如Protocol,Filter,Loadbalance等等。
  • Wrapper:Dubbo在加载某个接口的扩展类时候,如果某个实现中有一个拷贝类构造函数,那么该接口实现就是该接口的包装类,此时Dubbo会在真正的实现类上层包装上盖Wrapper。即这个时候从ExtensionLoader中返回的实际扩展类是被Wrapper包装的接口实现类。
  • Adaptive:这个自适应的扩展点比较难理解,所以这里直接以一个例子来讲解:在RegistryProtocol中有一个属性为Cluster,其中Protocol和Cluster都是Dubbo提供的扩展点,所以这时候当我们真正在操作中使用cluster的时候究竟使用的哪一个cluster的实现类呢?是FailbackCluster还是FailoverCluster?Dubbo在加载一个扩展点的时候如果发现其成员变量也是一个扩展点并且有相关的set方法,就会在这时候将该扩展点设置为一个自适应的扩展点,自适应扩展点(Adaptive)会在真正使用的时候从URL中获取相关参数,来调用真正的扩展点实现类。具体的实现会在下面的源码中详细解释。对于Adaptive的理解其实个人推荐的是Dubbo开发者指南,指南中有对于Adaptive的明确介绍。
  • Activate:官网的叫法是自激活,其实这个更合适的叫法我认为是条件激活,我们还记得上一篇中有提到Filter的内容,其中Filter链的获取就是通过@Activate注解来确定的,所以Activate的作用主要是:提供一种选择性激活的条件,可以是我们通过相关的配置来确定激活哪些功能。

5.3. @SPI

@SPI 注解可以使用在类、接口、枚举类上,Dubbo框架中都是使用在接口上。主要作用就是标记为扩展点,可以有多个不同的内置或者用户自定义实现。

Dubbo很多地方都是通过getExtension(Class type , String name)来获取扩展点接口的具体实现,此时会对传入的类做效验,判断是否是接口,以及是否有@SPI注解,两者缺一不可。

5.4. @Adaptive

扩展点自适应注解,@Adaptive可以标记到类、接口、枚举类、方法上。

5.5. @Activate

扩展点自动激活注解,@Activate标记的位置和@Adaptive一样,主要使用在多个扩展点实现、需要根据不同条件被激活的场景中。如Filter需要同时被激活多个,因为每个Filter实现的是不同的功能。

5.6. ExtensionLoader

作为整个SPI的核心,ExtensionLoader起着无可替代的作用,到时候单独去谈。

6. Dubbo集群容错

http://dubbo.apache.org/zh-cn/blog/dubbo-cluster-error-handling.html

官网中:

在分布式系统中,集群某个某些节点出现问题是大概率事件,因此在设计分布式RPC框架的过程中,必须要把失败作为设计的一等公民来对待。一次调用失败之后,应该如何选择对失败的选择策略,这是一个见仁见智的问题,每种策略可能都有自己独特的应用场景。因此,作为框架来说,应当针对不同场景提供多种策略,供用户进行选择。

在Dubbo设计中,通过Cluster这个接口的抽象,把一组可供调用的Provider信息组合成为一个统一的Invoker供调用方进行调用。经过路由规则过滤,负载均衡选址后,选中一个具体地址进行调用,如果调用失败,则会按照集群配置的容错策略进行容错处理。

Dubbo默认内置了若干容错策略,如果不能满足用户需求,则可以通过自定义容错策略进行配置。

6.1. 容错策略

  • Failover(失败自动切换)
  • Failsafe(失败安全)
  • Failfast(快速失败)
  • Failback(失败自动恢复)
  • Forking(并行调用)
  • Broadcast(广播调用)
6.1.1. Failover

Failover是高可用系统中的一个常用概念,服务器通常拥有主备两套机器配置,如果主服务器出现故障,则自动切换到备服务器中,从而保证了整体的高可用性。Dubbo也借鉴了这个思想,并且把它作为Dubbo默认的容错策略。当调用出现失败的时候,根据配置的重试次数,会自动从其他可用地址中重新选择一个可用的地址进行调用,直到调用成功,或者是达到重试的上限位置。

Dubbo里默认配置的重试次数是2,也就是说,算上第一次调用,最多会调用3次。

其配置方法,容错策略既可以在服务提供方配置,也可以服务调用方进行配置。而重试次数的配置则更为灵活,既可以在服务级别进行配置,也可以在方法级别进行配置。具体优先顺序为:

服务调用方方法级配置 > 服务调用方服务级配置 > 服务提供方方法级配置 > 服务提供方服务级配置

<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="failover" retries="2" />

<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"cluster="failover">
     <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策略也会带来一些副作用:

  • 重试会额外增加一下开销,例如增加资源的使用,在高负载系统下,额外的重试可能让系统雪上加霜。
  • 重试会增加调用的响应时间。
  • 某些情况下,重试甚至会造成资源的浪费。考虑一个调用场景,A->B->C,如果A处设置了超时100ms,再B->C的第一次调用完成时已经超过了100ms,但很不幸B->C失败,这时候会进行重试,但其实这时候重试已经没有意义,因此在A看来这次调用已经超时,A可能已经开始执行其他逻辑。
6.1.2. Failsafe

失败安全策略的核心是即使失败了也不会影响整个调用流程。通常情况下用于旁路系统或流程中,它的失败不影响核心业务的正确性。在实现上,当出现调用失败时,会忽略此错误,并记录一条日志,同时返回一个空结果,在上游看来调用是成功的。

应用场景,可以用于写入审计日志等操作。

<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="failsafe" />

<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="failsafe"/>

其中服务调用方配置优先于服务提供方配置。

6.1.3. Failfast

某些业务场景中,某些操作可能是非幂等的,如果重复发起调用,可能会导致出现脏数据等。例如调用某个服务,其中包含一个数据库的写操作,如果写操作完成,但是在发送结果给调用方的过程中出错了,那么在调用发看来这次调用失败了,但其实数据写入已经完成。这种情况下,重试可能并不是一个好策略,这时候就需要使用到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"/>
6.1.4. Failback

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秒。

6.1.5. Forking

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"/>

其中服务调用方配置优先于服务提供方配置。

6.1.6. Broadcast

在某些场景下,可能需要对服务的所有提供者进行操作,此时可以使用广播调用策略。此策略会逐个调用所有提供者,只要任意有一个提供者出错,则认为此次调用出错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="broadcast" />

<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="broadcast"/>

其中服务调用方配置优先于服务提供方配置。

6.2. Directory

Directory来获取所有Invoker列表,子类可以提供静态的Invoker列表,也可以提供动态的Invoker列表。静态列表是用户自己配置的,而动态列表根据注册中心的数据动态变化。

总体的设计是模板模式,RegistryDirectoryStaticDirectory分别是动态列表实现和静态列表实现。

6.3. 路由实现

Directory获取列表的时候,会调用路由接口,路由接口会根据用户配置的不同路由策略对Invoker列表进行过滤,只返回符合规则的Invoker。

路由整体结构

分为文件路由、条件路由、脚本路由,对应dubbo-admin中三种不同的规则配置方式。

6.4. 负载均衡

Directory获取所有Invoker列表,Router根据路由规则过滤Invoker,最后幸存的Invoker还需要经过负载均衡。

特性:

  • 粘滞连接:粘滞连接用于 有状态服务,尽可能让用户端总是向同一个提供者发起调用,除非该提供者挂了,在连接另一台。
  • 可用检测:Dubbo调用的URL中,如果check=false,则不检查远程服务是否可用,如果不设置,默认开启检查。
  • 避免重复调用:对于已经调用过的服务,避免重复选择,每次都使用同一个节点。为了避免并发场景下,某个节点瞬间被大量请求。

大体步骤:

负载的算法

http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html

  • RandomLoadBalance:随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。
  • RoundRobinLoadBalance:轮询负载均衡。轮询选择一个。
  • LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
  • ConsistentHashLoadBalance:一致性哈希负载均衡。相同参数的请求总是落在同一台机器上。

配置可以达到方法级别。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值