Dubbo

1. 从单体项目到SOA、微服务

1.1 单体架构

  • 优点:部署简单,适合小项目,常见的技术架构组合是SSM
  • 缺点:所有业务都堆积在一个项目里,开发、测试会越来越难,牵一发而动全身

1.2 垂直架构

根据业务将大项目垂直切割成多个子项目,分开部署

  • 优点:流量分流,对流量大的子项目可以单独扩容
  • 缺点:子项目之间的接口调用是硬编码(指定 IP 端口),负载均衡也麻烦,接口调用没有监控,没有统一的方式

1.3 SOA

Service Oriented Architecture,面向服务架构,在垂直架构拆分出来的子项目的基础上继续拆分出多个具备松耦合的服务,服务之间用统一的方式进行交互。同时,整个系统在也做了横向划分,进行了分层(应用层、业务服务层、基础业务层、存储层)

如何分层?按照业务性质分层,每一层都要求简单和容易维护

  • 应用层:离用户最近的一层,如网站、APP、小程序等
  • 业务服务层:负责处理具体的业务场景,可能是多个基础服务的聚合服务,如订单服务
  • 基础业务层:简单业务,如用户服务
  • 基础服务层:不管具体业务,只管服务能力输出,是通用的服务,如商品服务、消息服务
    • 特点:逻辑简单、请求量大、功能独立
  • 存储层:数据库

1.4 微服务架构

比SOA的粒度更细致,业务需要彻底的组件化和服务化,服务之间用 HTTP 通信

2. 认识和使用 Dubbo

Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架,主要提供了面向接口代理的高性能RPC调用、智能容错和负载均衡、服务自动注册和发现等功能。

中文官网 提供了用户文档、开发指南和运维管理指南。

2.1 处理流程

dubbo-architucture

2.2 配置方式

Dubbo 可以使用注解、API 还有 XML 的方式进行配置。

推荐使用 XML,可以集中管理配置,也避免了代码入侵,参照文档 XML 配置

2.3 标签介绍

  • dubbo:application 应用配置

    • name:当前应用名称,必选
  • dubbo:registry 注册中心配置

    • address:注册中心地址,格式为 协议://ip:port
  • dubbo:protocol 协议配置

    • name:通信协议,默认dubbo
    • port:端口
  • dubbo:service 服务配置

    • interface,对外暴露的接口
    • ref,具体实现对象的引用,一般是 Spring 的 beanId
    • version,对外暴露的版本号,消费者会根据版本号进行消费
  • dubbo:reference 引用服务配置,生成远程服务代理

    • id,引用的beanId
    • ref,引用的接口
    • timeout,调用超时后断开
    • mock,服务降级,需要一个 接口名 + Mock后缀 的实现类
    • check,启动时检查生产者是否有该服务
    • retris,重试次数,如果配置 2,则共调用 2 + 1 = 3 次,注意接口幂等
    • executes,并行度

3. 高级特性

3.1 SPI

SPI 是一种服务提供发现机制,可以动态替换发现,实现解耦,将装配控制逻辑与调用者业务代码分离。

image-20210129225213412

3.1.1 JDK 中的 SPI

  • 服务提供者实现了接口的一种具体实现后,在 META-INF/services 目录下创建一个以接口全限定名的文件,内容为实现类的全限定名
  • 接口实现类所在的 jar 放在主程序的 classpath
  • 主程序通过 java.util.ServiceLoader 动态加载实现模块,扫描 META-INF/services 目录下的配置文件,找到实现类的全限定名,把类加载到 JVM
  • SPI 的实现类必须携带一个无参构造方法

3.1.2 Dubbo 中的 SPI

  • dubbo 的拓展点在 META-INF.dubbo.internal 目录下,如负载均衡策略org.apache.dubbo.rpc.cluster.LoadBalance
  • 相比 JDK 原生 SPI,Dubbo 自己做 SPI 的目的在于
    • JDK 标准的 SPI 会一次性实例化扩展点所有实现,浪费资源
    • 如果有扩展点加载失败,则所有扩展点都无法使用
    • Dubbo 提供了对扩展点包装功能 Adaptive,并且支持通过 set 的方式对其他拓展点进行注入
  • 自定义步骤
    • 在接口使用 @SPI,表明是 dubbo 的 扩展点,可以指定默认的实现 key
    • 在 resources 目录下创建 META-INF/dubbo 目录,创建一个以接口全限定名的文件,内容为 key = 实现类的全限定名
    • 主程序通过 ExtensionLoader 加载

3.1.3 Dubbo 的 Adaptive

  • 解决了动态选择具体扩展点的问题

  • 自定义动态选择扩展点步骤

    • 在接口或者接口的方法贴上 @Adaptive ,表明调用时动态匹配url里的key

    • 通过 getAdaptiveExtension 统一对指定接口对应的所有扩展点进行封装

    • 通过 URL 对扩展点进行动态选择

// 调用接口 HelloService,实现类的 key 为 human
URL url = URL.valueOf("test://localhost/hello?hello.service=human");
HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
String msg = adaptiveExtension.say(url);
System.out.println(msg);

3.2 拦截机制

  • 该机制专门为服务消费方和服务提供方的调用过程进行拦截而设计的,可以实现白名单、监控、日志。

  • 自定义拦截器步骤

    • 实现 org.apache.dubbo.rpc.Filter 接口,自定义拦截时需要做的事情
    • 实现类贴上 @Activate ,还可以通过 group 属性指定生产端、消费端
    • META-INF/dubbo 目录下新建 org.apache.dubbo.rpc.Filter 文件,内容为 过滤器名字 = 实现类的全限定名
    • 打包后,在需要使用过滤器的项目里引入即可

3.3 负载均衡策略

  • 四种策略

    • 随机 random(默认)
    • 轮询 roundrobin
    • 最少活跃调用数 leastactive
    • 一致性 Hash,consistenthash
  • 通过属性 loadbalance 配置

    • 服务级别,在 dubbo:servie、dubbo:reference 中添加 loadbalance
    • 方法级别,在 dubbo:method 中添加 loadbalance
  • 自定义负载均衡器步骤

    • 实现 org.apache.dubbo.rpc.cluster.LoadBalance 接口,自定义负载均衡策略
    • META-INF/dubbo 目录下新建 org.apache.dubbo.rpc.cluster.LoadBalance 文件,内容为 负载器名字 = 实现类的全限定名
    • 打包,在需要使用自定义策略的的项目里引入
    • 在消费端的 dubbo 标签上添加 loadbalance 属性,value 为负载器名字

