dubbo源码学习笔记

简介

  1. Apache Dubbo 是一款由阿里巴巴公司开发的服务框架,它提供了 RPC 通信与微服务治理两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通 信能力,同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、 服务降级、集群容错等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意 功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。
  2. Dubbo3 定义了全新的 PCP 通信协议—Triple,Dubbo2中的协议叫dubbo
  3. Dubbo Spring Cloud
  4. Dubbo 的广播注册中心,类似于对讲机

Dubbo的系统架构

  1. dubbo的两大设计原则
    1. Dubbo 使用“微内核+插件(SPI接口的实现类)”的设计模式。内核只负责组装插件(扩展点),Dubbo 的功能都是由插件实现的。Dubbo 作为一个优秀的 RPC 框架,一个 Apache 的顶级项目,其最大的亮点之一就是其优秀的无限开放性设计架构—“微内核+插件”的架构设计思想, 使得其几乎所有组件均可方便的进行扩展、增强、替换. (通过SPI实现的,动态加实现类)
    2. 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
      1. dubbo 用url的原因,用url可以节省字符数量,协议地址端口不需要key
  2. dubbo 的三大领域模型
    1. Protocol 服务域:是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
    2. Invoker 实体域:是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
    3. Invocation 会话域:它持有调用过程中的变量,比如方法名,参数等。
  3. image.png
  4. image.png
  5. dubbo 的十层架构
    1. 三大层
      1. business (service) 业务层
      2. rpc (Config . proxy registry . cluster . monitor . protcol) rpc调用
      3. remoting (exchange --> dubbo的同步和netty的异步进行转换 transport serialize) 网络传输层
    2. ReferenceConfig reference-config 标签 . ServiceConfig service-config 标签
    3. Proxy . 消费者代理对象 . Invoker 提供者代理对象
    4. Registry 注册中心, Invoker需要注册到注册中心
    5. 将Invoker包装为Exporter服务暴露对象(封装了Filter过滤器) Invoker是Directory的列表
    6. DubboExporter . dubbo服务暴露协议,存到缓存map,key为服务名称,value为DubboExporter
    7. DubboProtocol . DubboHandler
    8. Proxy 代理是用消费端的Invoker委托对象包装的
    9. RegistryProtocol 注册协议 RegistryDirectory 注册目录 . NotifyListener . 注册中心发生变化的通知 (zookeeper 的watcher 机制)
    10. Directory 拉取到消费端的注册中心的目录
    11. Router . 地址过滤 LoadBalance 负载均衡侧略 RouterFactory
    12. Retry 重试策略
    13. MonitorFilter . Monitor . MonitorFactory . 监控
    14. 服务端和注册端的协议要对称的 Protocol . DubboInvoker
    15. ExchangeClient (同步和异步的转换) Exchanger . ExchangeServer . ExchangeHandler . 端口绑定 .
    16. Client . Transporter Server . ChannelHandler . Netty(Nio和非阻塞,异步)
    17. Codec 编解码 Dispatcher 分发

Dubbo SPI (获取接口实现类的方式)

概念

  1. Dubbo 内核的工作原理由四部分构成:服务发现机制 SPI、自适应机制 Adaptive、包装机制 Wrapper 与激活机制 Activate。Dubbo 通过这四种机制实现了对插件的 IoC、AOP,实现了对自动生成类的动态编译 Compile。
  2. jdk中不使用spring容器也可以发现接口的实现类

1.jdk spi

  1. 步骤
    1. 代码利用ServiceLoader.load(接口.class)
    2. 在META-INF/services创建与接口全路径名相同的文件
    3. 内容为实现类的全路径名
  2. 代码
 public static void main(String[] args) {
        // load()中放的是业务接口,其就相当于要加载的配置文件名
        ServiceLoader<SomeService> loader = ServiceLoader.load(SomeService.class);
    }
  1. 在META-INF.services目录下添加与接口全路径名相同的文件
com.abc.service.OneServiceImpl
com.abc.service.TwoServiceImpl
  1. 不合理的地方,加载的时候会将全部的类全部加载回来

dubbo spi

  1. 测试类
public void test01() {
    // 获取到用于加载Order类型扩展类实例的extensionLoader实例
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);

    Order alipay = loader.getExtension("alipay");
    System.out.println(alipay.way());

    Order wechat = loader.getExtension("wechat");
    System.out.println(wechat.way());
}
  1. 接口(接口上加SPI注解)
@SPI("wechat")
public interface Order {
    // 支付方式
    String way();
}
  1. 在META-INFO.dubbo.internal文件夹下添加与接口名相同的文件
alipay=com.abc.spi.AlipayOrder
# key值可以定义多个,用逗号隔开
wechat,wechat2=com.abc.spi.WechatOrder
  1. 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串
  2. 提供者配置文件路径:在依次查找的目录为
    1. META-INF/dubbo/internal, META-INF/dubbo,META-INF/services
  3. ExtensionLoader.getExtensionLoader 获取指定接口的loader,保证指定接口的扩展名不重复

Adaptive类. (Adaptive 是选择要使用哪一个具体的实现类)

  1. Adaptive 机制,即扩展类的自适应机制。即其可以指定想要加载的扩展名,也可以不指定。若不指定,则直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive注解实现的。
  2. 有些 SPI 接口中的方法不需要 URL 相关的参数,此时就可以直接让@Adaptivate 来修饰某个 SPI 接口的实现类,由该类实现对 SPI 扩展类的自适应。
  3. dubbo的两个自适应类AdaptiveExtensionFactory, AdaptiveCompiler
  4. adaptive 类
//类上加Adaptive注解
@Adaptive
public class AdaptiveOrder implements Order {
    //设置扩展类的名称
    private String defaultName;

    // 指定要加载扩展类的名称
    public void setDefaultName(String defaultName) {
        this.defaultName = defaultName;
    }

    @Override
    public String way() {
        ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
        Order order;
        if (StringUtils.isEmpty(defaultName)) {
            // 加载SPI默认名称的扩展类
            order = loader.getDefaultExtension();
        } else {
            // 加载指定名称的扩展类
            order = loader.getExtension(defaultName);
        }
        return order.way();
    }
}
  1. 在扩展类的文件中添加adaptive 为key, value为adaptive类全路径的配置
alipay=com.abc.spi.AlipayOrder
wechat=com.abc.spi.WechatOrder
adaptive=com.abc.spi.AdaptiveOrder
  1. 测试类
@Test
public void test01() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    //通过getAdaptiveExtension 获取到的是接口上SPI指定的实现类
    //order = loader.getDefaultExtension();
    Order order = loader.getAdaptiveExtension();
    System.out.println(order.way());
}
  1. getAdaptiveExtension() 没有指定内容,获取到的是AdaptiveOrder 扩展类,该扩展类会getDefaultExtension获取实现类
  2. ((AdaptiveOrder)order).setDefaultName(“alipay”); 获取别的实现类
  3. Adaptive 类不是直接扩展类

Adaptive 方法(自适应)

  1. 被@Adapative 修饰的 SPI 接口中的方法称为 Adaptive 方法。在 SPI 扩展类中若没有找到Adaptive 类,但系统却发现了 Adapative 方法,就会根据 Adaptive 方法自动为该 SPI 接口动态生成一个 Adaptive 扩展类,并自动将其编译。例如 Protocol 接口中就包含两个 Adaptive方法
  2. 其对于要加载的扩展名的指定方式是通过URL 类型的方法参数指定的。所以对于 Adaptive 方法的定义规范仅一条:其参数包含 URL 类型的参数,或参数可以获取到 URL 类型的值。方法调用者是通过 URL 传递要加载的扩展名的。
  3. String extName = url.getParameter(“order”, “wechat”);
    1. order 是 接口的驼峰命名转为xx.xx, wechat 是接口spi注解中的值
    2. GoodsOrder 转为goods.order
  4. 生成自适应类的格式image.png
  5. 接口 接口方法上加Adaptive 注解
@SPI("wechat")
public interface Order {
    String way();

    //在方法上添加Adaptive 注解,方法参数必须有URL参数,或者能获取到URL参数
    @Adaptive
    String pay(URL url);
}

  1. 实现类
public class WechatOrder implements Order {
    //这个方法不能通过自适应类调用,调用会报错
    @Override
    public String way() {
        System.out.println("---  使用微信支付  ---");
        return "微信支付";
    }
    //自适应方法,通过URL选择实现类
    @Override
    public String pay(URL url) {
        System.out.println("---  pay 使用微信支付  ---");
        return "pay 微信支付";
    }
}
  1. 在META-INFO.dubbo.internal文件夹下添加与接口名相同的文件
alipay=com.abc.spi.AlipayOrder
wechat=com.abc.spi.WechatOrder
  1. 测试类
public void test01() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    // 获取Order的自适应类,不是任何一个Order的实现类
    Order order = loader.getAdaptiveExtension();
    //如果没有order=alipay,则取Order接口SPI注解中指定的名称
    URL url = URL.valueOf("xxx://localhost:8080/ooo/jjj?order=alipay");
    //通过解析地址中的order参数来指定实现类,参数名接口的驼峰转为xx.xx
    System.out.println(order.pay(url));
    // way()不是自适应方法,其调用会报错
    // System.out.println(order.way());
}
  1. 生成的class
public class Order$Adaptive implements com.abc.spi.Order {
    public java.lang.String pay(org.apache.dubbo.common.URL arg0) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        //wechat因为order接口注解中指定了
        String extName = url.getParameter("order", "wechat");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (com.abc.spi.Order) name from url (" + url.toString() + ") use keys([order])");
        com.abc.spi.Order extension = (com.abc.spi.Order) ExtensionLoader.getExtensionLoader(com.abc.spi.Order.class).getExtension(extName);
        return extension.pay(arg0);
    }
    
    public java.lang.String way() {
        throw new UnsupportedOperationException("The method public abstract java.lang.String com.abc.spi.Order.way() of interface com.abc.spi.Order is not adaptive method!");
    }
}

Wrapper类

  1. Wrapper 机制,即扩展类的包装机制。就是对扩展类中的 SPI 接口方法进行增强,进行包装,是 AOP 思想的体现,是Wrapper 设计模式的应用。一个 SPI 可以包含多个 Wrapper。
  2. 规范
    1. 该类要实现 SPI 接口
    2. 该类中要有 SPI 接口的引用
    3. 该类中 SPI 接口实例是通过仅包含一个 SPI 接口参数的带参构造器传的
    4. 在接口实现方法中要调用 SPI 接口引用对象的相应方法
    5. 该类名称以 Wrapper 结尾
  3. wrapper类
public class OrderWrapper implements Order {
    //被调用的实际order
    private Order order;

    public OrderWrapper(Order order) {
        this.order = order;
    }

    @Override
    public String way() {
        System.out.println("before-OrderWrapper对way()的增强");
        String way = order.way();
        System.out.println("after-OrderWrapper对way()的增强");
        return way;
    }

    @Override
    public String pay(URL url) {
        //增强调用的方法
        System.out.println("before-OrderWrapper对pay()的增强");
        //实际调用的类
        String pay = order.pay(url);
        System.out.println("after-OrderWrapper对pay()的增强");
        return pay;
    }
}
  1. 在META-INFO.dubbo.internal文件夹下添加与接口名相同的文件
    alipay=com.abc.spi.AlipayOrder
    wechat=com.abc.spi.WechatOrder
    wrapper=com.abc.spi.OrderWrapper

3. wrapper类不是直接扩展类
4. Set<String> supportedExtensions = loader.getSupportedExtensions();   获取到该SPI接口的所有直接扩展类,能够独立使用的实现类为直接扩展类
5. 增强类配置上了都会自动增强
<a name="DtJFG"></a>
### Activate(激活,想要一次性激活多个实现类)

1. Activate注解
   1. group:为扩展类指定所属的组别,是当前扩展类的一个标识。String[]类型,表示一个扩展类可以属于多个组。 
   2. value:为当前扩展类指定的 key,是当前扩展类的一个标识,配置文件中的key。String[]类型,表示一个扩展类可以有多个指定的 key。 
   3. order:指定筛选条件相同的扩展类的加载顺序。序号越小,优先级越高, 越先加载默认值为 0。 
