Dubbo学习

DUBBO生态

RPC核心

集群容错 failover ,failsafe , failfast,forking,failback

负载均衡 random,roundRobin,leastactive,consistenthash

协议 dubbo hessian,rmi ,http,ws,thrift ,rest

传输 netty ,mina ,jetty:// , grizzly ,p2p

序列化 hessian2 , json (fastjson) , kryo ,FST

rpc和http

  • http1.0,1.1,2.0

    • 1.0是短连接

      每次请求都要一次tcp连接

    • 1.1长连接

    • 2.0 长连接+io多路复用(五大io模型之一)

  • 性能

    • 主要性能差别在序列化和反序列化
    • rpc一般是基于二进制的 thrift , hessian2,http 是json或者xml
  • 传输协议

    • rpc基于tcp或者http
    • http只能是http
  • 负载均衡

    • rpc自带
    • http需要结合nginx等
  • 传输效率

    • rpc可以自定义报文协议,报文比较小
    • http协议特别是老版本,很多无用且会重复传输的东西(Header,keepalive time ,reffer ...)
  • 通知

    • rpc自动通知
    • http需要自行配置
  • 场景

    • 系统对外一般是http
    • rpc不太适合传输图片等
    • 如果是公司内部上下游系统之间传输还可以使用MQ

服务治理

注册中心 (zk,nacos,etcd,consul)

配置中心 (zk,nacos,etcd,consul)

元数据中心(redis)

指标 (dubbo metrics)

断路器(sentinel , hystrix ,resilience4j)

管理控制台(dubbo admin)

微服务组件

OpenAPI (Swagger)

api 网关 (Kong,dubbo proxy, netflix 's zuul )

可靠性(sentinel , hystrix ,resilience4j)

事务 ( seata)

授权(Oauth)

事件 ( RocketMQ)

调度

可观测性

监控 Prometheus

追踪 zipkin ,opentracing ,pinpoint

日志 es ( elasticseach)

诊断 arthas

2.7升级特性

异步编程模型 - 消费端/提供端异步
服务治理规则增强
简化的注册模型
配置中心、元数据中心
package 重构

DUBBO SPI

getExtension流程

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。这其中,getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。该方法的逻辑比较简单,本章就不进行分析了。下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {
        // 获取默认的拓展实现类
        return getDefaultExtension();
    }
    // Holder,顾名思义,用于持有目标对象
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    // 双重检查
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 创建拓展实例
                instance = createExtension(name);
                // 设置实例到 holder 中
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}
// 上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。

private T createExtension(String name) {
    // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            // 通过反射创建实例
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 向实例中注入依赖
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            // 循环创建 Wrapper 实例
            for (Class<?> wrapperClass : wrapperClasses) {
                // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                instance = injectExtension(
                    (T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("...");
    }
}

/* createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

通过 getExtensionClasses 获取所有的拓展类
通过反射创建拓展对象
向拓展对象中注入依赖
将拓展对象包裹在相应的 Wrapper 对象中
以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。在接下来的章节中,将会重点分析 getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现
*/
 

getExtension-> getExtensionClasses-> loadExtensionClasses ->loadDirectory -> loadResource->loadClass

自己扩展 dubbo 中的组件

自己写个工程,要是那种可以打成 jar 包的,里面的 src/main/resources 目录下,搞一个 META-INF/services ,里面放个文件叫: com.alibaba.dubbo.rpc.Protocol ,文件里搞一个 my=com.bingo.MyProtocol 。自己把 jar 弄到 nexus 私服里去。

然后自己搞一个 dubbo provider 工程,在这个工程里面依赖你自己搞的那个 jar,然后在 spring 配置文件里给个配置:

<dubbo:protocol name=”my” port=”20000” />

provider 启动的时候,就会加载到我们 jar 包里的 my=com.bingo.MyProtocol 这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。

dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 key=实现类

然后对于对应的组件,类似 `` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。

Dubbo IOC

目前仅支持 setter 方式注入

Dubbo集群

cluster 接口 , cluster invoker

集群 Cluster 用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者

集群模块是消费者和服务提供者的中间层

集群组件

cluster , cluster invoker , directory , router , loadbalance

集群工作过程可分为两个阶段

第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例

cluster - - ( merge 操作) --> [cluster]invoker

第二个阶段是在服务消费者进行远程调用时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。

Directory 的用途是保存 Invoker,可简单类比为 List。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。

每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoke 方法,进行真正的远程调用。

集群容错

Failover 失败自动切换

Failover Cluster,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)