3.3 异步调用

使用方式

  • 客户端 Dubbo 配置增加 async 属性,可以在指定接口也可以指定方法
  • 主程序通过 RpcContext.getContext().getFuture().get() 获取返回结果

3.4 线程池

  • 自定义步骤
    • 实现 org.apache.dubbo.common.threadpool.ThreadPool,自定义线程池
    • META-INF/dubbo 目录下新建 org.apache.dubbo.common.threadpool.ThreadPool 文件,内容为 线程池名字 = 实现类的全限定名
    • 打包,在服务端里引入,并且配置 dubbo.provider.threadpool 属性

3.5 路由规则

  • 使用方式

    通过 Zookeeper 的节点保存路由规则,消费端可以根据规则寻找服务端

    RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
    Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://127.0.0.1:2181"));
    // 访问除 192.168.20.1 以外的机器
    registry.register(URL.valueOf("condition://0.0.0.0/com.example.service.HelloService?category=routers&force=true&dynamic=true&rule=" + URL.encode("=> host != 192.168.20.1")));
    
  • 应用:路由与上线系统结合,实现灰度发布

    • 利用 zookeeper 的路径感知能力,在服务准备进行重启之前将当前机器的IP地址和应用名写入 zookeeper
    • 服务消费者监听该目录,读取其中需要进行关闭的应用名和机器IP列表并且保存到内存中
    • 当前请求过来时,判断是否是请求该应用,如果是请求重启应用,则将该提供者从服务列表中移除

3.6 服务动态降级

当服务器压力剧增时,个别服务请求超时,为了防止分布式服务发生雪崩,需要降级,有以下几种方式

  • 在 Dubbo Admin 设置屏蔽或者容错
  • 指定返回简单值或者null,可以使用 <dubbo:reference mock=“return null”/>
  • 使用java代码,通过 Registry 写入
  • 整合 hystrix

4. 源码剖析

4.1 架构设计

官方文档

4.1.1 组件调用关系

/dev-guide/images/dubbo-relation.jpg

  1. 生产者启动,容器加载 Service 信息后通过 Protocol 注册到注册中心
  2. 消费者启动,监听生产者列表
  3. 当生产者列表发生变更时,注册中心及时通知消费者
  4. 消费者发起请求
    1. 通过 Proxy 模块发起请求
    2. 利用 Cluster 模块选择真实的要发送的生产者信息
    3. 再通过 Protocol 协议发送数据给生产者
    4. 生产者通过 Ptotocol 处理来自消费者的信息
    5. 最后由真正的服务提供者 Service 处理
  5. 调用过程中,Monitor 会进行数据统计

4.1.2 调用链

/dev-guide/images/dubbo-extension.jpg

  • 整个调用链可以分为三层
    • Interface层,业务逻辑层
    • RPC层,远程过程调用
    • Remoting层,数据传输层
  • 调用流程
  1. 消费者调用 Interface 的方法,由消费者的 Proxy 处理
  2. 交给 Filter 模块,统一拦截请求,有 local 过滤器、mock 过滤器等
  3. 进入主要的 Invoke 调用逻辑
    1. 通过 Directory 去配置中心读取信息,获取所有的 Invoker
    2. 通过 Cluster 模块和具体的路由规则获取 Invoker 列表
    3. 通过 LoadBalance 模块和选择的负载均衡策略,选择一个具体的 Invoker 来处理请求
    4. 如果执行中出现错误,且消费端配置了重试机制,会重新尝试执行
  4. 继续经过 Filter,进行执行功能的前后封装,选择具体的 Protocol
  5. 客户端进行编码和序列化,发送数据给服务端
  6. 服务端接收到数据,进行解码和反序列化
  7. 通过 Exporter 选择执行器
  8. 交给 Filter 进行一个服务端的过滤
  9. 通过 Invoker 调用接口的具体实现,然后原路返回

4.1.3 整体设计

dubbo-framework

分层,大的有3层,小的有10层

  • Business 业务逻辑层

    • Service 业务层,业务代码
  • RPC 远程过程调用层

    • Confiig 配置层

      对外提供配置,有 ServiceConfig、ReferenceConfig 两个配置类

    • Proxy 服务代理层

      产生一个代理类,使业务层对远程过程调用无感

    • Registry 注册中心层

      封装服务地址的注册与发现,以服务的 URL 为中心

    • Cluster 路由层(集群容错层)

      提供了多个提供者的路由和负载均衡,以 Invoker 为中心

    • Monitor 监控层

      统计 RPC 调用相关信息,如调用次数、成功失败的情况、调用时间等

    • Protocol 远程调用层

      封装 RPC 调用,无论是暴露服务还是引用服务,都是在 Protocol 作为主功能入口,负责 Invoker 整个生命周期,Dubbo 所有的模型都想 Invoker 靠拢

  • Remoting 远程数据传输层

    • Exchange 信息交换层

      封装请求和响应的模式,如同步请求转换为异步请求

    • Transport 网络传输层

      统一网络传输的接口欧,如 netty 和 mina 统一为一个玩哪个罗传输接口

    • Serialize 数据序列化层

      负责管理框架中数据传输的序列化与反序列化

4.2 服务注册与消费

4.2.1 Zookeeper 目录结构

image-20210106230756169

使用客户端连接 Zookeeper,输入 ls -R /dubbo 可以发现以下目录结构

  • dubbo 根目录
    • Service 接口的全限定名
      • consumers 当前服务下面所有的消费者列表(URL)
      • providers 当前服务下面所有的提供者列表(URL)
      • configurations 当前服务下的配置信息
      • routers 消费者调用提供者时,根据路由进行匹配

4.2.2 服务注册