2. 实现类
```java

// @Activate的order属性默认为0,order属性越小,其优先级越高
// @Activate的group属性与value属性均是用于给当前扩展类添加的标识
// group是一个“大范围标识”,value为一个“小范围标识”。
// 一个扩展类一旦指定了小范围标识value,那么这个大范围标识就不再起作用了
//如果要制定group的扩展类,如果有vlaue属性,则group中不包含指定value的实例类
@Activate(group = {"online","offline"}, value = "alipay")
public class AlipayOrder implements Order {
    @Override
    public String way() {
        System.out.println("---  使用支付宝支付  ---");
        return "支付宝支付";
    }
}

  1. 接口
@SPI("wechat")
public interface Order {
    String way();
}
  1. 在META-INFO.dubbo.internal文件夹下添加与接口名相同的文件
alipay=com.abc.spi.AlipayOrder
wechat=com.abc.spi.WechatOrder
  1. 测试
@Test
public void test03() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    URL url = URL.valueOf("xxx://localhost:8080/ooo/jjj?ooo=alipay");

    // getActivateExtension()的参数二与三的关系是“或”
    // 参数二指定的是要激活的value,vlaue 指定的是url的key
    // 参数三指定的是要激活的group
    List<Order> online = loader.getActivateExtension(url, "ooo", "online");

    // 一次性激活(加载)一批扩展类实例,这里指定的是激活所有group为“online"的扩展类
    List<Order> online = loader.getActivateExtension(url, "", "online");

    for (Order order : online) {
        System.out.println(order.way());
    }
}
  1. 激活类是直接扩展类

四类扩展

  1. 普通扩展类
  2. adaptive类 类上有@Adaptive 注解的
  3. wrapper类 构造方法中有接口的
  4. activate类 实现类上有@Activate注解的
  5. 在配置文件中可能会存在四种类:普通扩展类、Adaptive 类、Wrapper 类,及 Activate 类
  6. 共同点 : 都实现了 SPI 接口。
  7. 不同点:
    1. 定义方式:Adaptive(自适应) 类与 Activate(激活) 类都是通过注解定义的。
    2. 数量:一个 SPI 接口的 Adaptive 类(无论是否是自动生成的)只会有一个;Wrapper 类与 Activate 类是可以有多个的。
    3. 直接扩展类:只有普通扩展类与 Activate 类是直接扩展类,Adaptive 类与 Wrapper 类不 是直接扩展类

调用的的方法名

  1. Order order = getExtension(“”) 获取到指定名称的实现类
  2. Order order = getAdaptiveExtension(Url url) 根据Url自适应获取到自适应类,一个接口只能对应一个自适应类,如果配置文件中定义了Adaptive类,则返回自定义的自适应类,如果没有自己定义,dubbo会自动生成自适应类,在dubbo生成的自适应类中,调用具体的方法时,会根据URL中参数内容获取到具体的实现类,然后调用具体实现类的方法
  3. List orderList = getActivateExtension(Url url, String key ,String group) 根据条件获取到实现类集合 ,key 对应的是Activate 注解中的value, group对应的是Activate 注解中的group

SPI源码

  1. ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Order.class); 获取实现类加载器
  2. !type.isInterface() 若type不是接口,则抛出异常
  3. !withExtensionAnnotation(type) 若type接口没有被@SPI注解修饰,则抛出异常
  4. ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS. extension缓存map
  5. EXTENSION_LOADERS.get(type). 从缓存map中获取extension对象
  6. loader == null ==> EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)) 创建ExtensionLoader并放入缓存map
  7. new ExtensionLoader ==> this.type = type. 设置type属性
  8. objectFactory. 设置对象工厂, 用于创建当前type中指定“功能前辍”扩展名的实例
  9. type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension
    1. 如果type为ExtensionFactory , 则返回null,如果type不为null返回ExtensionFactory 的自适应类
  10. ExtensionFactory 的实现类.
  11. AdaptiveExtensionFactory
  12. SpiExtensionFactory
  13. SpringExtensionFactory
  14. getAdaptiveExtension()
  15. Holder cachedAdaptiveInstance = new Holder<>(). cachedAdaptiveInstance. 缓存者自适应实例的缓存. 双重检测锁
  16. Object instance = cachedAdaptiveInstance.get(). 从缓存中获取实例
  17. instance = createAdaptiveExtension() 创建自适应实例
  18. injectExtension((T) getAdaptiveExtensionClass().newInstance())
  19. getAdaptiveExtensionClass() 获取adaptive类,是个.class
  20. newInstance() 是调用这个.class的无参构造器创建一个adaptive实例
  21. injectExtension() 完成adaptive实例的IoC注入
  22. getAdaptiveExtensionClass. ==> Class<?> 获取自适应类,返回结果是一个class
  23. getExtensionClasses. 将当前SPI接口的所有扩展类(四类:普通扩展类、adaptive类、wrapper类及activate类). 全部加载并进行缓存
  24. Holder<Map<String, Class<?>>> cachedClasses. 直接扩展类的缓存 (普通扩展类和activate 类激活类) key为类前缀key,value为class
  25. Map<String, Class<?>> classes = cachedClasses.get() 获取直接扩展类的缓存
  26. classes = loadExtensionClasses() 加载并缓存直接扩展类
  27. cacheDefaultExtensionName(). 加载并缓存SPI接口的默认扩展类名称
    1. final SPI defaultAnnotation = type.getAnnotation(SPI.class). 从SPI接口中获取到SPI注解
    2. String value = defaultAnnotation.value(). 获取SPI注解的value属性
    3. String[] names = NAME_SEPARATOR.split(value). 使用逗号分隔value属性值
    4. names.length > 1 throw exception 如果分割出了多个路径抛出异常
    5. cachedDefaultName = names[0] 设置默认的名称(key) SPI注解中的value
  28. for (LoadingStrategy strategy : strategies). 从三种路径中将配置文件中的类加载并缓存
  29. loadDirectory 加载目录
  30. String fileName = dir + type; 拼接出文件名
  31. Enumeration<java.net.URL> urls. 将配置文件加载并转换为URL,加载出来的文件是多个在不同的模块下是可能存在相同文件的
  32. while (urls.hasMoreElements()) 遍历urls
  33. loadResource. 加载配置文件内容
  34. while ((line = reader.readLine()) != null). 逐行解析配置文件
  35. name = line.substring(0, i).trim(). 解析出功能性扩展名
  36. clazz = line.substring(i + 1).trim(). 解析出扩展类名
  37. loadClass(extensionClasses, resourceURL, …) 加载这个类
  38. !type.isAssignableFrom(clazz). 判断当前clazz是否实现了当前的SPI接口类型type
  39. clazz.isAnnotationPresent(Adaptive.class). 判断当前clazz类上是否出现了@Adaptive注解
  40. cacheAdaptiveClass(clazz, overridden). 缓存这个clazz
    1. if (cachedAdaptiveClass == null || overridden)
    2. cachedAdaptiveClass = clazz; 自适应扩展类的缓存,只能有1个,多的话抛出异常
    3. 若当前缓存中的类与clazz不相同,则抛出异常,为什么?一个SPI接口只允许有一个Adaptive类,无论是自定义的,还是自动生成的
  41. isWrapperClass(clazz). 判断当前类是否是wrapper类
    1. clazz.getConstructor(type). 获取当前clazz的单参构造器,且这个参数为SPI类型,若没有抛出,则说明当前clazz不是个wrapper类
  42. cacheWrapperClass(clazz). 缓存这个clazz
    1. Set<Class<?>> cachedWrapperClasses
    2. cachedWrapperClasses = new ConcurrentHashSet<>()
    3. cachedWrapperClasses.add(clazz). 一个SPI允许有多个wrapper类
  43. 对直接扩展类(普通扩展类与activate类)情况的处理
  44. clazz.getConstructor(). 验证当前clazz是否具有无参构造器。若没有,则直接抛出异常。若有,则当前clazz是扩展类,SPI直接扩展类要求,必须要有无参构造器
  45. StringUtils.isEmpty(name). 若功能性扩展名为空,则为其找一个
  46. name = findAnnotationName(clazz). 找一个扩展名
    1. Extension extension = clazz.getAnnotation. 获取当前类的@Extension注解
    2. return extension.value(); 若注解不空,则让注解的value值做扩展名
    3. String name = clazz.getSimpleName(); 若没有注解,获取当前clazz的简单类名
    4. name = name.substring(0, name.length() - type.getSimpleName().length()). 若类名是以SPI接口结尾,则截取类名中SPI接口名以外的部分,例如AlipayOrder截取出的是Alipay
    5. name.toLowerCase(). 若clazz类名不是以SPI结尾,则直接返回类名,全小写字母
  47. String[] names = NAME_SEPARATOR.split(name); 使用逗号将扩展名分隔为多个
  48. cacheActivateClass(clazz, names[0]); 仅使用第一个扩展名来缓存activate类 (激活类)
    1. Activate activate = clazz.getAnnotation(Activate.class). 获取clazz上的@Activate注解
    2. Map<String, Object> cachedActivates. 激活类缓存
    3. cachedActivates.put(name, activate) 将直接扩展类的第一个名称和activate激活类注解放入缓存
  49. for (String n : names) 遍历所有扩展名
  50. cacheName(clazz, n) 缓存名称
    1. ConcurrentMap<Class<?>, String> cachedNames. 类名称的缓存,key为class,value为第一个直接扩展类的名称
    2. cachedNames.put(clazz, name); 将第一个扩展名与clazz配对后缓存
  51. saveInExtensionClass(extensionClasses, clazz, n, overridden). 缓存扩展类的所有指定的扩展名
    1. Map<String, Class<?>> extensionClasses. 直接扩展类缓存map ,key为名称,value为class 2. Class<?> c = extensionClasses.get(name); 从缓存中获取名称对应的class
    3. extensionClasses.put(name, clazz); 将名称和对应的直接扩展类class放入缓存map
    4. else if (c != clazz). ==>. throw new IllegalStateException. 一个扩展名不能对应多个扩展类
  52. cachedAdaptiveClass != null ==> return cachedAdaptiveClass 如果缓存的自适应class不为空,返回缓存的自适应扩展类
  53. cachedAdaptiveClass = createAdaptiveExtensionClass(). 若缓存中没有adaptive类,则创建一个,创建adaptive类(要求必须要有adaptive方法)???
  54. getExtension(String name) 根据名称获取扩展类
  55. T extension = getExtension(name, true). 第二个参数 boolean wrap, 是否使用增强
  56. getExtension(String name, boolean wrap)
    1. “true”.equals(name). 若name指定为“true”,则加载SPI默认扩展名的实例
    2. return getDefaultExtension(). 获取默认扩展名的实例
      1. getExtensionClasses(); 加载并缓存“四类”,获取并缓存SPI接口的默认扩展名
      2. getExtension(cachedDefaultName). 加载默认扩展名的实例
    3. ConcurrentMap<String, Holder> cachedInstances. key名称和对应实例的缓存
    4. Holder holder = cachedInstances.get(name). 从缓存中获取指定name对应的扩展类实例的持有者holder
    5. holder == null ==> cachedInstances.putIfAbsent(name, new Holder<>()). holder = cachedInstances.get(name);
  57. instance = createExtension(name, wrap) 创建直接扩展类的实例
  58. Class<?> clazz = getExtensionClasses().get(name); 获取name的直接扩展类class
  59. ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES 实例的缓存,key为class,value为该class对应的实例对象
  60. T instance = (T) EXTENSION_INSTANCES.get(clazz); 从缓存中获取该clazz对应的instance实例
  61. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance()); 实例为null,则创建一个实例,放入缓存
  62. injectExtension(instance). 完成实例的IoC注入,设置属性

**插件的 IOC 注入源码解析 **

  1. dubbo 的注入: set方法设置的对象为SPI接口,就会自动在系统中寻找该对象的自适应对象设置进去
  2. 两种注册方式,通过dubbo的SPI找到自适应类,设置到属性上,通过Spring从容器中获取到实例,然后设置到属性上
  3. 以注册中心通信协议 RegsitryProtocol 实例注入具体通信协议 Protocol 为例来解析IoC 的过程
  4. InterfaceCompatibleRegistryProtocol. 为Protocol 的实现类, 有setProtocol方法,该set方法需要将Protocol接口通过SPI自适应,将实现类对象设置到RegistryProtocol 的属性上
  5. ReferenceConfig #. createProxy
    1. invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)). REF_PROTOCOL==> Protocol$Adaptive
    2. InterfaceCompatibleRegistryProtocol. register 注册中心的实现类
  6. Protocol$Adaptive ==> getExtension(extName). extName == register
  7. injectExtension(instance). 完成实例的IoC注入,设置属性
    1. for (Method method : instance.getClass().getMethods()) 遍历当前实例类的所有方法
    2. if (!isSetter(method)) {. 当前方法不是set方法,跳过
    3. method.getAnnotation(DisableInject.class) != null 若当前setter方法上被@DisableInject修饰,则说明当前setter不会自动注入,则跳过
    4. Class<?> pt = method.getParameterTypes()[0]. 获取当前setter的参数类型
    5. ReflectUtils.isPrimitives(pt) ==> continue; 若当前参数类型为基本数据类型,则跳过, 即Dubbo的自动注入,只会注入对象
    6. String property = getSetterProperty(method). 获取setter方法的参数名
    7. Object object = objectFactory.getExtension(pt, property). 创建当前setter的实参对象值。其依次尝试着使用SPI与Spring容器方式创建。扩展类工厂获取扩展类
      1. AdaptiveExtensionFactory. ==> getExtension.
      2. for (ExtensionFactory factory : factories). 依次通过SPI与Spring两种方式获取实例
      3. SpiExtensionFactory. ==> getExtension(Class type, String name). 利用Dubbo的SPI获取实例
        1. type.isInterface() && type.isAnnotationPresent(SPI.class). 若当前type为SPI接口
        2. ExtensionLoader loader = ExtensionLoader.getExtensionLoader(type); 获取当前type的扩展类加载器
        3. !loader.getSupportedExtensions().isEmpty() 如果当前类的直接扩展类不为空
        4. loader.getAdaptiveExtension() 返回当前类的自适应实现类
      4. SpringExtensionFactory. ==> getExtension(Class type, String name) 通过spring容器获取到实例
        1. if (type.isInterface() && type.isAnnotationPresent(SPI.class)). ==> return null; 若当前type为SPI接口,则直接结束
        2. for (ApplicationContext context : CONTEXTS) {. 遍历所有Spring容器
        3. T bean = BeanFactoryUtils.getOptionalBean(context, name, type); 先从当前遍历容器中尝试着获取指定name的bean,若没有,则再尝试着获取指定type的bean
    8. method.invoke(instance, object). 调用setter,完成注入

Dubbo的AOP包装源码解析

  1. Dubbo 的 AOP 是对 SPI 扩展类进行增强的方式,而 Wrapper 机制就是对 SPI 扩展类的增强。不同 SPI 的不同 Wrapper,其增强的功能不同
  2. getExtensionLoader 会加载配置文件中的内容,将普通扩展类,自适应扩展类,wrapper包装类和activate激活类进行缓存
  3. getAdaptiveExtension 获取到的是自适应扩展类
  4. getExtension 根据名称获取到具体的实现类
  5. Class<?> clazz = getExtensionClasses().get(name). 根据名称获取class
  6. clazz.getDeclaredConstructor().newInstance() 创建class对应的实例
  7. if (wrap) 是否增强
  8. List<Class<?>> wrapperClassesList = new ArrayList<>(). 将缓存中的wrapper set集合写入到list,并重新排序
  9. wrapperClassesList.addAll(cachedWrapperClasses) 将缓存的包装类放入到集合中
  10. for (Class<?> wrapperClass : wrapperClassesList). 遍历所有wrapper
  11. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
  12. wrapperClass.getConstructor(type) 获取当前wrapper的单参构造器
  13. newInstance(instance) 调用单参构造器创建实例
  14. injectExtension() 调用wrapper的setter完成IoC注入

自动生成类的动态编译源码解析

  1. javassist: Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。一般情况下,对字节码文件进行修改是需要使用虚拟机指令的。而使用 Javassist,可以直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
package com.abc;

import javassist.*;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Modifier;

public class JavassistCompiler {
    public static void main(String[] args) throws Exception {
        // 获取CtClass实例的工具类
        ClassPool pool = ClassPool.getDefault();
        // CtClass,Class Type Class,字节码类型的class类
        CtClass ctClass = genericClass(pool);
        // 生成相应实例,并调用其业务方法
        invokeInstance(ctClass);
    }

    private static CtClass genericClass(ClassPool pool) throws Exception {
        // 通过ClassPool生成一个public的com.abc.Person类的ctClass
        CtClass ctClass = pool.makeClass("com.abc.Person");

        // 下面都是对这个ctClass的初始化

        // 添加private String name;属性
        CtField nameField = new CtField(pool.getCtClass("java.lang.String"), "name", ctClass);
        nameField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(nameField);

        // 添加private int age;属性
        CtField ageField = new CtField(pool.getCtClass("int"), "age", ctClass);
        ageField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(ageField);

        // 添加getter and setter
        ctClass.addMethod(CtNewMethod.getter("getName",nameField));
        ctClass.addMethod(CtNewMethod.setter("setName",nameField));
        ctClass.addMethod(CtNewMethod.getter("getAge",ageField));
        ctClass.addMethod(CtNewMethod.setter("setAge",ageField));

        // 添加无参构造器
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
        String body = "{\nname=\"zhangsan\";\nage=23;\n}";
        ctConstructor.setBody(body);
        ctClass.addConstructor(ctConstructor);

        // 添加业务方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "personInfo", new CtClass[]{}, ctClass);
        ctMethod.setModifiers(Modifier.PUBLIC);
        StringBuffer buffer = new StringBuffer();
        buffer.append("{\nSystem.out.println(\"name=\"+name);\n");
        buffer.append("System.out.println(\"age=\"+age);\n}");
        ctMethod.setBody(buffer.toString());
        ctClass.addMethod(ctMethod);

        // 把生成的字节码文件写入的位置
        byte[] bytes = ctClass.toBytecode();
        File file = new File("D:\\Person.class");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(bytes);
        fos.close();

        return ctClass;
    }

    private static void invokeInstance(CtClass ctClass) throws Exception {
        // 完成真正的编译,形成字节码
        Class<?> clazz = ctClass.toClass();
        // 创建实例
        Object instance = clazz.newInstance();
        // 调用personInfo()业务方法
        instance.getClass()
                .getMethod("personInfo", new Class[]{})
                .invoke(instance, new Object[]{});
    }
}

  1. cachedAdaptiveClass = createAdaptiveExtensionClass(). 创建自适应类
  2. String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate() 生成自适应的code数据
    1. !hasAdaptiveMethod() 若没有@Adaptive,则直接抛出异常
    2. StringBuilder code = new StringBuilder(). 拼接代码字符串
  3. compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension() 获取compile的自适应类
  4. compiler.compile(code, classLoader). 调用自适应compiler完成自动编译
  5. AdaptiveCompiler # compile
  6. ExtensionLoader.getExtensionLoader(Compiler.class). 获取Compiler接口的扩展类加载器
  7. compiler = loader.getDefaultExtension() 获取默认的实现类. ==>. JavassistCompiler
  8. compiler.compile(code, classLoader) 调用AbstractCompiler的compile()
  9. AbstractCompiler # compile. 父类的compile方法
  10. 获取package信息. ==> 获取class信息 ==> 拼接出类的全限定性类名. ==> 加载指定的类. ==>如果没有该Class类
  11. doCompile(className, code). 调用子类的doCompile
  12. JavassistCompiler # doCompile
  13. CtClass cls = builder.build(classLoader). 使用构建器构建出ctClass实例
  14. cls.toClass. 完成真正的编译,形成字节码

ExtensionLoader 中的缓存

  1. Map<String, Object> cachedActivates 缓存激活类
  2. Set<Class<?>> cachedWrapperClasses wrapper 类缓存
  3. ConcurrentMap<Class<?>, String> cachedNames class 和名称key的缓存
  4. Holder<Map<String, Class<?>>> cachedClasses . 名称和class的缓存
  5. Map<String, Class<?>> extensionClasses 名称和class(cachedClasses 中存放的内容)
  6. Class<?> cachedAdaptiveClass 自适应类的缓存
  7. ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES 扩展实例 key为class,value为实例
  8. ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS . loader的cache key为class对象,value 为 ExtensionLoader(里边有各种缓存)

Dubbo 源码

Dubbo与Spring整合

  1. spring.handlers. 存放的是命名空间的处理器
  2. spring.schemas 存放的是命名空间的dtd,xsd文件
  3. DubboNamespaceHandler # init() 注册了各种标签的解析器
  4. DubboBeanDefinitionParser # parse
    1. element. 当前要解析的标签
    2. parserContext. 解析上下文,其中包含了当前配置文件中所有其它标签的解析信息
    3. beanClass. 当前标签解析出的内容要封装的类,其中就是存放的从标签中读出的属性。读到了什么就录下什么
    4. registered. 解析出的标签是否需要注解到注册中心
    5. return 解析对象。将读取出的数据最终要构建出一个“逻辑”上的对象。例如,构建出一个“注册中心”对象、“协议”对象等
  5. RootBeanDefinition beanDefinition = new RootBeanDefinition(); 创建并初始化解析对象
  6. String configId = resolveAttribute. 解决id问题: 为空与重复问题
  7. String beanName = configId; bean名称取id属性值
  8. String prefix = beanClass.getName(). 如果Id属性为空,取类名与数字的拼接,用# 进行拼接
  9. beanDefinition.setAttribute(BEAN_NAME, beanName). 设置beanName
  10. ProtocolConfig.class.equals(beanClass). 对特殊标签的处理,对dubbo:protocol/标签的处理
  11. ServiceBean.class.equals(beanClass). 对dubbo:service/标签的处理
  12. Map<String, Class> beanPropTypeMap = beanPropsCache.get(beanClass.getName()). 对普通标签的普适性处理

Dubbo中的中的重要接口

  1. Invocation. 调用信息. 方法名,参数, attachments 附件信息,键值对
  2. Invoker<?> getInvoker().
    1. Invoker 在提供者端为提供者的代理对象,消费端,为提供者的委托对象,从注册中心获取到接口进行调用
    2. Class getInterface(). 代理的接口Class
    3. Result invoke(Invocation invocation). 进行方法调用
  3. Node. ===> URL getUrl() url参数,用作自适应
  4. Exporter. 服务暴露对象,里边封装了Inovker. ==> getInvoker. ===> unexport. 和服务暴露协议有关
  5. DubboExporter. dubbo服务暴露
    1. Map<String, Exporter<?>> exporterMap 服务暴露map key 为group/interface:version.value 为Exporter 暴露对象,里边封装了Invoker
  6. InjvmExporter jvm服务暴露协议
    1. Map<String, Exporter<?>> exporterMap. ==> exporterMap.put(key, this). 将当前exporter写入到缓存map
  7. Directory. 维护着相同服务的invoker列表
    1. Class getInterface()
    2. List<Invoker> list(Invocation invocation) 列出interface对应服务的提供者(进行了过滤)
    3. List<Invoker> getAllInvokers(). 获取所有的提供者
    4. DynamicDirectory implements NotifyListener 动态列表,实现了NoticeListener ,通过watcher机制,服务提供者发生变更,能够通知到
      1. RegistryDirectory. ServiceDiscoveryRegistryDirectory 属于动态列表
    5. StaticDirectory 静态列表, 启动后,注册中心获取到的invoker列表是不会变的
      1. MockDirectory 属于静态列表

服务发布

  1. 主要两个内容: 一个是注册到注册中心,一个是把provider封装为DubboExporter放到本地缓存
  2. 本地服务暴露,消费者和提供者都在本地
  3. 服务暴露对象
    1. DubboExporter
    2. InjvmExporter
  4. DubboBootstrapApplicationListener. # onApplicationEvent. 当Spring容器创建时会触发该方法的执行
  5. event instanceof ContextRefreshedEvent. 容器刷新事件
  6. dubboBootstrap.start(). 启动Dubbo
  7. exportServices() 服务者, 服务暴露
    1. exportMetadataService() 暴露元数据信息
    2. registerServiceInstance() 注册服务实例
    3. referServices() 消费者, 服务引用
  8. ServiceConfigBase sc : configManager.getServices(). 遍历当前配置文件中的所有dubbo:service/标签
  9. sc.shouldExportAsync(). 判断是否是异步暴露
    1. Boolean shouldExportAsync = getExportAsync(). 获取dubbo:service/中的export-async属性
    2. shouldExportAsync == null. 消费端的该属性为空,获取提供者端的属性
    3. shouldExportAsync = provider != null && provider.getExportAsync() != null && provider.getExportAsync(). 获取dubbo:provider/标签中的export-async属性
  10. dubbo 中属性的优先级image.png
  11. 消费端中没有获取服务端中的值
  12. sc.export(); 服务暴露
  13. this.shouldExport() && !this.exported. 若dubbo:service/的export属性为true,且当前服务尚未暴露
  14. doExport(). 服务暴露
  15. StringUtils.isEmpty(path). 若dubbo:servcie/的path属性为空,则取interface属性值, URL的格式 protocol://ip:port/path?a=b&b=c&…
  16. doExportUrls(); 假设有3个注册中心,2个服务暴露协议,为每个服务暴露协议在每个注册中心中进行暴露
  17. dubbo 支持多服务暴露协议
  18. Dubbo也支持多注册中心
  19. 为每个暴露协议在每个注册中心进行暴露
  20. List registryURLs = ConfigValidationUtils.loadRegistries(this, true). 获取所有注册中心的【标准化地址URL】与【兼容性地址URL】, true 表示为provider
  21. ApplicationConfig application = interfaceConfig.getApplication(). 获取dubbo:application/标签
  22. List registries = interfaceConfig.getRegistries(). 获取dubbo:registry/标签
  23. for (RegistryConfig config : registries) { 遍历所有dubbo:registry/标签
  24. String address = config.getAddress(). 获取dubbo:registry/标签的address属性
  25. Map<String, String> map = new HashMap<String, String>(). 创建并初始化一个map,这个map中的值为dubbo:registry/标签及相当标签中的属性值
  26. AbstractConfig.appendParameters(map, application); 将dubbo:application/标签属性写入map
  27. AbstractConfig.appendParameters(map, config); 将dubbo:registry/标签属性写入map
  28. List urls = UrlUtils.parseURLs(address, map) 构建注册中心标准URl
    1. String[] addresses = REGISTRY_SPLIT_PATTERN.split(address). 使用分号分隔address,分隔出多个注册中心地址
    2. for (String addr : addresses) { 遍历所有注册中心地址
    3. parseURL(String address, Map<String, String> defaults) 解析注册中心地址
    4. address.contains(“😕/”) || address.contains(URL_PARAM_STARTING_SYMBOL). 若address本身包含://或?,那么其本身就是一个URL,是一个标准地址形式.
    5. 标准的地址形式 zookeeper://localhost1:2181?backup=localhost2:2181,localhost3:2181
    6. String[] addresses = COMMA_SPLIT_PATTERN.split(address); 使用逗号分隔出一个注册中心集群中的所有主机地址
  29. addParameter(REGISTRY_KEY, url.getProtocol()). 向URL中添加registry属性,例registry=zookeeper
  30. setProtocol(extractRegistryType(url)). 将URL的协议修改为registry. 将zookeeper,redis…注册中心,开头变为registry,将具体的注册中心放到registry 地址参数中
  31. registryList.add(url) 将url添加到registryList 注册地址list中
  32. genCompatibleRegistries(registryList, provider). 为每个标准URL生成一个兼容URL(标准url和兼容url的协议不一样,兼容url(service-discovery-registry)的目的为了服务发现)
    1. if (provider) { 如果是provider
    2. SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol(). 处理URL本身就是以service-discovery-registry开头的情况
    3. result.add(registryURL)
    4. 处理URL不是以service-discovery-registry开头的情况
    5. setProtocol(SERVICE_REGISTRY_PROTOCOL). 设置URL的protocol为service-discovery-registry
address="zookeeper://localhost1:2181?backup=localhost2:2181,localhost3:2181"
protocol="zookeeper" address="localhost1:2181,localhost2:2181
//用分号隔开表示两个注册中心,逗号隔开说明在一个注册中心中
address="zookeeper://localhost1:2181?backup=localhost2:2181; zookeeper://localhost3:2181?backup=localhost4:2181
protocol="zookeeper" address="localhost1:2181,localhost2:2181; localhost3:2181,localhost4:2181"

  1. for (ProtocolConfig protocolConfig : protocols) 遍历所有服务暴露协议(dubbo:protocol/)
  2. doExportUrlsFor1Protocol(protocolConfig, registryURLs). 使用当前遍历的服务暴露协议与所有注册中心配对进行服务暴露 ,1个暴露协议对应多个注册中心
  3. Map<String, String> map = buildAttributes(protocolConfig). 构建服务暴露URL要使用的map
  4. serviceMetadata.getAttachments().putAll(map). 元数据注册
  5. URL url = buildUrl(protocolConfig, registryURLs, map). 构建服务暴露URL
  6. exportUrl(url, registryURLs). 服务暴露
    1. 本地暴露: 将InjvmExporter 写入到缓存map中
    2. 有注册中心的远程暴露: 将DubboExporter 写入到缓存map中(与netty进行关联),连接zookeeper并向注册中心创建临时节点
    3. 没有注册中心的远程暴露: 将DubboExporter 写入到缓存map中(与netty进行关联)
    4. String scope = url.getParameter(SCOPE_KEY). 获取dubbo:service/的scope属性
    5. !SCOPE_NONE.equalsIgnoreCase(scope). 若scope的值不等于none,则进行暴露
    6. !SCOPE_REMOTE.equalsIgnoreCase(scope). 若scope的值不等于remote,则进行本地暴露
    7. exportLocal(url). 本地暴露
      1. setProtocol(LOCAL_PROTOCOL). 将URL的protocol设置为injvm
      2. setPort(0) URL没有端口号
      3. doExportUrl(local, false). 本地暴露
        1. Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url); 构建出invoker
        2. Exporter<?> exporter = PROTOCOL.export(invoker). 本地暴漏
        3. Protocol$Adaptive. # extension.export(arg0)
        4. ProtocolListenerWrapper # export
        5. InjvmProtocol. # export
        6. new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap). 创建InjvmExporter 并将该对象放入exporterMap中
        7. exporterMap.put(key, this). 将当前exporter写入到缓存map
        8. InjvmProtocol. 中维护着. Map<String, Exporter<?>> exporterMap. key 为serviceKey ,value 为InjvmExporter
    8. !SCOPE_LOCAL.equalsIgnoreCase(scope). 若scope的值不等于local,则进行远程暴露
    9. url = exportRemote(url, registryURLs) 远程暴露
      1. if (CollectionUtils.isNotEmpty(registryURLs)) {. 处理有注册中心的情况
      2. for (URL registryURL : registryURLs) 遍历所有注册中心URL
      3. doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true) 远程暴露(仅跟踪registryURL地址为 registry://… 的地址)
        1. Invoker<?> invoker = PROXY_FACTORY.getInvoker 构建出invoker
        2. Protocol$Adaptive # extension.export(arg0)
        3. ProtocolFilterWrapper # export
        4. UrlUtils.isRegistry(invoker.getUrl()). 地址是注册
        5. protocol.export(invoker). 进行服务注册
          1. RegistryProtocol # export. 注册中心进行注册
          2. URL registryUrl = getRegistryUrl(originInvoker) 获取注册中心URL,将地址复原zookeeper 开头
            1. String protocol = registryUrl.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY); 获取regsitry属性值,这里的值为zookeeper
            2. registryUrl = registryUrl.setProtocol(protocol).removeParameter(REGISTRY_KEY); 将URL中的protocol由registry替换为zookeeper,并将registry属性删除
          3. URL providerUrl = getProviderUrl(originInvoker) 获取服务暴露URL,要写到zookeeper中provider的地址
            1. image.png
            2. Object providerURL = originInvoker.getUrl().getAttribute(EXPORT_KEY); 获取URL中的export属性值,即要暴露的服务的URL.
          4. final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl). 服务暴露,即将服务的exporter写入到缓存map
            1. protocol.export(invokerDelegate)
            2. Protocol$Adaptive # extension.export
            3. extName = dubbo
            4. ProtocolListenerWrapper # export ==> protocol.export(invoker)
            5. DubboProtocol # export
            6. DubboExporter exporter = new DubboExporter(invoker, key, exporterMap) 将invoker封装为了DubboExporter. DubboExporter 需要与NettyServer进行绑定,别的服务需要通过它进行提供者的调用
            7. exporterMap.put(key, exporter). 将exporter写入到缓存map
            8. openServer(url). 创建并启动Netty Server
              1. String key = url.getAddress(); key格式 ip:暴露协议端口 例如,192.168.59.1:20881
              2. boolean isServer = url.getParameter(IS_SERVER_KEY, true). 表示当前应用是否是provider
              3. ProtocolServer server = serverMap.get(key) 从缓存map中获取当前key对应的同异步转换对象。从key的值可知,一个应用中每个服务暴露协议都会对应一个同异步转换对象,而一个同异步转换对象会对应一个Netty Server ProtocolServer 与Exchange 对应
              4. 一个主机中每个服务暴露协议会对应创建一个Netty Server
              5. serverMap.put(key, createServer(url)). 创建同异步转换对象
                1. Exchangers.bind(url, requestHandler)
                2. new HeaderExchangeServer. 转换服务器
                3. Transporter$Adaptive # extension.bind(). extName 默认为netty
                4. new NettyServer(url, handler)
                5. AbstractServer # 构造方法. ==> doOpen() 创建并启动Netty Server
                6. NettyServer. #. doOpen. netty的代码
                7. ChannelFuture channelFuture = bootstrap.bind(getBindAddress()). 启动Netty Server
                8. 20881 netty服务绑定的地址
          5. Registry registry = getRegistry(registryUrl). 获取注册中心实例
            1. RegistryFactory$Adaptive # extension.getRegistry(arg0)
            2. AbstractRegistryFactory # getRegistry
            3. Registry registry = REGISTRIES.get(key). 从缓存获取当前服务注册中心URL对应的实例。 key为zookeeper://zookeeperOS:2181/org.apache.dubbo.registry.RegistryService
            4. registry = createRegistry(url). 创建注册中心实例
            5. ZookeeperRegistryFactory # createRegistry
            6. new ZookeeperRegistry(url, zookeeperTransporter)
            7. ZookeeperRegistry # 构造方法
              1. zkClient = zookeeperTransporter.connect(url). 创建zk客户端,与zk进行连接
            8. REGISTRIES.put(key, registry) 放入到缓存中
          6. register(registry, registeredProviderUrl). 注册到注册中心
            1. doRegister(url) 进行注册
            2. zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true)). 创建一个临时节点
            3. /dubbo/org.apache.dubbo.demo.GreetingService/providers/dubbo%3A%2F%2F127.0.0.1%3A20881%2Forg.apache.dubbo.demo.GreetingService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Dgreeting%26interface%3Dorg.apache.dubbo.demo.GreetingService%26metadata-type%3Dremote%26methods%3Dhello%26pid%3D7212%26release%3D%26revision%3D1.0.0%26service-name-mapping%3Dtrue%26side%3Dprovider%26timestamp%3D1717233419615%26version%3D1.0.0. 注册的节点信息
      4. 处理没有注册中心的情况
      5. doExportUrl(url, true) 远程暴露(与上面的暴露相比,仅没有向注册中心注册)直连的方式,消费端把url地址写死的情况,会通过DubboExporter 与NettyServer进行关联让其它服务进行调用

服务订阅-创建consumer的代理对象

  1. DubboBootstrapApplicationListener. # onApplicationEvent. 当Spring容器创建时会触发该方法的执行
  2. event instanceof ContextRefreshedEvent. 容器刷新事件
  3. dubboBootstrap.start(). 启动Dubbo
  4. referServices()
  5. configManager.getReferences(). 遍历所有dubbo:reference/标签
  6. rc.shouldInit(). 判断当前dubbo:reference/标签引用的实例是否需要立即初始化
    1. Boolean shouldInit = isInit(); 获取标签的init属性
    2. shouldInit == null && getConsumer() != null. shouldInit = getConsumer().isInit(). 若init为空,则获取dubbo:consumer/中的init属性
  7. cache.get(rc); 处理同步引用
  8. String key = generator.generateKey(referenceConfig). 生成服务标识key,其格式为 group/interface:version
  9. Class<?> type = referenceConfig.getInterfaceClass(). 获取业务接口class
  10. ConcurrentMap<String, Object> proxiesOfType = proxies.computeIfAbsent(type, _t -> new ConcurrentHashMap<>())
  11. 从缓存map中获取当前业务接口的内层map,即获取到当前业务接口的所有服务
  12. proxies为一个缓存map,其为一个双层map。该缓存map中存放的是业务接口对应的所有服务
  13. 外层map的key为业务接口class,value为一个内层map
  14. 内层map的key为服务标识key(group/interface:version),value为该服务对应的代理对象

// 返回内层map的value,即当前服务标识对应的代理对象
return (T) proxiesOfType.computeIfAbsent(key, _k -> {
    // 创建代理对象
    Object proxy = referenceConfig.get();
    referredReferences.put(key, referenceConfig);
    return proxy;
});
  1. Object proxy = referenceConfig.get(). 创建代理对象
  2. init(). 创建代理对象
  3. Map<String, String> map = new HashMap<String, String>(); ==> map.put(SIDE_KEY, CONSUMER_SIDE). 创建并初始化一个URL使用的map
  4. serviceMetadata.getAttachments().putAll(map). 注册元数据
  5. ref = createProxy(map). 创建代理对象
  6. invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)). 获取provider的委托对象
  7. (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic)) 创建代理对象
  8. ProxyFactory$Adaptive # getProxy #. extension.getProxy(arg0, arg1) extName = javassist
  9. proxyFactory.getProxy(invoker, generic). 创建代理对象
  10. Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker))
  11. getProxy() 创建代理类proxy的class
  12. newInstance() 创建这个class的实例,即代理对象.
  13. 创建InvokerInvocationHandler,consmer 的动态代理调用类
  14. Proxy # getProxy
  15. proxy = (Proxy) pc.newInstance(). 创建proxy的class

服务订阅-createProxy 方法

  1. ReferenceConfig # createProxy
  2. shouldJvmRefer(map) 判断是否是本地引用
    1. isInjvm() == null. 若dubbo:reference/的injvm属性为null
    2. url != null && url.length() > 0 ==> return false. 若dubbo:reference/的url属性不空,则为直连引用,不属于本地引用
    3. isJvmRefer = InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl). 若dubbo:reference/的injvm与url属性均为空,则继续判断
    4. isJvmRefer = isInjvm() 返回injvm 配置
    5. invoker = REF_PROTOCOL.refer(interfaceClass, url) 从本地的缓存中获取到Invoker返回, InjvmExporter
  3. 处理远程引用
  4. url != null && url.length() > 0 若dubbo:reference/的url属性不空,则当前对provider的调用为“直连”调用,无需注册中心
  5. 处理有注册中心的调用情况
  6. checkRegistry() 处理有注册中心的调用情况
    1. convertRegistryIdsToRegistries. 获取合并dubbo:application/ 和dubbo:reference/ 标签中的registry属性值,注册中心的ID值
    2. for (RegistryConfig registryConfig : registries). 遍历所有指定的注册中心
    3. !registryConfig.isValid() ==>. !StringUtils.isEmpty(address)只要有一个注册中心不可用,就抛出异常
    4. List us = ConfigValidationUtils.loadRegistries(this, false). 获取注册中心的标准URL,其仅仅包含registry://…格式的URL, 没有service-discovery-registry://…的URL
    5. for (URL u : us). 遍历所有注册中心URL
    6. URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u)获取所有监控中心URL
    7. u = u.putAttribute(MONITOR_KEY, monitorUrl). 以monitor属性的形式将监控中心URL添加到注册中心URL中
    8. urls.add(u.putAttribute(REFER_KEY, map)). 以refer属性的形式将消费者URL添加到注册中心URL中
      1. 注册中心地址既可以获取到注册中心的地址,也可以通过refer获取到消费者的信息
    9. urls.size() == 1. 如果只有1个注册中心,创建1个Invoker
      1. invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0))
    10. 1个Invoker代表1个注册中心的所有provider
    11. 有多个注册中心,会创建多个Invoker
    12. for (URL url : urls) { 遍历注册中心的地址
    13. invokers.add(REF_PROTOCOL.refer(interfaceClass, url)) 创建注册中心对应的Invoker对象并放入到list中
    14. if (registryURL != null) {. 若有可用的注册中心
    15. invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
      1. 将所有注册中心的Invoker封装为1个Invoker
      2. 由于服务启动后,注册中心的地址个数就不可变了,所以这里的Directory是静态的(StaticDirectory)
      3. 注册中心Invoker中对应的provider是可变的,所以注册中心里边的Directory是动态的

服务订阅-REF_PROTOCOL.refer 从注册中心地址获取provider

  1. invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0))
  2. Protocol$Adaptive # extension.refer(arg0, arg1). extName # register
  3. RegistryProtocol # refer
  4. Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY)). 获取cluster属性,即集群容错策略,默认failover策略. 获取消费者标签dubbo:reference/ 中的failover属性
  5. doRefer(cluster, registry, type, url, qs) 继续订阅 返回的Invoker中有集群容错的内容
  6. ClusterInvoker migrationInvoker = getMigrationInvoker. 创建一个具有“注册中心迁移”功能的invoker,允许一个Invoker从一个注册中心迁移到另一个注册中心
  7. interceptInvoker(migrationInvoker, url, consumerUrl, url). 拦截invoker,为invoker添加迁移监听器
    1. for (RegistryProtocolListener listener : listeners) 遍历所有监听器,为订阅过程添加这些监听功能
    2. listener.onRefer
      1. MigrationRuleListener # onRefer
      2. MigrationRuleHandler<?> migrationRuleHandler = handlers.computeIfAbsent. 从缓存中获取迁移规则处理器
      3. migrationRuleHandler.doMigrate(rule) 将迁移规则应用于迁移规则处理器
      4. refreshInvoker(step, threshold, rule). 根据注册中心更新本地invoker委托对象
      5. switch (step) {. 根据不同迁移步骤,采用不同刷新方式
      6. migrationInvoker.migrateToApplicationFirstInvoker(newRule). 应用优先
        1. refreshInterfaceInvoker(latch). 刷新invoker
        2. invoker = registryProtocol.getInvoker(cluster, registry, type, url). 根据注册中心更新本地委托对象invoker
        3. DynamicDirectory directory = new RegistryDirectory<>(type, url). 创建了一个动态列表(里边维护了provider)
        4. ClusterInvoker invoker = doCreateInvoker(directory, cluster, registry, type). 创建invoker
          1. registry.register(directory.getRegisteredConsumerUrl()). 将当前consumer注册到注册中心
            1. 给zookeeper接口节点下边的子节点consumers中添加consumer的url
            2. image.png
          2. directory.buildRouterChain(urlToRegistry). 将所有RouterFactory激活扩展类创建的router添加到directory
          3. directory.subscribe(toSubscribeUrl(urlToRegistry)). 服务订阅
            1. toSubscribeUrl(urlToRegistry). 地址中添加属性category=providers,configurators,routers
            2. ZookeeperRegistry # doSubscribe
            3. if (ANY_VALUE.equals(url.getServiceInterface())). 处理dubbo:reference/的interface属性为"*"的情况
            4. 处理dubbo:reference/的interface属性为普通值的情况
            5. for (String path : toCategoriesPath(url)) 遍历configurators、routers与providers三个路径
            6. zkClient.create(path, false); 创建持久节点
            7. List children = zkClient.addChildListener(path, zkListener). 为创建的节点添加子节点列表变更的watcher监听
            8. urls.addAll(toUrlsWithEmpty(url, path, children))
              1. toUrlsWithEmpty. 如果节点下没有子节点会创建空的url地址
              2. boolean isProviderPath = path.endsWith(PROVIDERS_CATEGORY). 判断当前path是否以providers结尾
              3. if (isProviderPath) { 如果是providers
              4. urls = toUrlsWithoutEmpty(consumer, providers). 将providers节点的所有子节点变为url
              5. 处理configurators与routers节点情况
              6. urls = toConfiguratorsWithoutEmpty(consumer, providers). 将节点的所有子节点变为url
              7. if (urls.isEmpty()) ==> urls.add(empty); 为没有子节点的节点创建一个 empty://…的URL
            9. notify(url, listener, urls). 主动调用notify更新本地invoker
              1. AbstractRegistry # notify
              2. Map<String, List> result = new HashMap<>() map的key为configurators、routers与providers value为这些节点下对应的子节点Url
              3. listener.notify(categoryList). 主动调用各分类节点的notify(),更新到本地
              4. RegistryDirectory # notify
              5. refreshOverrideAndInvoker(providerURLs) 刷新invoker
          4. (ClusterInvoker) cluster.join(directory). 将多个invoker伪装为一个具有复合功能的invoker ,cluster 具有容错功能, directory 为一个动态列表,返回的Invoker 有一堆provider和一些功能
            1. MockClusterWrapper # join
            2. new MockClusterInvoker(directory, this.cluster.join(directory)).
              1. this.cluster 为 FailoverCluster
            3. AbstractCluster # join
            4. new FailoverClusterInvoker<>(directory)
        5. calcPreferredInvoker(newRule). this.currentAvailableInvoker在该方法中进行的赋值
          1. this.currentAvailableInvoker = invoker;

服务订阅-RegistryDirectory.refreshOverrideAndInvoker 刷新invoker

  1. refreshInvoker(urls). 刷新invoker, 更新本地invoker列表, urls zk中的url地址
  2. EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol()). 判断是否有empty://开头
    1. this.forbidden = true. 禁止远程调用
    2. destroyAllInvokers(). 将缓存中的所有invoker删除
  3. 正常的url
  4. this.forbidden = false; 允许远程访问
  5. Map<URL, Invoker> oldUrlInvokerMap = this.urlInvokerMap. 先将缓存map发生变更前的值进行暂存
    1. Map<URL, Invoker> urlInvokerMap
    2. 缓存map,更新本地invoker列表,就是更新这里
    3. key为URL,value为该URL对应的invoker实例(invoker委托对象)
  6. invokerUrls zk中的provider的url cachedInvokerUrls 本地缓存的url
    1. invokerUrls.addAll(this.cachedInvokerUrls). 如果zk的url为空,本地缓存不为空,将本地的添加到zk中的url,如果zk中的地址没有了,将本地缓存的地址添加进去,增加可用性 提供者端与zk的连接掉了,消费端依然可以从本地缓存获取到提供者端的地址进行调用
    2. zk的url不为空,将zk的url添加到本地的url中
  7. invokerUrls.isEmpty(). ==> return 走到这里invokerUrls仍为空,则说明真的是没有任何可用的invoker
  8. Map<URL, Invoker> newUrlInvokerMap = toInvokers(invokerUrls) 从缓存map中获取相应的invoker,若存在,则返回,并将其从缓存map中删除,若不存在,则创建一个invoker, 保证了缓存中剩下的invoker是有问题的
    1. 对urls的各种检测
    2. URL url = mergeUrl(providerUrl). 将provider相关的配置进行合并. 优先级 override > -D >Consumer > Provider 例子:如果provider和consumer中都配置了相同的属性,合并为consumer中的属性

  9. List<Invoker> newInvokers = Collections.unmodifiableList(newUrlInvokerMap). 获取到最新的可用invoker列表
  10. this.urlInvokerMap = newUrlInvokerMap; 将缓存map更新为新map
  11. Map<URL, Invoker> localUrlInvokerMap = this.urlInvokerMap; 本地的缓存map
  12. Invoker invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.remove(url)
  13. 若缓存map为空,则直接返回null,说明缓存map中没有当前url对应的invoker
  14. 若缓存map不空,则获取缓存map中该url对应的invoker,同时将该entry删除,剩下的就是没有用的Invoker
  15. if (invoker == null). 如果invoker为空,创建新的Invoker
  16. enabled = url.getParameter(ENABLED_KEY, true). 从url中获取disabled或enabled属性
  17. if (enabled) ==> invoker = protocol.refer(serviceType, url). 若当前url中没有禁用该invoker, 创建invoker委托对象, 该Invoker对象会和提供端的Invoker进行联系调用
  18. if (invoker != null)
  19. newUrlInvokerMap.put(url, invoker). 将创建的委托对象写入到新map
  20. destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); 删除老map中剩余的invoker(不可用的Invoker)
  21. if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) 若最新的invoker列表为空,则说明当前已经没有任何可用的invoker了,此时将缓存map中的所有invoker全部删除
    1. destroyAllInvokers()
  22. for (Map.Entry<URL, Invoker> entry : oldUrlInvokerMap.entrySet()) 将原来缓存map中的invoker全部删除,因为它们中的这些已经没用了,最新可用的都在新map中
    1. invoker.destroy();

服务订阅-RegistryDirectory.refer 刷新invoker

  1. refreshInvoker(urls). 刷新invoker, 更新本地invoker列表, urls zk中的url地址
  2. EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol()). 判断是否有empty://开头
    1. this.forbidden = true. 禁止远程调用
    2. destroyAllInvokers(). 将缓存中的所有invoker删除
  3. 正常的url
  4. this.forbidden = false; 允许远程访问
  5. Map<URL, Invoker> oldUrlInvokerMap = this.urlInvokerMap. 先将缓存map发生变更前的值进行暂存
    1. Map<URL, Invoker> urlInvokerMap
    2. 缓存map,更新本地invoker列表,就是更新这里
    3. key为URL,value为该URL对应的invoker实例(invoker委托对象)
  6. invokerUrls zk中的provider的url cachedInvokerUrls 本地缓存的url
    1. invokerUrls.addAll(this.cachedInvokerUrls). 如果zk的url为空,本地缓存不为空,将本地的添加到zk中的url,如果zk中的地址没有了,将本地缓存的地址添加进去,增加可用性 提供者端与zk的连接掉了,消费端依然可以从本地缓存获取到提供者端的地址进行调用
    2. zk的url不为空,将zk的url添加到本地的url中
  7. invokerUrls.isEmpty(). ==> return 走到这里invokerUrls仍为空,则说明真的是没有任何可用的invoker
  8. Map<URL, Invoker> newUrlInvokerMap = toInvokers(invokerUrls) 从缓存map中获取相应的invoker,若存在,则返回,并将其从缓存map中删除,若不存在,则创建一个invoker, 保证了缓存中剩下的invoker是有问题的
    1. 对urls的各种检测
    2. URL url = mergeUrl(providerUrl). 将provider相关的配置进行合并. 优先级 override > -D >Consumer > Provider 例子:如果provider和consumer中都配置了相同的属性,合并为consumer中的属性

  9. List<Invoker> newInvokers = Collections.unmodifiableList(newUrlInvokerMap). 获取到最新的可用invoker列表
  10. this.urlInvokerMap = newUrlInvokerMap; 将缓存map更新为新map
  11. Map<URL, Invoker> localUrlInvokerMap = this.urlInvokerMap; 本地的缓存map
  12. Invoker invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.remove(url)
  13. 若缓存map为空,则直接返回null,说明缓存map中没有当前url对应的invoker
  14. 若缓存map不空,则获取缓存map中该url对应的invoker,同时将该entry删除,剩下的就是没有用的Invoker
  15. if (invoker == null). 如果invoker为空,创建新的Invoker
  16. enabled = url.getParameter(ENABLED_KEY, true). 从url中获取disabled或enabled属性
  17. if (enabled) ==> invoker = protocol.refer(serviceType, url). 若当前url中没有禁用该invoker, 创建invoker委托对象, 该Invoker对象会和提供端的Invoker进行联系调用
  18. if (invoker != null)
  19. newUrlInvokerMap.put(url, invoker). 将创建的委托对象写入到新map
  20. destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); 删除老map中剩余的invoker(不可用的Invoker)
  21. if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) 若最新的invoker列表为空,则说明当前已经没有任何可用的invoker了,此时将缓存map中的所有invoker全部删除
    1. destroyAllInvokers()
  22. for (Map.Entry<URL, Invoker> entry : oldUrlInvokerMap.entrySet()) 将原来缓存map中的invoker全部删除,因为它们中的这些已经没用了,最新可用的都在新map中
    1. invoker.destroy();

服务订阅-Protocol.refer 创建invoker委托对象

  1. invoker = protocol.refer(serviceType, url)
  2. Protocol$Adaptive # extension.refer.==> extName = dubbo
  3. DubboProtocol # refer. ==>. protocolBindingRefer
  4. DubboInvoker invoker = new DubboInvoker(serviceType, url, getClients(url), invokers);
    1. 创建一个与Dubbo协议相绑定的委托对象invoker,这个invoker可以与provider间建立很多的连接。
    2. 而第一个连接就会对应一个Netty Client,通过getClients(url)可以获取到其绑定的这些连接
    3. 一个消费端Invoker可以与多个provider建立连接,因为可以并行消费
  5. getClients(url). 获取到绑定的这些连接
    1. boolean useShareConnect = false. 标识连接是否中可共享的(即一个连接是否可以并行提交多个请求)
    2. int connections = url.getParameter(CONNECTIONS_KEY, 0) 获取connections属性,该属性指定了当前consumer与provider间创建的连接数量(非共享的),默认值为0,表示只有一个连接dubbo:reference 标签中配置的connections 属性,表示的可以有多少个物理连接
    3. List shareClients = null 表示可共享连接client
      1. ReferenceCountExchangeClient. # 构造方法
      2. this.client = client; 物理连接client
      3. referenceCount.incrementAndGet(); referenceCount 表示当前共享连接被共享的次数,只要创建一个共享连接client,该count就会增一,该count默认值为0,即共享连接的count值最小为1
    4. if (connections == 0) {. useShareConnect = true 当前连接只有一个,所以该连接是可共享的
    5. String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null) 从xml的配置文件的dubbo:consumer/中获取shareconnections属性,该属性表示共享连接最多可共享的次数
    6. connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) 若xml中的该属性配置不空,则直接取该值,否则从属性中获取。此时的connections 表示一个物理连接上可以共享多少个连接
    7. shareClients = getSharedClient(url, connections). 创建相应的共享连接
    8. ExchangeClient[] clients = new ExchangeClient[connections]. connections的值大于0的,只不过,其可能代表的是共享连接数量或非共享连接数量
    9. if (useShareConnect) { clients[i] = shareClients.get(i); 直接获取共享连接
    10. else clients[i] = initClient(url). 创建非共享连接
  6. getSharedClient(url, connections). 创建相应的共享连接
  7. String key = url.getAddress(). key为consumer的ip:服务协议port
  8. Object clients = referenceClientMap.get(key). 从缓存中获取clients
  9. 检测clients可用性
  10. if (clients instanceof List) {. 遍历所有的client
  11. if (checkClientCanUse(typedClients)) {
  12. 判断clients的可用性:只有当clients中的所有client均可用时,该clients才称为可用的,checkClientCanUse()返回true。只要有一个client不可用,就返回false
  13. referenceCountExchangeClient == null || referenceCountExchangeClient.getCount() <= 0 || referenceCountExchangeClient.isClosed() ==>. return false 只要有一个client不可用,就直接结束,返回false
  14. batchClientRefIncr(typedClients). ==> referenceCountExchangeClient.incrementAndGetCount(). 为每个client的count增一
  15. 代码走到这里,说明clients中一定存在不可用client
  16. referenceClientMap.put(key, PENDING_OBJECT). 若当前key对应的clients不可用,则将一个Object对象写入该value
  17. else if (clients == PENDING_OBJECT). ==>. referenceClientMap.wait() 若当前clients为Object,则阻塞当前线程
  18. else. referenceClientMap.put(key, PENDING_OBJECT). 若当前clients为其它情况,则将Object写入到当前key对应的value
  19. connectNum = Math.max(connectNum, 1). 共享连接数
  20. CollectionUtils.isEmpty(typedClients). 若clients为空,则创建clients
  21. buildReferenceCountExchangeClientList(url, connectNum); 创建client
  22. for (int i = 0; i < connectNum; i++) { 遍历共享连接数
  23. clients.add(buildReferenceCountExchangeClient(url)). 创建共享client
  24. ExchangeClient exchangeClient = initClient(url). 创建一个client,这个client最终会创建出一个Netty Client
    1. if (url.getParameter(LAZY_CONNECT_KEY, false)) {. 判断当前连接是否是Lazy连接
    2. client = Exchangers.connect(url, requestHandler). 正常连接
    3. getTransporter().connect(url, handler)
    4. Transporter$Adaptive # extension.connect ==> extName = netty
    5. NettyTransporter # connect
    6. new NettyClient(url, handler)
    7. AbstractClient 构造方法中 # doOpen 创建并启动Netty Client
    8. AbstractClient 构造方法中 # connect 连接
  25. new ReferenceCountExchangeClient(exchangeClient). 将该client封装为一个共享连接client

  26. } else {. 走到这里,说明当前clients一定是包含不可用client的列表
  27. for (int i = 0; i < typedClients.size(); i++) {. 遍历client
  28. if (referenceCountExchangeClient == null || referenceCountExchangeClient.isClosed()) 若当前遍历的client不可用,则为其创建一个client
  29. typedClients.set(i, buildReferenceCountExchangeClient(url)). 若当前遍历的client不可用,则为其创建一个client
  30. referenceCountExchangeClient.incrementAndGetCount(). 若当前遍历的client就是可用的,则为其count增一
  31. 放入缓存map
  32. synchronized (referenceClientMap) {
  33. if (typedClients == null) {. ==> referenceClientMap.remove(key);
  34. referenceClientMap.put(key, typedClients). 将clients写入缓存map
  35. referenceClientMap.notifyAll(). 唤醒所有阻塞的线程
  36. initClient(url). 创建非共享连接,创建共享连接时也会创建非共享连接,逻辑用上边蓝色标记
  37. client = Exchangers.connect(url, requestHandler). 正常连接
  38. 1个Consumer会创建几个NettyClient总结:
  39. dubbo:reference 标签中的connections属性大于0,创建NettyClient的个数以此为准,并且创建的NettyClient是不可共享的,dubbo:consumer 标签中的shareconnections属性不起作用
  40. dubbo:reference 标签中的connections属性等于0或者不存在该属性,则表示创建的NettyClient是共享的,创建NettyClient的个数由dubbo:consumer 标签中的shareconnections属性决定,如果shareconnections属性不存在,则从配置属性中获取,如果没有配置,则默认值为1
  41. 例子:
  42. dubbo:reference 标签中connections 属性为5 ,dubbo:consumer 标签中的 shareconnections 属性为8,会创建5个nettyClient,连接不是共享的
  43. dubbo:reference 标签中connections 属性为1 ,dubbo:consumer 标签中的 shareconnections 属性为8,会创建1个nettyClient,这1个是不可共享的
  44. dubbo:reference 标签中connections 属性为0 ,dubbo:consumer 标签中的 shareconnections 属性为8, 会创建8个nettyClient,是共享的

消费端调用服务端

  1. greetingService.hello(). 消费端调用
  2. InvokerInvocationHandler # invoke
  3. if (method.getDeclaringClass() == Object.class) { ==> method.invoke(invoker, args). 若当前调用方法为Object的方法,则直接调用该本地方法
  4. RpcInvocation rpcInvocation = new RpcInvocation(method, invoker.getInterface().getName(), protocolServiceKey, args). 生成RpcInvocation,调用的信息
  5. invoker.invoke(rpcInvocation).recreate();
  6. currentAvailableInvoker.invoke(invocation)
  7. MockClusterInvoker # invoke 带有降级功能的invoker
  8. result = this.invoker.invoke(invocation) 远程调用
  9. AbstractClusterInvoker # invoke
  10. List<Invoker> invokers = list(invocation). 通过路由策略,将不符合路由规则的invoker过滤掉
  11. LoadBalance loadbalance = initLoadBalance(invokers, invocation). 获取负载均衡策略,并创建相应的负载均衡实例
  12. doInvoke(invocation, invokers, loadbalance). 调用具体的集群容错策略中的doInvoke()
  13. FailoverClusterInvoker # doInvoke
  14. Invoker invoker = select(loadbalance, invocation, copyInvokers, invoked). 负载均衡
  15. Result result = invokeWithContext(invoker, invocation). 远程调用
  16. AbstractInvoker # invoke
  17. AsyncRpcResult asyncResult = doInvokeAndReturn(invocation). 远程调用
  18. DubboClient # doInvoke
  19. ExchangeClient currentClient; 一个client连接着一个Netty Client
  20. if (clients.length == 1) { ==> currentClient = clients[0]; 若只有一个client,则直接选择
  21. currentClient = clients[index.getAndIncrement() % clients.length]. 若有多个client,则轮询选择一个
  22. waitForResultIfSync(asyncResult, invocation). 同步调用,这里将阻塞
  23. if (InvokeMode.SYNC != invocation.getInvokeMode()) ==> return 如果是异步调用则返回
  24. asyncResult.get(Integer.MAX_VALUE, TimeUnit.MILLISECONDS). 同步调用,获取结果
  25. boolean isOneway = RpcUtils.isOneway(getUrl(), invocation). 若只发请求,无需server给出响应,则为oneway,否则为twoway
  26. currentClient.request(inv, timeout, executor). 通过client提交请求
  27. HeaderExchangeChannel # request
  28. channel.send(req)
  29. channel.send(message, sent). 发送请求
  30. ChannelFuture future = channel.writeAndFlush(message). netty发送请求

服务端处理消费端请求

  1. 从DubboExporter 缓存mapper中找到Invoker,然后调用本地的方法
  2. NettyServerHandler # channelRead
  3. handler.received(channel, msg)
  4. MultiMessageHandler # received. 判断请求是否为multipart请求
  5. HeartbeatHandler # received. 判断是否为心跳请求
  6. AllChannelHandler # received. 请求分发器Dispatcher
  7. ExecutorService executor = getPreferredExecutorService(message). 线程池
  8. executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message)). 将对端请求/响应封装为一个任务,从线程池中拿到一个线程,来处理这个任务。
  9. ChannelEventRunnable # run()
  10. handler.received(channel, message)
  11. decode(message) 解码message
  12. handler.received(channel, message). 处理解码后的message
  13. if (message instanceof Request) { 处理client请求的情况
  14. if (request.isTwoWay()) {. ==> handleRequest(exchangeChannel, request). 处理双向请求
  15. CompletionStage future = handler.reply(channel, msg). 处理调用
  16. future.whenComplete((appResult, t) -> { 添加监听,一旦异步操作完成,就会触发该回调
  17. Invoker<?> invoker = getInvoker(channel, inv); 获取invoker
  18. DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey); 从缓存map中获取exporter
  19. exporter.getInvoker(). 获取exporter中封装的invoker
  20. Result result = invoker.invoke(inv). 完成invoker的本地调用计算
  21. result.thenApply(Function.identity()); 将result构建为一个异步结果

消费端处理服务端响应

  1. NettyClientHandler # channelRead
  2. handleResponse(channel, (Response) message) 处理server响应的情况
  3. future.doReceived(response)
  4. this.complete(res.getResult())
  5. CompletableFuture # complete

服务路由

服务路由的使用
  1. 服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter(基于脚本) 和标签路由(给provider打标) TagRouter。其中条件路由是我们最常使用的
  2. 路由规则的设置: 我们通过 Dubbo 管控平台为某服务/应用设置的路由规则,其不仅会设置到注册中心该服务下,同时也会设置到配置中心该服务下(如果搭建了配置中心的话)当然,我们也可以直接通过配置中心GUI 管理端来设置和修改路由规则。例如,通过Apollo 可视化平台来设置 Apollo 中的动态配置,通过ZooInspector 来设置 Zookeeper 中的动态配置等。
  3. 不过,通过配置中心管理端所进行的修改,是不会修改到注册中心中的路由规则的。所以,在设置了配置中心后,配置中心中的动态配置优先级是高于注册中心中的。
  4. Dubbo 管控平台设置路由规则(黑白名单和条件路由)
    1. 规则体: 路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。
    2. [服务消费者匹配条件] => [服务提供者匹配条件]
    3. 当消费者的 URL 满足匹配条件时,对该消费者执行后面的过滤规则
    4. => 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表
    5. 服务消费者匹配条件为空,表示不对服务消费者进行限制,所有消费者均将被路由到行后面的提供者。
    6. 服务提供者匹配条件为空,表示对符合消费者条件的消费者将禁止调用任何提供者。
    7. 参数符号
      1. method:将调用方法作为路由规则比较的对象
      2. argument:将调用方法参数作为路由规则比较的对象
      3. protocol:将调用协议作为路由规则比较的对象
      4. host:将 IP 作为路由规则比较的对象
      5. port:将端口号作为路由规则比较的对象
      6. address:将 IP:端口号作为路由规则比较的对象
      7. application:将应用名称作为路由规则比较的对象
    8. zk中dubbo节点下的内容
      1. meatdata 元数据中心
      2. GettingService 注册中心
        1. consumers. 消费者信息
        2. providers 提供者信息
        3. configurations 配置信息
        4. routes 路由信息
      3. config 配置中心
        1. dubbo dubbo协议
          1. 接口路由信息

** 添加激活 RouterFactory 创建的 Router 到 Directory**
  1. 将dubbo-cluster项目下org.apache.dubbo.rpc.cluster.RouterFactory 和org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory 文件中指定的RouterFactory的实现类进行加载
    1. FileRouterFactory. ConditionRouterFactory. ServiceRouterFactory. AppRouterFactory. TagRouterFactory. MockRouterFactory. MeshRuleRouterFactory. ==> RouterFactory
    2. TagDynamicStateRouterFactory. TagStaticStateRouterFactory. ==> StateRouterFactory
  2. RegistryProtocol # doCreateInvoker
  3. directory.buildRouterChain(urlToRegistry). 将所有RouterFactory激活扩展类创建的router添加到directory
  4. DynamicDirectory # buildRouterChain
  5. this.setRouterChain(RouterChain.buildChain(url)). 创建一个RouterChain,并设置到directory
  6. new RouterChain<>(url); 创建RouterChain
  7. RouterChain # 构造方法
  8. 对普通路由的处理
  9. List extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class).getActivateExtension. 获取激活RouterFactory实例
  10. initWithRouters(routers). 将routers写入到routerChain
  11. 对状态路由的处理
  12. ExtensionLoader.getExtensionLoader(StateRouterFactory.class)
读取配置中心的路由配置
  1. actory.getRouter(url). 由factory映射为了该factory创建出的router
  2. CacheableRouterFactory # getRouter
  3. routerMap.computeIfAbsent(url.getServiceKey(), k -> createRouter(url)); 从缓存map中获取当前服务标识key对应的router,若没有,则创建一个router,放入到缓存map后返回新创建的router
  4. ServiceRouterFactory # createRouter
  5. new ServiceRouter(url)
  6. ListenableRouter # 构造方法
  7. this.init(ruleKey). 从配置中心读取路由规则,初始化到这里
  8. String routerKey = ruleKey + RULE_SUFFIX; 由服务标识与Router后辍构成router key 例: org.apache.dubbo.demo.GreetingService:1.0.0:greeting.condition-router
  9. String rule = this.getRuleRepository().getRule(routerKey, DynamicConfiguration.DEFAULT_GROUP); 从配置中心读取动态路由规则
  10. DynamicConfiguration dynamicConfiguration = getDynamicConfiguration() 获取动态配置
  11. dynamicConfiguration.getConfig(key, group, timeout). 读取动态配置数据
  12. AbstractDynamicConfiguration. ==>. getConfig. ==> execute(() -> doGetConfig(key, group), timeout)
  13. TreePathDynamicConfiguration. ==> doGetConfig
  14. String pathKey = buildPathKey(group, key). 构建出要读取配置节点名称
  15. zkClient.getContent(pathKey) 读取zk节点中指定path的节点内容
    1. pathKey =/dubbo/config/dubbo/org.apache.dubbo.demo.GreetingService:1.0.0:greeting.configurators

读取注册中心的路由配置
  1. RegistryDirectory # notify
  2. toRouters(routerURLs).ifPresent(this::addRouters). 读取routers子节点下的路由规则,并添加到directory中
  3. RegistryDirectory # toRouters
  4. for (URL url : urls) {. 遍历routers子节点下的所有Url
  5. String routerType = url.getParameter(ROUTER_KEY); 获取router属性值,这里就是condition
  6. url = url.setProtocol(routerType). 将url的protocol由router://…变为condition://…
  7. Router router = ROUTER_FACTORY.getRouter(url). 创建相应的router
    1. RouterFactory$Adaptive # extension.getRouter(arg0). extName == condition
    2. ConditionRouterFactory # getRouter. ==>new ConditionRouter
  8. routers.add(router). 添加到routers中
  9. this::addRouters. ==> routerChain.addRouters(routers) 添加到路由过滤器链中
路由过滤使用
  1. AbstractClusterInvoker # invoke()
  2. List<Invoker> invokers = list(invocation). 通过路由策略,将不符合路由规则的invoker过滤掉
  3. DynamicDirectory # doList
  4. invokers = routerChain.route(getConsumerUrl(), invocation). 进行路由
  5. RouterChain # route
  6. for (StateRouter stateRouter : stateRouters) {. 处理状态路由
  7. for (Router router : routers) {. 处理普通路由
  8. ListenableRouter(ServiceRoute) # route
  9. for (Router router : conditionRouters) { 遍历所有条件路由
  10. ConditionRouter # route
  11. if (!enabled) { ==> return invokers; 若router不可用,则直接返回所有invoker
  12. if (!matchWhen(url, invocation)) {. ==> return invokers;
  13. matchWhen() 判断当前消费者与条件规则中前半部分(规则体中=>之前的内容)是否匹配,若没有匹配上,则返回所有invoker
  14. 没有匹配上说明当前路由规则不起作用,返回所有invokers
  15. List<Invoker> result = new ArrayList<Invoker>(). 用于存放过滤出的invoker
  16. if (thenCondition == null) {. ==> return result;
  17. thenCondition 代表规则体=>之后的表达式,若该表达式为空,说明当前路由规则为黑白名单
  18. 返回result,空的Invoker集合
  19. for (Invoker invoker : invokers) {. 代码走到这里,说明thenCondition不为null,matchThen() 用于判断invoker与thenCondition是否匹配, 遍历所有invoker,查找到所有可用的invoker
  20. matchThen(invoker.getUrl(), url). ==> result.add(invoker). 添加匹配到invoker

服务降级

  1. 服务降级常见设置:
    1. mock=”force:return null” 表示消费方对该服务的方法调用都直接强制性返回 null 值,不 发起远程调用,即使远程的提供者没有出现问题。不过需要注意,这里的 return null, 并不一定必须是 null,可以是任意想看到的内容。用来屏蔽不重要服务。
    2. mock=”fail:return null” 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。不过需要注意,这里的 return null,并不一定必须是 null,可以是任意想看到的 内容。例如,123456,例如某个字符串等。用来容忍不重要服务不稳定时的影响
    3. mock=”true”表示消费方对该服务的方法调用在失败后,会调用消费方定义的服务降级Mock 类实例的相应方法。而该 Mock 类的类名为“业务接口名+Mock”,且放在与接口相同的包中。
    4. mock=降级类的全限定性类名 与 mock=”true”功能类似,不同的是,该方式中的降级类 名可以是任意名称,在任何包中
  2. InvokerInvocationHandler # invoker
  3. MockClusterInvoker # invoke
  4. String value = getUrl().getMethodParameter(invocation.getMethodName()…) 获取mock属性值
  5. if (value.length() == 0 || “false”.equalsIgnoreCase(value)) {. 若没有设置mock属性,或mock属性设置为false,则不进行服务降级
  6. else if (value.startsWith(“force”)) 若mock属性值以force开头,则进行强制降级(无论是否有可用的invoker,都不管,直接降级)
  7. result = doMockInvoke(invocation, null); 直接服务降级
  8. 其它情况:当远程调用过程发生异常时,进行降级
  9. catch (RpcException e) {. 捕获rpcException, 如果是业务异常直接抛出
  10. result = doMockInvoke(invocation, e) 降级
  11. List<Invoker> mockInvokers = selectMockInvoker(invocation); 获取最新的invoker列表
  12. minvoker = (Invoker) new MockInvoker(getUrl(), directory.getInterface()). 没有可用的invoker,则创建一个降级invoker
  13. result = minvoker.invoke(invocation) 若minvoker为可用invoker,那么这里的调用就是正常的远程调用,否则这里走的是降级处理
  14. if (getUrl().hasMethodParameter(invocation.getMethodName())) {. 获取dubbo:method/中的mock
  15. if (StringUtils.isBlank(mock)) {. 若没有,则获取dubbo:reference/中的mock
  16. mock = normalizeMock(URL.decode(mock)). 规范化mock取值
  17. 若mock为return,则返回returnnull
  18. 若mock以fail:开头,则mock取其子串:将前面的fail:去掉
  19. if (mock.startsWith(RETURN_PREFIX)) {
  20. Object value = parseMockValue(mock, returnTypes). 获取return的结果
  21. return AsyncRpcResult.newDefaultAsyncResult(value, invocation). 将结果封装为异步结果
  22. Invoker invoker = getInvoker(mock); 调用本地降级类
  23. Invoker invoker = (Invoker) MOCK_MAP.get(mockService); 从缓存maInvoker
  24. Class serviceType. 获取业务接口class
  25. T mockObject = (T) getMockObject(mockService, serviceType); 创建本地降级类实例
  26. mockService = serviceType.getName() + “Mock”; 拼接本地降级类名
  27. mockClass = ReflectUtils.forName(mockService). 将降级类加载到内存
  28. return mockClass.newInstance(); 创建降级类实例
  29. invoker = PROXY_FACTORY.getInvoker(mockObject, serviceType, url). 将实例封装为invoker
  30. invoker.invoke(invocation). 执行降级类的方法

集群容错

  1. 消费者调用提供者服务异常,先按照容错处理,最终抛出RpcException异常,并且配置了降级,再进行降级.先进行容错,容错失败,再进行降级
  2. 容错配置 dubbo:reference 标签中的 cluster属性,默认为failover, 下边实例属性为failback
  3. dubbo-cluster 项目下的org.apache.dubbo.rpc.cluster.Cluster 文件
    1. FailoverCluster. FailfastCluster. FailsafeCluster. FailbackCluster. ForkingCluster. AvailableCluster. MergeableCluster. BroadcastCluster. ZoneAwareCluster. MockClusterWrapper
  4. ReferenceConfig # createProxy
  5. invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)). 获取provider的委托对象
  6. Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY)). 获取cluster属性,即集群容错策略,默认failover策略
  7. getCluster(name, true)
  8. (ClusterInvoker) cluster.join(directory); 将多个invoker伪装为一个具有复合功能的invoker
  9. new MockClusterInvoker(directory, this.cluster.join(directory)). cluster ==> FailbackCluster. 具有服务容错的cluster
  10. doJoin ==> new FailbackClusterInvoker<>(directory)
  11. result = this.invoker.invoke(invocation). 远程调用
  12. return doInvoke(invocation, invokers, loadbalance). 调用具体的集群容错策略中的doInvoke
  13. FailbackClusterInvoker # doInvoke
  14. 集群容错策略
  15. failover: 故障转移策略。当消费者调用提供者集群中的某个服务器失败时,其会自动尝试着调用 其它服务器。而重试的次数是通过 retries 属性指定的。
    1. FailoverClusterInvoker # doInvoke
    2. checkInvokers(copyInvokers, invocation). 检测invokers列表是否为空
    3. String methodName = RpcUtils.getMethodName(invocation) 获取RPC调用的方法名
    4. int len = calculateInvokeTimes(methodName). 获取retries属性值
    5. List<Invoker> invoked = new ArrayList<Invoker>(copyInvokers.size()). 存放所有已经尝试调用过的invoker,这些invoker中,除了最后一个外,其它的都是不可用的
    6. for (int i = 0; i < len; i++) {. 遍历尝试的次数次
    7. Invoker invoker = select(loadbalance, invocation, copyInvokers, invoked); 负载均衡, invoked 为需要排除的机器
    8. invoked.add(invoker). 将选择出的invoker写入到invoked集合
  16. failfast 快速失败策略。消费者端只发起一次调用,若失败则立即报错。通常用于非幂等性的写操作,比如新增记录。
  17. failsafe: 失败安全策略。当消费者调用提供者出现异常时,直接忽略本次消费操作。该策略通常用于执行相对不太重要的服务
  18. failback: 失败自动恢复策略。消费者调用提供者失败后,Dubbo 会记录下该失败请求,然后会定时发起重试请求,而定时任务执行的次数仍是通过配置文件中的 retries 指定的。该策略通 常用于实时性要求不太高的服务。
  19. forking: 并行策略。消费者对于同一服务并行调用多个提供者服务器,只要一个成功即调用结束并返回结果。通常用于实时性要求较高的读操作,但其会浪费较多服务器资源。
    1. ForkingClusterInvoker # invoker
    2. final List<Invoker> selected; 存放的是挑选出的用于进行并行运行的invoker
    3. final int forks = getUrl().getParameter(FORKS_KEY, DEFAULT_FORKS). 获取forks属性值
    4. final int timeout = getUrl().getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT). 获取timeout属性值,远程调用超时时限
    5. final AtomicInteger count = new AtomicInteger(); 计数器,记录并行运行异常的invoker数量
    6. final BlockingQueue ref = new LinkedBlockingQueue<>(). 队列:存放并行运行结果
    7. for (final Invoker invoker : selected) {. 并行运行
    8. executor.execute(() -> { 使用线程池中的线程执行,
    9. ref.offer(result); 将当前invoker执行结果写入到队列
    10. int value = count.incrementAndGet(); 若invoker执行过程中出现异常,则计数器加一
    11. if (value >= selected.size()) {. ==> ref.offer(e); 代码走到这里说明,没有任何一个并行远程调用是成功的。为了能够唤醒后面的poll(),这里就将异常信息写入到ref队列
    12. Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
    13. poll()是一个阻塞方法,等待ref中具有一个元素。
    14. 只要ref中被写入了一个元素,阻塞马上被唤醒。或一直等待到timeout超时
    15. 注意,该poll()方法的执行与前面的并行远程调用的执行也是并行的
  20. broadcast: 广播策略。广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
  21. available: 首个可用策略。从所有 invoker 中查找,选择第一个可用的 invoker。
  22. mergeable: 合并策略。将多个 group 的 invoker 的执行结果进行合并。
  23. zone-aware : 当有多个注册中心可供订阅时,该容错机制提供了一种策略,用于决定如何在它们之间分配流量:
    1. 标记为“preferred=true”的注册表具有最高优先级。
    2. 检查当前请求所属的区域,首先选择具有相同区域的注册表。
    3. 根据每个注册表的权重均衡所有注册表之间的流量。
    4. 挑选任何有空的人。

负载均衡

  1. 负载均衡策略 dubbo:reference dubbo:method 标签中都可以设置loadbalance属性
    1. random: 加权随机算法,是 Dubbo 默认的负载均衡算法。权重越大,获取到负载的机率就越大。
    2. leastactive: 加权最小活跃度调度算法。活跃度越小,其优选级就越高,被调度到的机率就越高。活跃度相同,则按照加权随机算法进行负载均衡
    3. shortestresponse: 加权最短响应时间算法。从 Invoker 列表中查找平均响应时间最短的作为要选择的 Invoker。
    4. roundrobin: 双权重轮询算法,是结合主机权重与轮询权重的、方法级别的轮询算法。
    5. consistenthash: 一致性 hash 算法。其是一个方法参数级别的负载均衡。对于同一调用方法的、相同实参的、远程调用请求,其会被路由到相同的 invoker。其是以调用方法的指定实参的 hash 值为 key 进行 invoker 选择的。
  2. InvokerInvocationHandler # invoke
  3. LoadBalance loadbalance = initLoadBalance(invokers, invocation). 获取负载均衡策略,并创建相应的负载均衡实例
  4. ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension 获取负载均衡的扩展类
  5. doInvoke(invocation, invokers, loadbalance). 调用具体的集群容错策略中的doInvoke()
  6. Invoker invoker = select(loadbalance, invocation, copyInvokers, invoked). 负载均衡
  7. select(LoadBalance loadbalance, Invocation invocation, List<Invoker> invokers, List<Invoker> selected)
    1. invoker 这是包含所有invoker的集合,这些invoker可能有问题,也可能是没问题的
    2. selected. 这里放的invoker是已经被选择过的,但都是有问题的invoker
  8. boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, CLUSTER_STICKY_KEY, DEFAULT_CLUSTER_STICKY). 获取sticky属性,粘连连接属性,所谓粘连连接是指,让所有对同一服务相同方法的访问请求,尽可能由同一个invoker提供服务,在消费端的dubbo:reference 的sticky属性,默认为false
  9. if (stickyInvoker != null && !invokers.contains(stickyInvoker)) ==> stickyInvoker = null
    1. 若当前粘连连接invoker不空,但其没有包含在所有invoker集合中,
    2. 说明这个粘连连接invoker已经挂了,则将缓存粘连连接清空
  10. if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
  11. 代码走到这里,说明粘连连接invoker没有挂掉,然后再进行判断:
  12. 若当前开启了粘连连接功能,粘连连接invoker也不空,且粘连连接invoker也没有包含在有问题invoker集合,则粘连连接invoker可能是可用的。是否可用,需要再进一步判断
  13. if (availablecheck && stickyInvoker.isAvailable()) {. ==> return stickyInvoker;
    1. 若可用性检测功能开启了,且粘连连接invoker是可用的,则直接返回粘连连接invoker,无需再进行负载均衡选择了
  14. Invoker invoker = doSelect(loadbalance, invocation, invokers, selected). 负载均衡选择
  15. Invoker invoker = loadbalance.select(invokers, getUrl(), invocation); 负载均衡选择
  16. if ((selected != null && selected.contains(invoker)). || (!invoker.isAvailable() && getUrl() != null && availablecheck)). 若当前选择出的invoker包含在有问题invoker集合中,或当前选择出的invoker直接就是不可用的,则进行重新负载均衡选择
  17. Invoker rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck); 重新负载均衡选择
  18. if (rInvoker != null) {. ==> invoker = rInvoker; 若重新选择的invoker不空,则直接返回,不再判断是否可用了
  19. else {==> invoker = invokers.get((index + 1) % invokers.size()); 若重新选择的invoker为空,则采用轮询方式再选择一个,轮询选择一个invoker,其是否为空,是否可用,不再进行判断
  20. if (sticky) {. ==> stickyInvoker = invoker. 将选择出的invoker缓存到粘连连接invoker中
  21. reselect. 重新负载均衡选择
  22. 先从不包含selected(之前有问题的Invoker)集合中选择可用的Invoker集合,如果有进行负载均衡
  23. 如果上边没有可用集合为空,从selected集合中选择可用Invoker,如果有,进行负载均衡
  24. 也没有,返回null
  25. List<Invoker> reselectInvokers = new ArrayList<>(). 其将来存放的invoker,就是再进行负载均衡选择的总集合,相当于前面的invokers集合的作用
  26. for (Invoker invoker : invokers) {. 遍历所有invokers集合,从中挑选出所有可用的invoker
  27. if (availablecheck && !invoker.isAvailable()) { ==> continue. 若当前遍历invoker不可用,则直接跳过
  28. if (selected == null || !selected.contains(invoker)) {. ==> reselectInvokers.add(invoker); 能走到这里,说明当前invoker一定是可用的。判断当前遍历invoker是否在曾被选择过集合,即有问题invoker集合,若不在,则将其记录到reselectInvokers
  29. 走到这里,reselectInvokers中的invoker一定是可用的,且还未被选择过的
  30. if (!reselectInvokers.isEmpty()) {. ==> loadbalance.select(reselectInvokers, getUrl(), invocation); 若reselectInvokers不空,则负载均衡从reselectInvokers中选择一个invoker
  31. 走到这里,说明reselectInvokers为空
  32. if (selected != null) {. for (Invoker invoker : selected) {. 遍历有问题invoker集合,从中查找出可用的invoker,以前有问题,可能现在没有问题了. ==> if ((invoker.isAvailable()) ==> reselectInvokers.add(invoker)
  33. if (!reselectInvokers.isEmpty()) { ==> return loadbalance.select(reselectInvokers, getUrl(), invocation)若reselectInvokers不空,则负载均衡从reselectInvokers中选择一个invoker
  34. return null. 否则返回null
  35. loadbalance.select
  36. AbstractLoadBalance # select
  37. doSelect(invokers, url, invocation) 调用各种不同的负载均衡策略中的doSelect()
一致性hash算法原理
  1. image.png
  2. m1为Invoker,o1为请求,问题,增加一个Invoker只会对最近的Invoker有影响
  3. 目的,增加1个物理Invoker对所有的Invoker有影响,能够分担请求,方案增加虚拟主机
  4. image.png
  5. 配置方式image.png
  6. hash.arguments,利用方法参数取hash值, (0,1)代表参数的位置, 逗号分割代表用第一个参数和第二个参数取hash值,可以继续逗号扩展
  7. hash.nodes 代表有多少个虚拟Invoker
一致性hash算法源码
  1. ConsistentHashLoadBalance # doSelect
  2. String methodName = RpcUtils.getMethodName(invocation); 获取RPC调用的全限定性方法名
  3. String key = invokers.get(0).getUrl().getServiceKey() + “.” + methodName; 计算一致性hash选择器的key.
  4. ConsistentHashSelector selector = (ConsistentHashSelector) selectors.get(key).
    1. ConcurrentMap<String, ConsistentHashSelector<?>> selectors;
    2. selectors是一个缓存map,其key为前面的key,value为一致性hash选择器
    3. 获取当前调用方法对应的选择器
  5. selectors.put(key, new ConsistentHashSelector(invokers, methodName, invokersHashCode)); 创建选择器后,再写入到缓存map
    1. ConsistentHashSelector # 构造方法
    2. this.virtualInvokers = new TreeMap<Long, Invoker>(); 一个map,其key为虚拟invoker的hash,value为物理invoker
    3. this.replicaNumber = url.getMethodParameter(methodName, HASH_NODES, 160). 获取hash.nodes属性值,即要创建的虚拟invoker的数量,即副本数量
    4. String[] index = COMMA_SPLIT_PATTERN.split. 获取hash.arguments属性值,并使用逗号进行分隔 (0,1,1)
    5. argumentIndex = new int[index.length]; ==> argumentIndex[i] = Integer.parseInt(index[i])将分隔出的参数索引变为整型后写入到数组
    6. for (Invoker invoker : invokers) {. 遍历所有物理invoker,为它们创建相应的虚拟invoker
    7. for (int i = 0; i < replicaNumber / 4; i++) {.
    1. replicaNumber代表要创建的虚拟主机数量,因为1个digest 为128位,一个hash由32位组成,1个摘要可以生成4个hash,所以这儿要除以4
  8. byte[] digest = Bytes.getMD5(address + i);  使用md5算法生成一个128位(16字节)的摘要
  9. for (int h = 0; h < 4; h++) {.  // 一个hash由32位二进制数生成,所以一个摘要可以生成4个hash。
  10. long m = hash(digest, h);  每32位二进制数生成一个hash
  11. virtualInvokers.put(m, invoker);  每个hash将作为一个虚拟invoker,即map的key
  12. selector.select(invocation). 进行一致性hash选择
     1.  String key = toKey(invocation.getArguments());  将数组元素代表的索引的实参值进行字符串拼接
private String toKey(Object[] args) {
    StringBuilder buf = new StringBuilder();
    // 遍历数组,将该数组元素代表的索引的实参值进行字符串拼接,
    //argumentIndex代表所用参数的下标集合
    for (int i : argumentIndex) {
        if (i >= 0 && i < args.length) {
            //arg[i] 获取到对应下标的参数
            buf.append(args[i]);
        }
    }
    return buf.toString();
}
     2. byte[] digest = Bytes.getMD5(key);  使用key生成一个摘要
     3. hash(digest, 0). 取摘要的前32位生成一个hash,使用该hash进行选择
     4. ConsistentHashLoadBalance # selectForKey 选择 (一执行hash算法的核心,利用treeMap实现)
     5.  Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);  选择一个比当前hash值大的最小的一个缓存map的key对应的entry
     6. if (entry == null) { ==> entry = virtualInvokers.firstEntry(); 如果没有找到,则获取第一个,hash环连成圈,treeMap中没有找到,获取treeMap的第一个元素
     7.  return entry.getValue();  获取entry中的物理invoker

双权重轮询算法原理(主机权重 + 轮询权重)
  1. image.png
  2. 刚开始轮询权重都为0
  3. 计算本次轮询权重 = 上一次的轮询权重 + 主机权重
  4. 选择轮询权重最大的主机,选择后 该主机的轮询权重 = 该主机的轮询权重 - 轮询权重的和
  5. 主机权重越大,从最小变为最大越快
双权重轮询算法源码
  1. RoundRobinLoadBalance # doSelect
//回收期  如果一个主机的增重时间一直没有变,说明该主机宕机了
private static final int RECYCLE_PERIOD = 60000;

protected static class WeightedRoundRobin {
    // 主机权重
    private int weight;
    // 当前轮询权重值,初始值为0
    private AtomicLong current = new AtomicLong(0);
    // 当前轮询权重值的“增重”时间
    private long lastUpdate;

    public int getWeight() {
        return weight;
    }

    // 修改主机权重时会将轮询权重值清零
    public void setWeight(int weight) {
        this.weight = weight;
        // 将当前轮询权重值进行了清零
        current.set(0);
    }

    // 增重:增加主机权重
    public long increaseCurrent() {
        return current.addAndGet(weight);
    }

    // 减重:减去总主机权重之和
    public void sel(int total) {
        current.addAndGet(-1 * total);
    }

    public long getLastUpdate() {
        return lastUpdate;
    }

    public void setLastUpdate(long lastUpdate) {
        this.lastUpdate = lastUpdate;
    }
}


// 双层map,
// 外层map的key是全限定方法名,value为内层map,即提供该key方法服务的所有invoker的map
// 内层map的key为invoker的url,value为轮询权重实例
private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap =
    new ConcurrentHashMap<String, ConcurrentMap<String, WeightedRoundRobin>>();

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    // 获取外层map的key
    String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
    // 获取指定key的所有invoker构成的map,即内层map。
    // 若不存在,则创建一个内层map,再放入其中
    ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.computeIfAbsent(key, k -> new ConcurrentHashMap<>());
    int totalWeight = 0;
    // 记录当前最大的轮询权重值
    long maxCurrent = Long.MIN_VALUE;
    long now = System.currentTimeMillis();
    Invoker<T> selectedInvoker = null;
    WeightedRoundRobin selectedWRR = null;

    // 遍历所有invoker,为它们的轮询权重值增重,并挑选出具有最大轮询权重值的invoker
    for (Invoker<T> invoker : invokers) {
        // 获取内层map的key
        String identifyString = invoker.getUrl().toIdentityString();
        // 获取主机权重
        int weight = getWeight(invoker, invocation);
        // 从缓存map中获取指定key的轮询权重,若不存在,则创建一个轮询权重实例,再放入其中
        WeightedRoundRobin weightedRoundRobin = map.computeIfAbsent(identifyString, k -> {
            WeightedRoundRobin wrr = new WeightedRoundRobin();
            //设置主机权重,并且将当前的轮询权重设为0 ,setWeight 方法
            wrr.setWeight(weight);
            return wrr;
        });

        // 只有预热权重会发生变化
        if (weight != weightedRoundRobin.getWeight()) {
            //预热权重,将当前主机的权重重新设置
            weightedRoundRobin.setWeight(weight);
        }

        // 增重 当前的轮询权重 + 主机权重  current.addAndGet(weight)
        long cur = weightedRoundRobin.increaseCurrent();
        // 记录增重时间
        weightedRoundRobin.setLastUpdate(now);

        // 若当前增重后的轮询权重值大于当前记录的最大的轮询权重值,
        // 则修改记录的最大轮询权重值,并将当前invoker记录下来
        // 寻找轮询权重最大的主机
        if (cur > maxCurrent) {
            maxCurrent = cur;
            selectedInvoker = invoker;
            selectedWRR = weightedRoundRobin;
        }
        // 计算所有invoker的主机权重和
        totalWeight += weight;
    }  // end-for

    // 这里的不等,只有一种可能: invokers.size() < map.size(),不可能出现大于的情况
    // 小于说明出现了invoker宕机,而大于则是扩容。但扩容后在执行前面的for()时,
    // 会使map的size()增加,然后这里的invokers.size()与map.size()就又相等了。
    if (invokers.size() != map.size()) {
        // map.entrySet() 获取一个set集合,其元素为entry
        // removeIf() 的作用是,其参数predicate若为true,则将当前元素删除
        // now - item.getValue().getLastUpdate() 计算出的是,距离上次增重已经过去多久了
        // predicate条件为,若当前invoker距离上次增重时长已经超过了回收期,则说明当前invoker
        // 已经宕机很久了,就可以将其从缓存map中删除了
        // 删除的前提条件 invokers的集合中没有该Invoker了
        map.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
    }

    // 若选择出的invoker不为null,则返回该invoker
    if (selectedInvoker != null) {
        // 减重 当前预热权重 - 主机权重    current.addAndGet(-1 * total);
        selectedWRR.sel(totalWeight);
        return selectedInvoker;
    }
    // should not happen here
    return invokers.get(0);
}
  1. AbstractLoadBalance # getWeight. 主机权重
int getWeight(Invoker<?> invoker, Invocation invocation) {
    int weight;
    URL url = invoker.getUrl();
    // 对于多注册中心的场景,各invoker的权重值就是注册中心的权重
    if (REGISTRY_SERVICE_REFERENCE_PATH.equals(url.getServiceInterface())) {
        weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);
    } else {  
        // 单注册中心
        // 获取weight属性的值,先获取<dubbo:method> 的属性值
        // 如果没有获取<dubbo:reference>中的属性值
        // DEFAULT_WEIGHT = 100 默认值
        weight = url.getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
        if (weight > 0) {
            // 获取provider主机的启动时间戳
            long timestamp = invoker.getUrl().getParameter(TIMESTAMP_KEY, 0L);
            if (timestamp > 0L) {
                // 计算当前provider已经启动多久了
                long uptime = System.currentTimeMillis() - timestamp;
                if (uptime < 0) {
                    return 1;
                }
                // 获取warmup(预热)属性值,刚开始的运行效率比较低,默认为10分钟
                int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
                //如果启动时间小于预热时间
                if (uptime > 0 && uptime < warmup) {
                    // 计算预热过程中的权重
                    weight = calculateWarmupWeight((int)uptime, warmup, weight);
                }
            }
        }
    }
    return Math.max(weight, 0);
}
//计算预热权重
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
    // 以下式子等价于:  (uptime / warmup) * weight
    // (启动时间/预热时间) * 权重
    int ww = (int) ( uptime / ((float) warmup / weight));
    return ww < 1 ? 1 : (Math.min(ww, weight));
}

加权随机算法原理
  1. image.png
  2. 5,8,14 为 前边主机权重的和
  3. 生成0到14的随机数,寻找生成的随机数小于数组中最小的元素

加权随机算法源码
  1. RandomLoadBalance # doSelect
  2. int length = invokers.size(); length invoker的数量
  3. if (!needWeightLoadBalance(invokers,invocation)){
  4. return invokers.get(ThreadLocalRandom.current().nextInt(length)); 若不需要加权负载均衡(配置xml中没有weight属性),则直接生成一个[0, length)的随机数,选择出invoker
  5. int[] weights = new int[length]; 记录当前invoker及其前面所有invoker的权重之和
  6. for (int i = 0; i < length; i++) {.
  7. int weight = getWeight(invokers.get(i), invocation); 获取当前主机的权重
  8. totalWeight += weight; 当前的权重和 = 当前主机的权重 + 之前的权重和
  9. weights[i] = totalWeight; 设置当前 主机下标对应的权重和
  10. if (sameWeight && totalWeight != weight * (i + 1)) {. ==> sameWeight = false; 判断Invoker的权重不是相同的
  11. if (totalWeight > 0 && !sameWeight) {. 处理各invoker的权重不同的情况
  12. int offset = ThreadLocalRandom.current().nextInt(totalWeight); 生成一个随机权重[0, totalWeight)
  13. for (int i = 0; i < length; i++) {
  14. if (offset < weights[i]) {. ==> return invokers.get(i); 找到权重和大于随机权重的第一个下标,返回对应的invoker
  15. return invokers.get(ThreadLocalRandom.current().nextInt(length)); 处理各invoker权重相同的情况(与无权重的处理方式相同)
加权最小活跃度算法
  1. 先选择最小活跃度的主机,如果最小活跃度的主机有相同的,利用权重随机算法 挑选一个主机
  2. LeastActiveLoadBalance # doSelect
  3. int[] leastIndexes = new int[length]; 最小活跃度的主机集合
  4. int[] weights = new int[length]; 记录每个invoker的权重
  5. for (int i = 0; i < length; i++) {. 挑选出具有最小活跃度的所有invoker
  6. int active = RpcStatus.getStatus(invoker.getUrl(), …). 获取当前遍历invoker的最小活跃度,默认为0
  7. if (leastActive == -1 || active < leastActive) {. 如果之前的leastActive = = -1 表明刚开始,或者当前的活跃度比保存的最小活跃度还小,则更新最小活跃度的信息
  8. else if (active == leastActive) { 说明最小活跃的主机相等
  9. leastIndexes[leastCount++] = i; 添加集合
  10. if (!sameWeight && totalWeight > 0) {. 权重随机算法
  11. image.png
  12. 7,对应的在B的范围内,则选择B主机,weights中存放的是主机对应的权重
  13. i = 0 随机数 - i对应主机的权重
  14. 7-5-3 < 0 ,对应的3就是当前7所在的主机
  15. 判断是否小于0,小于0,说明找到了
if (!sameWeight && totalWeight > 0) {
        // 总权重和选择一个随机数
        int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
            
        for (int i = 0; i < leastCount; i++) {
            //获取最小活跃度对应的主机的下标
            int leastIndex = leastIndexes[i];
            //随机权重 = 随机权重 - 当前主机对应的权重
            offsetWeight -= weights[leastIndex];
            if (offsetWeight < 0) {
                //如果小于0,说明找到了
                return invokers.get(leastIndex);
            }
        }
    }

最短响应时间算法
  1. 先寻找最短响应时间的主机,如果找到了就返回,如果有最短响应时间相同的主机,利用加权随机算法选择一个主机返回
  2. ShortestResponseLoadBalance # doSelect
  3. long shortestResponse = Long.MAX_VALUE; 记录最短响应时间RT
  4. int[] shortestIndexes = new int[length]; 最短响应时间的invoker集合
  5. int[] weights = new int[length]; 所有invoker的权重
  6. RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl() 获取rpc的状态
  7. long succeededAverageElapsed = rpcStatus.getSucceededAverageElapsed(); 获取当前invoker的平均响应时间
public long getSucceededAverageElapsed() {
    // 获取当前invoker成功响应的次数
    long succeeded = getSucceeded();
    if (succeeded == 0) {
        return 0;
    }
    // 当前invoker的所有成功响应时间和 / 次数
    return getSucceededElapsed() / succeeded;
}
public long getSucceededElapsed() {
    // 当前invoker的所有响应时间和 - 当前invoker的所有异常响应时间和
    return getTotalElapsed() - getFailedElapsed();
}
  1. int active = rpcStatus.getActive(); 当前invoker正在处理的活动连接数量
  2. long estimateResponse = succeededAverageElapsed * active; 计算出当前invoker总的响应时间
    1. 总的响应时间 = 平均响应时间 * 正在处理的连接数量
  3. 寻找最小的响应时间
image.png

基础

consumer端

  1. xml中的标签

  2. . dubbo:reference version=“1.0.0” group=“greeting” id=“greetingService” check=“false”

              interface="org.apache.dubbo.demo.GreetingService" />
    
  3. key ==> greeting/org.apache.dubbo.demo.GreetingService:1.0.0

  4. 注册中心地址==> zookeeper://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&pid=43073&timestamp=1715836962379

  5. 修改后注册中心的地址===> registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&pid=43073&registry=zookeeper&timestamp=1715836962379

  6. failoverCluster

  7. Failovercluster 重试集群

  8. MigrationInvoker 迁移执行器 .

  9. MigrationRuleListener . 迁移规则监听器

  10. MigrationRuleHandler 迁移规则处理器

  11. comsumer 往zookeeper注册的地址 ==> dubbo://10.18.100.76/org.apache.dubbo.demo.GreetingService?application=demo-consumer&check=false&dubbo=2.0.2&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=43073&release=&revision=1.0.0&side=consumer&sticky=false&timestamp=1715836819420&version=1.0.0

  12. 消费端三个节点 providers ,configurators, routers

  13. dubbo://10.18.100.76/org.apache.dubbo.demo.GreetingService?application=demo-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.2&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=43073&release=&revision=1.0.0&side=consumer&sticky=false&timestamp=1715836819420&version=1.0.0

  14. /dubbo/org.apache.dubbo.demo.GreetingService/providers

  15. /dubbo/org.apache.dubbo.demo.GreetingService/configurators

  16. /dubbo/org.apache.dubbo.demo.GreetingService/routers

  17. 消费端子节点变更通知URL ==> zookeeper://localhost:2181/ConfigCenterConfig?check=true&config-file=dubbo.properties&group=dubbo&highest-priority=false&namespace=dubbo&timeout=3000

  18. providers 节点的内容==>dubbo://10.18.100.76:20881/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&release=release&revision=1.0.0&service-name-mapping=true&side=provider&version=1.0.0

  19. configurators 节点的内容: empty://10.18.100.76/org.apache.dubbo.demo.GreetingService?application=demo-consumer&category=configurators&check=false&dubbo=2.0.2&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=43073&release=&revision=1.0.0&side=consumer&sticky=false&timestamp=1715836819420&version=1.0.0

  20. routers 节点的内容:empty://10.18.100.76/org.apache.dubbo.demo.GreetingService?application=demo-consumer&category=routers&check=false&dubbo=2.0.2&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=43073&release=&revision=1.0.0&side=consumer&sticky=false&timestamp=1715836819420&version=1.0.0

  21. this.currentAvailableInvoker = serviceDiscoveryInvoker;

  22. AbstractClusterInvoker # invoke

provider端

  1. 提供者标签
  2. <dubbo:service version=“1.0.0” group=“greeting” interface=“org.apache.dubbo.demo.GreetingService” ref=“greetingService”/>
  3. 注册中心的两个地址==>
    1. service-discovery-registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=57588&registry=zookeeper&timestamp=1715844424181
    2. registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=57588&registry=zookeeper&timestamp=1715844424181
  4. 服务暴漏URL ==> dubbo://10.18.100.76:20881/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=10.18.100.76&bind.port=20881&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=57588&release=&revision=1.0.0&side=provider&timestamp=1715844571772&version=1.0.0
  5. 本地暴露URL ==> injvm://127.0.0.1/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=10.18.100.76&bind.port=20881&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=57588&release=&revision=1.0.0&side=provider&timestamp=1715844571772&version=1.0.0
  6. providerUrRL ==> dubbo://10.18.100.76:20881/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=10.18.100.76&bind.port=20881&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=67342&release=&revision=1.0.0&service-name-mapping=true&side=provider&timestamp=1715849100690&version=1.0.0
  7. overrideSubscribeUrl==> provider://10.18.100.76:20881/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=10.18.100.76&bind.port=20881&category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=67342&release=&revision=1.0.0&service-name-mapping=true&side=provider&timestamp=1715849100690&version=1.0.0
  8. registeredProviderUrl==> dubbo://10.18.100.76:20881/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=68795&release=&revision=1.0.0&service-name-mapping=true&side=provider&timestamp=1715849909146&version=1.0.0
  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值