<dubbo:reference retries="2" />
<dubbo:reference>
    <dubbo:method name="findFoo" retries="2" /> 
</dubbo:reference>
 <dubbo:service retries="2" />

  #### Failfast Cluster

  快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

Failsafe Cluster

  失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

  #### Failback Cluster

  失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

Forking Cluster

  并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数

  #### Broadcast Cluster

  广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。

整合Hystrix

  • maven spring-cloud-starter-netflix-hystrix
  • 服务端 @EnableHystrix , @HystrixCommand
  • 消费端 @EnableHystrix , @HystrixCommand(fallbackMethod="foo")

负载均衡

职责是将网络请求均摊到服务器 软负载硬负载*,开发中接触的基本是软负载 .

Dubbo的4种负载均衡

   服务端服务级别
    <dubbo:service interface="..." loadbalance="roundrobin" />

  客户端服务级别
    <dubbo:reference interface="..." loadbalance="roundrobin" />

  服务端方法级别
    <dubbo:service interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:service>

  客户端方法级别
    <dubbo:reference interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:reference>
  • 权重随机 RandomLoadBalance

    • 默认机制
    • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
  • 加权轮询 RoundRobinLoadBalance

    • 总体上是轮询的顺序,可以理解成在权重比的指导下依次按轮询顺序塞满服务器请求
    • 按公约后的权重设置轮循比率。存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上
  • 最少活跃调用 LeastActiveLoadBalance

    • 活跃数指调用前后计数差

    • 总是挑一个最快的服务器相同活跃数的随机

    • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大

  • 一致性hash ConsistentHashLoadBalance

    • 服务器不出现故障的情况下,相同参数请求会落到同一服务器上

    • 所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况

    • 为了避免数据倾斜问题,dubbo引入虚拟节点,默认有160个虚拟节点 , 比如 Invoker1-1,Invoker1-2,……, Invoker1-160

    • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

      • 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
      • 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />
      • 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />

Dubbo服务多版本

从架构哪个角度考虑设计的

  • 扩展性
  • 可用性

发生在哪个阶段

  • 升级重构

长什么样子

<dubbo:service  id = "versionService" …… version = 1.0 >
<dubbo:reference  id = "versionService" …… version = 1.0 >

Dubbo 服务降级

  • 服务降级 是从架构的 可用性和稳定性考虑的方案
  • 服务降级是从客户端配置的

屏蔽降级

  • 为了保证核心服务的可用性,高性能。在业务高峰期间暂停掉一些不太重要的服务
  • eg. 商品评论,论坛,退款等

容错降级

  • 某些服务因某种原因不可用时,但是整个调用流程不能直接终止,需要本地(调用端)模拟服务端实现保证流程正常执行

Mock配置服务降级

public class ServiceAMock implements ServiceA {
  public String  method(String param){
        return "mock" + param;
    }
}
<dubbo:reference id="serviceA" mock = "true" interface = "....ServiceA"/>
## Dubbo服务分组

* 提高扩展性
* 可以针对性地提高性能

​~~~xml
<dubbo:service  id = "versionService" …… group = "groupA" >
<dubbo:reference  id = "versionService" …… group = "groupA" >