/dev-guide/images/dubbo_rpc_export.jpg

  • 查看 ServiceConfig 类,有三个重要的属性

    • ref,父类属性,实现类的引用
    • ProxyFactory 负责将实现类转换为 Invoker
    • Protocol 负责将 Invoker 转换为 Exporter
  • RegistryService 类是注册中心,可以对指定 URL 进行注册和解绑、监听和取消监听、查询等操作

  • RegistryFactory 是生成注册中心的工厂类,默认是 dubbo,getRegistry 方法可以通过 protocol 参数选择协议

  • 注册过程

    • 首先 ServiceConfig 拿到对外提供的实现类引用 ref(如 HelloServiceImpl),然后通过 ProxyFactorygetInvoker 方法将 ref 生成一个 Invoker

      // 入口在 ServiceConfig类 的 doExportUrlsFor1Protocol 方法中
      Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
      
    • 接着 Invoker 要转化为 Exporter,这个动作在 RegistryProtocolexport 中完成,export 将需要执行的信息注册到中心并导出本地

      // 拿到invokeer之后,将其转为 exporter
      Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
      
      // 先调自己的 register 方法
      private void register(URL registryUrl, URL registeredProviderUrl) {
          // 生成 ListenerRegistryWrapper 包装类
          Registry registry = registryFactory.getRegistry(registryUrl);
          // 然后调包装类的 register 方法
          registry.register(registeredProviderUrl);
      }
      
      // 走到FailbackRegistry的register方法,里面有个 doRegister 方法负责真正执行注册
      public void register(URL url) {
          ...
          super.register(url);
          removeFailedRegistered(url);
          removeFailedUnregistered(url);
          try {
              // 指正执行注册
              doRegister(url);
          } catch (Exception e) {
              ...
          }
      }
      
      // 查看ZookeeperRegistry类的doRegister方法,可以看到toUrlPath方法将url转换为dubbo的目录结构,然后由zkClient创建节点,至此完成注册流程
      public void doRegister(URL url) {
          try {
              zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
          } catch (Throwable e) {
              throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
          }
      }
      /* toUrlPath(url) 示例
      /dubbo/org.apache.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F192.168.73.241%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26metadata-type%3Dremote%26methods%3DsayHello%2CsayHelloAsync%26pid%3D9164%26release%3D%26side%3Dprovider%26timestamp%3D1612021321954 
      */
      

4.2.3 URL 规则

  • 规则:protocol://host:port/path?key=value&key=value

  • 示例

    [17/01/21 15:30:24:354 CST] main INFO zookeeper.ZookeeperRegistry: [DUBBO] Subscribe: provider://192.168.73.241:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.73.241&bind.port=20880&category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=12364&qos.port=22222&release=&side=provider&timestamp=1610868622734, dubbo version: , current host: 192.168.73.241

  • Dubbo 的 URL 与 Java 的 URL 的区别

    • 支持动态修改参数
    • 支持缓存,能够缓存一些数据如路径 path

4.2.4 本地服务原理

  • 原理:Dubbo 在引用服务时会创建 Registry 对象并加载本地缓存文件,优先订阅注册中心,如果注册中心订阅失败,再访问本地缓存文件获取服务提供信息。
  • 创建 Registry 对象的过程
    • AbstractRegistry 对象的构造函数开始,根据本地默认路径加载 cache 文件,读取已有的配置信息
    • 每次收到通知后,由 saveProperties 负责更新 cache 文件
    • 保存文件时采用了文件锁规避并发问题(跨JVM)
    • 如果执行出现错误,则交给专门的线程进行重试,默认重试2次

4.2.5 服务消费

/dev-guide/images/dubbo_rpc_refer.jpg

  • 消费过程
    • 首先 ReferenceConfiginit 方法调用了 createProxy,使用 Protocol 调用 refer 方法生成了 Invoker 实例
      invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
      
    • 接着 ProxyFactory 通过 getProxy 生成客户端需要的接口(如 HelloService)
      PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
      

4.3 SPI 原理

ExtensionLoader 是重要的类,是所有 Dubbo SPI 的入口

4.3.1 getExtensionLoader 加载过程

  1. 查看 ExtensionLoader 类的 getExtensionLoader 方法,主要是判断接口是否有 @SPI 修饰,以及从缓存中获取 extensionLoader

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        // 要求是接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        
        // 判断传入的 class 是否为 `@SPI` 修饰的接口
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                                               ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
    
        // 尝试从缓存中加载,如果没有再进行创建,并放入缓存
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
  2. ExtensionLoader 的构造器中,创建了一个 ExtensionFactory 对象,从获取的方式能看出扩展工厂是一个扩展点

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
    
  3. 查看 ExtensionFactory 类的 getExtension 方法,他是根据扩展点类型和名字来获取扩展点,这里就和 SPI 中的具体名称实现相挂钩

    @SPI
    public interface ExtensionFactory {
    	// 获取扩展点
        <T> T getExtension(Class<T> type, String name);
    }
    
    1. 拓展工厂有三个实现(dubbo 2.7.5),在 /META-INF/dubbo/internal 目录下可以查看 org.apache.dubbo.common.extension.ExtensionFactory 文件内容

      spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
      adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
      spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
      
    2. 其中,AdaptiveExtensionFactory 使用了 @Adaptive 修饰,代理其他 ExtensionFactory

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        // 获取支持的扩展
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        // 缓存 ExtensionFactory
        factories = Collections.unmodifiableList(list);
    }
    
    1. 查看 getSupportedExtensions 方法,内部又调用了 getExtensionClasses 来获取拓展类,并且使用双重检查来避免重复加载类
    public Set<String> getSupportedExtensions() {
        // 获取所有扩展类信息
        Map<String, Class<?>> clazzes = getExtensionClasses();
        // 返回所有扩展点名称
        return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
    }
    
    private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中获取已经加载的扩展类
        Map<String, Class<?>> classes = cachedClasses.get();
        // 双重检查
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 加载扩展点
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
    
  4. 真正负责加载扩展点的方法是 loadExtensionClasses,做了两件事情

    private Map<String, Class<?>> loadExtensionClasses() {
        // (1)加载当前SPI的默认实现(如果有的话)
        cacheDefaultExtensionName();
        // (2)加载每个目录下的实现名称和对应的classes
        // 	SERVICES_DIRECTORY = "META-INF/services/";
        // 	DUBBO_DIRECTORY = "META-INF/dubbo/";
        // 	DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
    	...
        return extensionClasses;
    }
    
  5. loadDirectory 根据传入的文件夹路径和 包名.接口名 寻找真正的文件列表,并对文件内容进行解析,最后放到 extensionClassMap

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) {
    // 文件名称规则:路径/包名.接口名
    String fileName = dir + type;
    try {
        // 寻找classloader和url列表
        Enumeration<java.net.URL> urls = null;
        ClassLoader classLoader = findClassLoader();

        // 如果有需要,先从当前类的 ClassLoader 找
        if (extensionLoaderClassLoaderFirst) {
            ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
            if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                urls = extensionLoaderClassLoader.getResources(fileName);
            }
        }

        // 继续从当前线程的 ClassLoader 找
        if(urls == null || !urls.hasMoreElements()) {
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
        }

        // 如果文件存在,遍历并加载到 extensionClasses
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                // 解析文件内容
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        ...
    }
}
  1. 文件内容解析工作由 loadResource 负责,根据 key=value 格式拿到 name 和 class,再交给 loadClass 加载、缓存

    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            	...
                String name = null;
            	// 根据“=”分隔每一行的内容
                int i = line.indexOf('=');
                if (i > 0) {
                    // 扩展点名称,如spring
                    name = line.substring(0, i).trim();
                    // 扩展点实现全限定名,如org.apache.dubbo.config.spring.extension.SpringExtensionFactory
                    line = line.substring(i + 1).trim();
                }
                if (line.length() > 0) {
                    // 加载、缓存
                    loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                }
                ...
        } catch (Throwable t) {
            ...
        }
    }
    
  2. 最后在 loadClass 方法中完成加载,并且保存 class 和 name 的映射关系

    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        ...
        // 如果是包含了Adaptive注解,则认为是需要对扩展点包装的方法,这里只做了存储操作,存储至cachedAdaptiveClass中
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            // 判断是否是wapper类型
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
        	}
    
            // 将class和name做映射关系
            // 如org.apache.dubbo.config.spring.extension.SpringExtensionFactory=spring
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }
    

    当整个流程执行后,会对一下几个字段进行更新:

    • cachedAdaptiveClass: 当前Extension类型对应的AdaptiveExtension类型(只能一个)
    • cachedWrapperClasses: 当前Extension类型对应的所有Wrapper实现类型(无顺序)
    • cachedActivates: 当前Extension实现自动激活实现缓存(map,无序)
    • cachedNames: 扩展点实现类对应的名称(如配置多个名称则值为第一个)