Dubbo微服务核心流程

  1. 定义服务接口:暴露给外部服务的接口
  2. 实现接口,定义服务实现类
  3. 服务端配置 ,把服务注册到【注册中心】
  4. 客户端配置 ,注册地址,声明引用bean

与SpringBoot整合的三种方式

  1. @EnableDubbo (开启基于注解的配置方式)

    • 没有dubbo.xml只有application.properties

    • dubbo-starter

    • @Service , @Reference

  2. dubbo-starter + dubbo.xml + @importResource(locations = )

  3. 使用注解API的方式

    • 没有了xml文件,也可以详细针对method进行配置

    • @Configuration 标记配置类

    • ApplicationConfig,RegistryConfig , ServiceConfig , ProviderConfig,MethodConfig…… (每个标签对应一个配置类@Bean)

Zookeeper宕机

  • dubbo直连
  • 本地缓存

异步调用

  1. NIO future主动获取结果,返回结果放在RpcContext中
  //需要注意的是,由于RpcContext是单例模式,所以每次调用完后,需要保存一个Future实例;如:

      fooService.findFoo(fooId);
      Future<Foo> fooFuture = RpcContext.getContext().getFuture();
      barService.findBar(barId);
      Future<Bar> barFuture = RpcContext.getContext().getFuture();
      barService.findBar(barId);
      Bar bar = barFuture.get();
  1. 通过回调(Callback)参数

    Callback并不是dubbo内部类或接口,而是由应用自定义的、实现了Serializable的接口;

    分两步:1)服务提供者需在方法中声明Callback参数,其后在Service实现中显示地调用Callback的方法;

       <dubbo:service ..>
         <dubbo:method name="method1">
          <dubbo:argument index="1" callback="true" /> #标识第二个参数是callback类型
         </dubbo:method>
       </dubbo:service>
    

    2)Callback接口的实现类在消费端,当方法发生调用时,消费端会自动export一个Callback服务,在Rpc调用完成后,不能立即结束线程。

     <dubbo:reference ...>
         <dubbo:method name="method1" async="true">
       </dubbo:reference>
    

    3、事件通知(推荐)

    这种方式更简单,对服务提供方来讲是透明的,包括配置和代码上,均无需做任何改动。

    消费端定义一个“通知者”的Spring Bean,指定方法的onreturn和onthrow事件action就可以。

    <bean id="notify" class="com.alibaba.dubbo.callback.implicit.NofifyImpl" />
       <dubbo:reference >
    <dubbo:method name="method1" async="true" onreturn="notify.onreturn" onthrow="notify.onthrow" />
       </dubbo:reference>
    

泛化调用

存在一些情况,例如客户端没有 jar 包,或者是跨语言的调用,这个时候,就需要客户端使用字符串进行泛化调用

还是根据官方的例子来看一下:SOFA rpc 

ConsumerConfig<GenericService> consumerConfig = new ConsumerConfig<GenericService>()
           .setInterfaceId("com.alipay.sofa.rpc.quickstart.HelloService")
           .setGeneric(true);
GenericService testService = consumerConfig.refer();

String result = (String) testService.$invoke("sayHello", new String[] { "java.lang.String" },new Object[] { "1111" });
我们看到,实际上,设置接口 ID 是和普通的调用时类似的,只是需要增加一个 Generic 属性为 true。

然后就返回了一个 GenericService 类型的代理对象,通过这个对象,就可以对服务发起调用,而调用的方式,则是使用 GenericService 的 $invoke 方法,需要传递方法名称,参数类型,参数值。并指定返回值。

SOFA 是如何实现的呢?

灰度发布

客户端在发起RPC服务调用之前,在客户端首先从服务器列表中选择一个服务调用者,包含如下关键角色:
   1、Directory
   服务的动态发现,通常基于注册中心进行服务的动态注册与发现,其具体实现类为RegistryDirectory。
   2、Router
   路由实现,其含义是根据Directory发现的所有服务提供者列表中,进行路由选择,也就是根据一定的路由规则选择合适的服务提供者,为Directory发现的服务提供者列表子集,可以基于Condition或脚本(默认为JS脚本,其实现类为ScriptRouter)。
   3、LoadBalance
   负载均衡机制,其作用主要是根据负载均衡算法(随机、轮询)