4.3.2 getExtension 实现原理

  1. 从获取扩展点的方法 getExtension 开始,通过 name 获取扩展点

    public T getExtension(String name, boolean wrap) {
        // 获取或者创建当前类的 Holder,实现原理类似 cacheClasses
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        // 双重检查避免重复创建拓展点
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 创建扩展点
                    instance = createExtension(name, wrap);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
    
  2. 通过 getOrCreateHolder 从缓存中获取扩展点的 Holder

    private Holder<Object> getOrCreateHolder(String name) {
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            // 通过 ConcurrentMap 的 putIfAbsent 保证原子性(使用了synchronized)
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }
    
  3. 如果 Hodler 里没有扩展点,则进行创建

    private T createExtension(String name, boolean wrap) {
        // 从配置文件中加载的扩展类中获取字节码对象
        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);
            ...
            // 初始化扩展点
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            ...
        }
    }
    
  4. 通过 setter 注入其他扩展点的实体

    private T injectExtension(T instance) {
        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    // 不是 setter 方法,跳过
                    continue;
                }
                if (method.getAnnotation(DisableInject.class) != null) {
                    // 贴了关闭注入的注解,跳过
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    // 不是公开的方法,跳过
                    continue;
                }
    
                try {
                    // 获取需要set的扩展点名称(比如有一个方法为setRandom(LoadBalance loadBalance),那么则以为着需要加载负载均衡中名为random的扩展点)
                    String property = getSetterProperty(method);
                    // 从 ExtensionLoader 中加载指定的扩展点
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                        // 注入
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                    ...
                }
    
            }
        } catch (Exception e) {
            ...
        }
        return instance;
    }
    

4.3.3 Adapter 实现原理

Adaptive 的主要功能是将所有的扩展点封装成一个类,通过 URL 传入的参数动态选择需要使用的扩展点,实现原理是动态代理。

  1. 从获取拓展点的入口 getAdaptiveExtension 方法开始

    public T getAdaptiveExtension() {
        // 通过 holder 和加锁的方式获取动态扩展点
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            ...
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        // 创建拓展点
                        instance = createAdaptiveExtension();
                        // 创建完成之后放进 holder
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        ...
                    }
                }
            }
        }
        return (T) instance;
    }
    
  2. 创建扩展点 createAdaptiveExtension,先实例化,然后通过 setter 注入的方式进行扩展

    private T createAdaptiveExtension() {
        try {
            // 先使用 getAdaptiveExtensionClass 方法进行构建类并且执行实例化
            // 然后和普通的其他 class 相同,依旧使用 injectExtension 进行扩展
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            ...
        }
    }
    
  3. 实例化是在 getAdaptiveExtensionClass 方法中完成的,生成Adaptive的代码,编译并生成一个class

    private Class<?> createAdaptiveExtensionClass() {
        // 生成一个新的Adaptive的代码生成器,并生成代码
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        // 获取类加载器
        ClassLoader classLoader = findClassLoader();
        // 通过扩展点寻找编译器
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        //编译并生成一个class
        return compiler.compile(code, classLoader);
    }
    
  4. 代码生成器,先判断是否存在用 @Adaptive 修饰的方法,然后再生成类的代码字符串

    public String generate() {
        if (!hasAdaptiveMethod()) {
            // 如果类的方法都没有用 @Adaptive 修饰,则抛出异常
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }
    
        StringBuilder code = new StringBuilder();
        code.append(generatePackageInfo());
        code.append(generateImports());
        code.append(generateClassDeclaration());
    
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            // 生成方法
            code.append(generateMethod(method));
        }
        code.append("}");
    
        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString();
    }
    
  5. 查看 generateMethod 里面的 generateMethodContent,该方法负责生成方法内容,包含如何获取扩展点的名称并且执行,是 Adaptive 最核心的代码

    private String generateMethodContent(Method method) {
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        if (adaptiveAnnotation == null) {
            // 只支持被 @Adaptive 修饰的方法
            return generateUnsupported(method);
        } else {
            // 获取 URL 参数的在方法参数列表的位置
            int urlTypeIndex = getUrlTypeIndex(method);
            // found parameter in URL type
            if (urlTypeIndex != -1) {
                // Null Point check
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                // did not find parameter in URL type
                code.append(generateUrlAssignmentIndirectly(method));
            }
            // 获取扩展点的适配名称,["类名.方法名"]
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
            // 判断方法的参数是否存在 Invocation 类,如果存在,则影响获取扩展点名称的方式
            // 存在Invocation时,通过getMethodParameter,否则通过getParameter来执行
            // getMethodParameter是dubboURL中特有的,用于将"test.a"转换为"testA"的形式
            boolean hasInvocation = hasInvocationArgument(method);
            code.append(generateInvocationArgumentNullCheck(method));
            // 生成获取扩展点名称的方法
            code.append(generateExtNameAssignment(value, hasInvocation));
            // check extName == null?
            code.append(generateExtNameNullCheck(value));
            // 生成扩展点实现
            code.append(generateExtensionAssignment());
            // 生成方法的返回语句
            code.append(generateReturnAndInvocation(method));
        }
    
        return code.toString();
    }
    