等算法,从(Directory–>Router)中返回的服务提供者列表中选择一个服务提供者,进行本次的RPC服务调用。
4、Cluster
   集群(容错机制),就是当从服务提供者列表中按照负载均衡算法选择一个服务提供者,进行RPC服务调用后,发送了异常后的策略,例如failover(重试)、failfast(快速失败)等。
   服务的灰度发布,其目标是希望根据请求,某些请求走新版本服务器,某些请求走旧版本服务器,其本质就是路由机制,即通过一定的条件来缩小服务的服务提供者列表,正好与Dubbo的Router相吻合。
   有关于Dubbo的Router机制,请参考官方文档第【46、47、48】页,如果想从源码的角度了解其实现机制,请参考博文:https://blog.csdn.net/prestigeding/article/details/80848594
   有了理论支持,下文将根据上述理论进行实战。
3、方案具体实现示例
   本示例代码需要完成的任务是,对DemoService#createUser服务,其用户机构ID(orgId)为1的走新版本(当前服务提取者列表的最后一台服务器),其他的请求走所有的服务器(除最后一台服务器)。

原文链接:https://blog.csdn.net/prestigeding/article/details/83057273

https://blog.csdn.net/hueason/article/details/81054093

Dubbo原理

框架设计

  • Service层

    • 用户自己要实现定义的服务
  • RPC层

    • Config 配置层 (收集配置信息,每个标签都对应一个config类)
    • Proxy 代理层
    • Registry(注册中心,subscribe,register)
    • Cluster (调用 invoker, directory,路由 router,负载 LoadBalance,cluster )
    • Monitor(监控层)
    • Protocol(远程调用 invoker + protocol + Exporter)
  • Remoting

    • Exchange 交换
      • 在两个服务间架起Exchanger , ExchangClient ->(Exchanger ) -> ExchangeServer
    • Transport 传输层
      • 真正的传输层 Transporter
      • Transporter真正的底层就是netty框架
    • Serialize 负责序列化和反序列化

启动加载配置

  • 如果是XML方式,它本身就是一个spring的配置文件
  • interface BeanDefinitionParser DubboBeanDefinitionParser
    1. 容器启动 解析配置文件
    2. DubboNameSpaceHandler 创建dubbo标签解析器
    3. DubboBeanDefinitionParser 解析dubbo标签,把信息放入到对应的config类中
      • ApplicationConfig
      • RegistryConfig
      • ServiceConfig ...
        • ServiceBean解析 ,暴露服务 ……

服务暴露

  • ServiceBean implements InitializingBean , ApplicationListener
  • InitializingBean--> 创建完对象时调用 afterPropertiesSet()
    • ServiceBean -> afterPropertiesSet -> setProvider() ,setApplication() ……
  • IOC容器创建完成时回调 onApplicationEvent ()
    • ServiceBean -> onApplicationEvent->exporter()->ServiceConfig
    • ->ServiceConfig -> doExporter()->doExportUrls() -> protocols
    • ->ProxyFactory ->Invoker

服务引用

  • 核心就是返回一个invoker代理(里面包含了能和远程服务建立连接的客户端)

  • ReferenceBean implements FactoryBean -> getObject() ReferenceConfig

  • ProtocolFactory -createProxy -> ( DubboProtocol , RegistryProtocol )

  • DubboProtocol -> refer() -> connect & getClients ()

  • RegistryProtocol -> doRefer() & subcribe service -> ProviderConsumerRegTable.registerConsumer

  • 返回invoker

服务调用

  • Proxy -> invoker.invoke(RpcInvocation)
posted @ 2021-06-27 17:39  沉梦匠心  阅读(94)  评论(0)    收藏  举报