4.4 集群容错

4.4.1 组件一览

img

集群工作流程分为两个阶段

  • 第一阶段:服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 ClusterInvoker 实例,即上图的 merge 操作
  • 第二阶段:服务消费者进行远程调用时,ClusterInvoker 先调用 Directory 获取 Invoker 列表,接着调用 Router 过滤不符合路由规则的 Invoker,再通过 LoadBalance 从 Invoker 列表选取一个 Invoker 实例进行真正的远程调用

4.4.2 信息缓存组件 Directory

  • 查看 Directory 的接口定义,可以通过 list 方法获取服务提供者列表

    public interface Directory<T> extends Node {
    	// 获取服务类型,即 HelloService
        Class<T> getInterface();
        // 根据本次调用的信息来获取所有可用的 invoker 列表
        List<Invoker<T>> list(Invocation invocation) throws RpcException;
        // 获取所有可用的 invoker 列表
        List<Invoker<T>> getAllInvokers();
    }
    
  • 查看 Directory 的抽象实现 AbstractDirectorylist 方法,但里面的 doList 其实是个抽象方法,具体实现在 RegistryDirectory,由 routerChain 返回提供者列表

public List<Invoker<T>> list(Invocation invocation) throws RpcException {
    if (destroyed) {
        throw new RpcException("Directory already destroyed .url: " + getUrl());
    }

    // 由子类实现
    return doList(invocation);
}

public List<Invoker<T>> doList(Invocation invocation) {
	...
    List<Invoker<T>> invokers = null;
    try {
        // 交给 routerChain 去处理并且获取所有的 invokers
        invokers = routerChain.route(getConsumerUrl(), invocation);
    } catch (Throwable t) {
        ...
    }

    return invokers == null ? Collections.emptyList() : invokers;
}
  • 那么 routerChain 的 invoker 是哪里来的呢,RegistryProtocol#refer 构建了 invoker 列表,而真正与 routerChain 产生关联的地方在 doRefer 方法里

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        // 实例化 Directory
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
        // 生成监听 URL
        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
        if (directory.isShouldRegister()) {
            directory.setRegisteredConsumerUrl(subscribeUrl);
            registry.register(directory.getRegisteredConsumerUrl());
        }
        // 构建 routerChain
        directory.buildRouterChain(subscribeUrl);
        // 监听所有的 providers
        directory.subscribe(toSubscribeUrl(subscribeUrl));
        // 加入到集群中
        Invoker<T> invoker = cluster.join(directory);
        List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {
            return invoker;
        }
    
        RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker);
        for (RegistryProtocolListener listener : listeners) {
            listener.onRefer(this, registryInvokerWrapper);
        }
        return registryInvokerWrapper;
    }
    
  • 遍历路由,调用 RouterChainroute 方法过滤 invoker 列表

    public List<Invoker<T>> route(URL url, Invocation invocation) {
        List<Invoker<T>> finalInvokers = invokers;
        for (Router router : routers) {
            finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }
    

4.4.3 路由规则组件 Router

image-20210124233207790

  • ConditionRouter 有两个重要的属性

    // 是否满足判断条件
    protected Map<String, MatchPair> whenCondition;
    // 满足判断条件时如何选取 invokers
    

protected Map<String, MatchPair> thenCondition;

// MatchPair 存放的是匹配和不匹配的条件
final Set<String> matches = new HashSet<String>();
final Set<String> mismatches = new HashSet<String>();
```
  • 查看 route 方法,判断 URL 是否满足判断条件、匹配条件规则

    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        ...
        try {
            // 不满足判断条件,直接返回
            if (!matchWhen(url, invocation)) {
                return invokers;
            }
            List<Invoker<T>> result = new ArrayList<Invoker<T>>();
            ...
            // 判断每一个 invoker 的 URL 是否满足
            for (Invoker<T> invoker : invokers) {
                if (matchThen(invoker.getUrl(), url)) {
                    result.add(invoker);
                }
            }
            if (!result.isEmpty()) {
                return result;
            } else if (force) {
                // log
                return result;
            }
        } catch (Throwable t) {
            ...
        }
        return invokers;
    }
    
  • matchWhenmatchThen 底层都调用了 matchCondition

    // 是否满足判断条件:
    // 1. 如果判断条件为空则直接认定为匹配
    // 2. 如果条件匹配则认定为匹配
    boolean matchWhen(URL url, Invocation invocation) {
        return CollectionUtils.isEmptyMap(whenCondition) || matchCondition(whenCondition, url, null, invocation);
    }
    
    // 判断条件不能为空并且匹配条件规则时才返回
    private boolean matchThen(URL url, URL param) {
        return CollectionUtils.isNotEmptyMap(thenCondition) && matchCondition(thenCondition, url, param, null);
    }
    
  • init 方法中可以查看路由规则的生成方式,在这里生成 whenCondition 和 thenCondition 的内容

    public void init(String rule) {
        try {
            ...
            // 根据 "=>" 来分隔when或者then条件
            int i = rule.indexOf("=>");
            String whenRule = i < 0 ? null : rule.substring(0, i).trim();
            String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
            // 生成when或者then条件,存在 MatchPair 里
            Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
            Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
            this.whenCondition = when;
            this.thenCondition = then;
        } catch (ParseException e) {
            ...
        }
    }
    

4.4.4 集群容错组件 Cluster

  • 查看 Cluster 的接口定义,容错方案默认使用 FailOverCluster

    @SPI(Cluster.DEFAULT)
    public interface Cluster {
        String DEFAULT = FailoverCluster.NAME;
        // 生成 invoker
        @Adaptive
        <T> Invoker<T> join(Directory<T> directory) throws RpcException;
    }
    
  • 查看 join 方法,与 Register 类似,Cluster 也提供了AbstractCluster 抽象实现,但真正处理的是 doJoin 抽象方法,由子类实现

    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        // 1.由子类 doJoin 生成新的invoker
        // 2.将新的 invoker 用拦截器封装,并加入到拦截链的队尾
        return buildClusterInterceptors(doJoin(directory), directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
    }
    
  • 容错策略 FailoverCluster 的内部只是创建了一个新的 invoker

    public class FailoverCluster extends AbstractCluster {
        public final static String NAME = "failover";
        @Override
        public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
            return new FailoverClusterInvoker<>(directory);
        }
    }
    
  • 查看 Invoker 接口的抽象实现 AbstractClusterInvoker

    public Result invoke(final Invocation invocation) throws RpcException {
        // 检查是否已经关闭
        checkWhetherDestroyed();
    
        // 拷贝当前 RpcContext 的附加信息到 invocation 中
        Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
        }
    
        // 从 directory 获取 invoker 列表(经过路由过滤的)
        List<Invoker<T>> invokers = list(invocation);
        // 初始化负载均衡器
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        // 适配异步请求
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        // 交给子类进行真正的调用
        return doInvoke(invocation, invokers, loadbalance);
    }
    
  • 查看 FailoverClusterInvoker 实现的 doInvoke 方法,主要是通过for循环重试,每次重试都走一遍路由规则

    public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        List<Invoker<T>> copyInvokers = invokers;
        checkInvokers(copyInvokers, invocation);
        String methodName = RpcUtils.getMethodName(invocation);
        // 获取最大重试次数,默认重试2次,2+1=3,执行3次
        int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        RpcException le = null; // last exception.
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                // 检查 consumer 是否关闭
                checkWhetherDestroyed();
                // 获取 invoker 列表
                copyInvokers = list(invocation);
                // 再次检查 invoker 列表是否不为空
                checkInvokers(copyInvokers, invocation);
            }
            // 通过负载均衡选择一个具体的 inovker
            Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
            invoked.add(invoker);
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                // 真正调用
                Result result = invoker.invoke(invocation);
                ...
                return result;
            } catch (RpcException e) {
                if (e.isBiz()) { // biz exception.
                    throw e;
                }
                le = e;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
                providers.add(invoker.getUrl().getAddress());
            }
        }
    }
    

4.4.5 负责均衡组件 LoadBalance

  • 查看 LoadBalance 的接口定义,负载均衡策略默认使用随机算法

    @SPI(RandomLoadBalance.NAME)
    public interface LoadBalance {
    	// 选择一个 invoker
        @Adaptive("loadbalance")
        <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
    }
    
  • 查看 LoadBalance 的抽象实现 AbstractLoadBalance

    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        if (invokers.size() == 1) {
            // 如果只有一个,那么不用走负载均衡,直接返回
            return invokers.get(0);
        }
        // 由子类实现
        return doSelect(invokers, url, invocation);
    }
    
  • 查看 RandomLoadBalancedoSelect 方法,先给每个 invoker 计算权重,如果权重不相同,采用轮盘算法随机选择一个 invoker,如果权重相同,则随机返回一个 invoker

    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // 给每个 invoker 计算权重
        // Number of invokers
        int length = invokers.size();
        // Every invoker has the same weight?
        boolean sameWeight = true;
        // the weight of every invokers
        int[] weights = new int[length];
        // the first invoker's weight
        int firstWeight = getWeight(invokers.get(0), invocation);
        weights[0] = firstWeight;
        // The sum of weights
        int totalWeight = firstWeight;
        for (int i = 1; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            // save for later use
            weights[i] = weight;
            // Sum
            totalWeight += weight;
            if (sameWeight && weight != firstWeight) {
                sameWeight = false;
            }
        }
        
        // 权重不同,用轮盘算法选择
        if (totalWeight > 0 && !sameWeight) {
            // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
            int offset = ThreadLocalRandom.current().nextInt(totalWeight);
            // Return a invoker based on the random value.
            for (int i = 0; i < length; i++) {
                offset -= weights[i];
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        // 权重相同,随机选择
        return invokers.get(ThreadLocalRandom.current().nextInt(length));
    }
    

4.4.6 Invoker 执行逻辑

  • 查看 Invoke 的接口定义

    public interface Invoker<T> extends Node {
    	// 获取当前执行器的服务接口
        Class<T> getInterface();
    
        // 执行请求操作
        Result invoke(Invocation invocation) throws RpcException;
    }
    
  • 查看 Invoke 的抽象实现 AbstractInvoker,主要是基础信息封装,最后由子类处理请求

    public Result invoke(Invocation inv) throws RpcException {
        ...
        RpcInvocation invocation = (RpcInvocation) inv;
        invocation.setInvoker(this);
        // 设置所有的 RpcContect 中的附加信息
        if (CollectionUtils.isNotEmptyMap(attachment)) {
            invocation.addObjectAttachmentsIfAbsent(attachment);
        }
    
        Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
        if (CollectionUtils.isNotEmptyMap(contextAttachments)) {
            invocation.addObjectAttachments(contextAttachments);
        }
    
        // 获取执行模式
        invocation.setInvokeMode(RpcUtils.getInvokeMode(url, invocation));
        // 设置执行id,适配异步模式
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    
        AsyncRpcResult asyncResult;
        try {
            // 由子类执行
            asyncResult = (AsyncRpcResult) doInvoke(invocation);
        } catch (InvocationTargetException e) { // biz exception
            ...
        } catch (Throwable e) {
            asyncResult = AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
        }
        // 设置执行的结果信息
        RpcContext.getContext().setFuture(new FutureAdapter(asyncResult.getResponseFuture()));
        return asyncResult;
    }
    
  • 查看 DubboInvokerdoInvoke 方法,底层依赖通过客户端进行调用

    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(PATH_KEY, getUrl().getPath());
        inv.setAttachment(VERSION_KEY, version);
    
        // 传输的客户端
        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
            // 是否需要返回值(void 或者 是异步请求)
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = calculateTimeout(invocation, methodName);
            if (isOneway) {
                // 不需要返回值
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                // 发送命令
                currentClient.send(inv, isSent);
                // 告知为异步的结果
                return AsyncRpcResult.newDefaultAsyncResult(invocation);
            } else {
                // 获取真正执行的线程池
                ExecutorService executor = getCallbackExecutor(getUrl(), inv);
                // 发送请求并且等待结果
                CompletableFuture<AppResponse> appResponseFuture =
                    currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
                // 在2.6.x中使用,设置完成的结果信息
                FutureContext.getContext().setCompatibleFuture(appResponseFuture);
                AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
                result.setExecutor(executor);
                return result;
            }
        } catch (TimeoutException e) {
            ...
        } catch (RemotingException e) {
            ...
        }
    }
    
  • 查看 ExchangeClient 的父接口 ExchangeChannelrequest 方法

    // 发送请求信息
    CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException;
    
  • 底层实现是 HeaderExchangeClient,但又交给了 channel 进行数据处理

    public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
        return channel.request(request, timeout, executor);
    }
    
  • 查看 HeaderExchangeChannelrequest 方法,内部有一个 DefaultFuture

    public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
        if (closed) {
            throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
        }
        // create request.
        Request req = new Request();
        req.setVersion(Version.getProtocolVersion());
        req.setTwoWay(true);
        req.setData(request);
        // 创建执行结果的回调信息处理
        DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
        try {
            // 交给真正的业务渠道处理,如nettyChannel
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        return future;
    }
    

4.5 网络通信

4.5.1 数据包结构

image-20210127223421730

dubbo 协议采用固定消息头(16 byte)和不定长度的消息体来进行消息传输

  • Magic Hight & Magic Low(16 bit):标志协议版本号,如 dubbo 协议:0xdabb

  • Serialization ID (5 bit):标志序列化类型,如 fastjson 的值为 6

  • Event(1 bit):标志是否为事件消息

  • two ways(1 bit):标志是否期望从服务器返回值,仅在 Req/Res 为 1 时有用,如果需要服务器返回值,则为 1

  • Req/Res(1 bit):标志是请求还是响应,请求:1,响应:0

  • status(8 bit):标志响应状态,仅在 Req/Res 为 0 时有用,常见状态如 20 - OK、40 - BAD_REQUEST

  • Request ID(64 bit):标志唯一请求,类型为 long

  • Data Lenght(32 bit):序列化后的内容长度,按字节计数,类型为 int

  • Variable Part(可变长):被特定的序列化类型序列化后,每个部分都是一个 byte[] 或者 byte

    • 请求包:

      Dubbo version
      Service name
      Service version
      Method name
      Method parameter types
      Method arguments
      Attachments

    • 响应包:

      返回值类型(byte),标识从服务器端返回的值类型:
      返回空值:RESPONSE_NULL_VALUE 2
      正常响应值: RESPONSE_VALUE 1
      异常:RESPONSE_WITH_EXCEPTION 0
      返回值:从服务端返回的响应bytes

dubbo 协议的优点

  • 设计紧凑,能用 1 个 bit 表示的不用 1 个 byte 来表示
  • 请求、响应的 header 一致,通过序列化器对 content 组装特定的内容,代码实现简单

dubbo 协议的缺点 / 可以改进的点

  • dubbo 需要使用特定序列化协议才可以将服务名、方法、方法签名解析出来才能确定要访问的资源,可以将这些信息转成 byte 并且放到 header 中,提升性能
  • 判断 req/res 是否是请求后,可以精简协议,去掉一些不需要的标志和添加一些特定的标志
    • 如status、twoway可以严格定制,去掉冗余标志,减少bit
    • 如超时时间是网络请求必须的参数,所以attachment应该放到请求头,而不是在请求体
    • 去除重复的字段,如path和version
  • dubbo 会将服务名(包名)转成斜杠的形式,没有必要
  • dubbo 协议没有预留扩展字段,无法新增标志

4.5.2 数据协议 ExchangeCodec

4.5.2.1 编码
  • ExchangeCodec 类可以查看请求头各变量的长度

    // header length.
    protected static final int HEADER_LENGTH = 16;
    // magic header.
    protected static final short MAGIC = (short) 0xdabb;
    protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];
    protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];
    // message flag.
    protected static final byte FLAG_REQUEST = (byte) 0x80;
    protected static final byte FLAG_TWOWAY = (byte) 0x40;
    protected static final byte FLAG_EVENT = (byte) 0x20;
    protected static final int SERIALIZATION_MASK = 0x1f;
    
  • 这个类的 encode 编码方法和 decode 解码方法都是将数据发送到 ByteBuffer

    public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
        if (msg instanceof Request) {
            // 处理请求
            encodeRequest(channel, buffer, (Request) msg);
        } else if (msg instanceof Response) {
            // 处理响应
            encodeResponse(channel, buffer, (Response) msg);
        } else {
            // 其他请求交给父类处理,用于telnet模式
            super.encode(channel, buffer, msg);
        }
    }
    
  • 查看 encodeRequest 方法,可以看到组装 header 的过程,先在 buffer 预留 header 的空间,然后 body 写入 buffer,获取到 body 的 data lenght 后再写入 header,最后将整个 header 写入 buffer 预留的空间

    protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
        // 请求的序列化类型
        Serialization serialization = getSerialization(channel);
        // 创建 header 的 byte 数组
        byte[] header = new byte[HEADER_LENGTH];
        // 将魔数写入header
        Bytes.short2bytes(MAGIC, header);
        // 标记为请求
        header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
        // 单向还是双向(异步)
        if (req.isTwoWay()) {
            header[2] |= FLAG_TWOWAY;
        }
        // 是否为事件
        if (req.isEvent()) {
            header[2] |= FLAG_EVENT;
        }
        // 写入请求id
        Bytes.long2bytes(req.getId(), header, 4);
    
        // 保存当前 buffer 写入位置,预留 header 的空间,先写入请求内容
        int savedWriteIndex = buffer.writerIndex();
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
        ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
        // 根据是否为事件,来写入不同的内容
        if (req.isEvent()) {
            encodeEventData(channel, out, req.getData());
        } else {
            encodeRequestData(channel, out, req.getData(), req.getVersion());
        }
        out.flushBuffer();
        if (out instanceof Cleanable) {
            ((Cleanable) out).cleanup();
        }
        bos.flush();
        bos.close();
        int len = bos.writtenBytes();
        checkPayload(channel, len);
        // 将内容长度(Data Lenght)写入 header
        Bytes.int2bytes(len, header, 12);
    
        // 回到保存的写入位置,将 header 写入到 buffer
        buffer.writerIndex(savedWriteIndex);
        buffer.writeBytes(header);
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    }
    
  • 查看 DubboCodecencodeRequestData 方法,可以看到组装请求内容的过程

    protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
        RpcInvocation inv = (RpcInvocation) data;
    	// dubbo版本
        out.writeUTF(version);
    	// 接口全限名
        String serviceName = inv.getAttachment(INTERFACE_KEY);
        if (serviceName == null) {
            serviceName = inv.getAttachment(PATH_KEY);
        }
        out.writeUTF(serviceName);
        // 接口版本号
        out.writeUTF(inv.getAttachment(VERSION_KEY));
    	// 方法名称
        out.writeUTF(inv.getMethodName());
        // 参数描述信息
        out.writeUTF(inv.getParameterTypesDesc());
        // 所有参数
        Object[] args = inv.getArguments();
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                out.writeObject(encodeInvocationArgument(channel, inv, i));
            }
        }
        // 所有附加信息
        out.writeAttachments(inv.getObjectAttachments());
    }
    
  • 查看 encodeResponse 方法,与 encodeResponse 类似,只是如果有错误,会将错误信息写入,不交给序列化

    protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
        int savedWriteIndex = buffer.writerIndex();
        try {
            Serialization serialization = getSerialization(channel);
            // header.
            byte[] header = new byte[HEADER_LENGTH];
            // set magic number.
            Bytes.short2bytes(MAGIC, header);
            // set request and serialization flag.
            header[2] = serialization.getContentTypeId();
            if (res.isHeartbeat()) {
                header[2] |= FLAG_EVENT;
            }
            // set response status.
            byte status = res.getStatus();
            header[3] = status;
            // set request id.
            Bytes.long2bytes(res.getId(), header, 4);
    
            buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
            ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
            ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
            // encode response data or error message.
            if (status == Response.OK) {
                if (res.isHeartbeat()) {
                    encodeEventData(channel, out, res.getResult());
                } else {
                    encodeResponseData(channel, out, res.getResult(), res.getVersion());
                }
            } else {
                // 写入错误信息
                out.writeUTF(res.getErrorMessage());
            }
            out.flushBuffer();
            if (out instanceof Cleanable) {
                ((Cleanable) out).cleanup();
            }
            bos.flush();
            bos.close();
    
            int len = bos.writtenBytes();
            checkPayload(channel, len);
            Bytes.int2bytes(len, header, 12);
            // write
            buffer.writerIndex(savedWriteIndex);
            buffer.writeBytes(header); // write header.
            buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
        } catch (Throwable t) {
            ...
        }
    }
    
4.5.2.3 解码
  • 查看 ExchangeCodec 类的 decode 方法

    public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
        int readable = buffer.readableBytes();
        byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
        buffer.readBytes(header);
        return decode(channel, buffer, readable, header);
    }
    
    protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
        // 检查魔数
        if (readable > 0 && header[0] != MAGIC_HIGH
            || readable > 1 && header[1] != MAGIC_LOW) {
            int length = header.length;
            if (header.length < readable) {
                header = Bytes.copyOf(header, readable);
                buffer.readBytes(header, length, readable - length);
            }
            for (int i = 1; i < header.length - 1; i++) {
                if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) {
                    buffer.readerIndex(buffer.readerIndex() - header.length + i);
                    header = Bytes.copyOf(header, i);
                    break;
                }
            }
            return super.decode(channel, buffer, readable, header);
        }
        // 检查 header 长度
        if (readable < HEADER_LENGTH) {
            // 拆包问题:不完成的包,需要继续读取
            return DecodeResult.NEED_MORE_INPUT;
        }
    
        // 获取数据长度
        int len = Bytes.bytes2int(header, 12);
        checkPayload(channel, len);
    
        // 检查数据包长度
        int tt = len + HEADER_LENGTH;
        if (readable < tt) {
            return DecodeResult.NEED_MORE_INPUT;
        }
    
        // limit input stream.
        ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);
    
        try {
            // 解码
            return decodeBody(channel, is, header);
        } finally {
            if (is.available() > 0) {
                try {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Skip input stream " + is.available());
                    }
                    StreamUtils.skipUnusedStream(is);
                } catch (IOException e) {
                    logger.warn(e.getMessage(), e);
                }
            }
        }
    }
    

4.5.3 处理粘包和拆包问题

Dubbo 处理 TCP 的粘包和拆包问题的方法是通过 InternalDecoder 的 buffer 缓存对象来缓存不完整的 dubbo 协议栈数据,等待下次 inbound 事件再合并进去。

4.5.3.1 拆包
  • 解决:在 ExchangeCodec 类的 decode 方法里可以看到,Dubbo 在真正解码之前,先检查 header 长度是否符合预期,然后获取数据长度并且判断整个数据包长度是否符合预期,如果不符合则触发 NEED_MORE_INPUT,说明 buffer 里面的 inbound 消息是不完整的,此时需要回滚读索引,中断循环,等待第二次 inbound 消息的到来。

    private class InternalDecoder extends ByteToMessageDecoder {
    
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception {
    
            ChannelBuffer message = new NettyBackedChannelBuffer(input);
    
            NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
    
            // decode object.
            do {
                int saveReaderIndex = message.readerIndex();
                // 解码
                Object msg = codec.decode(channel, message);
                if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
                    // 回滚读索引
                    message.readerIndex(saveReaderIndex);
                    break;
                } else {
                    //is it possible to go here ?
                    if (saveReaderIndex == message.readerIndex()) {
                        throw new IOException("Decode without read data.");
                    }
                    if (msg != null) {
                        out.add(msg);
                    }
                }
            } while (message.readable());
        }
    }
    
4.5.3.2 粘包
  • 解决:先处理 buffer 里完整的 Dubbo 协议栈数据,经过 ExchangeCodec 类的 decode 方法解码后,此时还没有到达 buffer 的尾部,message.readable() 返回的是true,继续遍历 message,剩下的数据是不完整的,返回NEED_MORE_INPUT,演变成拆包问题。

4.5.4 线程派发模型

image-20210131181107147

  • Dispather: all, direct, message, execution, connection
  • ThreadPool: fixed, cached
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火车站卖橘子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值