简介
- Apache Dubbo 是一款由阿里巴巴公司开发的服务框架,它提供了 RPC 通信与微服务治理两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通 信能力,同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、 服务降级、集群容错等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意 功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。
- Dubbo3 定义了全新的 PCP 通信协议—Triple,Dubbo2中的协议叫dubbo
- Dubbo Spring Cloud
- Dubbo 的广播注册中心,类似于对讲机
Dubbo的系统架构
- dubbo的两大设计原则
- Dubbo 使用“微内核+插件(SPI接口的实现类)”的设计模式。内核只负责组装插件(扩展点),Dubbo 的功能都是由插件实现的。Dubbo 作为一个优秀的 RPC 框架,一个 Apache 的顶级项目,其最大的亮点之一就是其优秀的无限开放性设计架构—“微内核+插件”的架构设计思想, 使得其几乎所有组件均可方便的进行扩展、增强、替换. (通过SPI实现的,动态加实现类)
- 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
- dubbo 用url的原因,用url可以节省字符数量,协议地址端口不需要key
- dubbo 的三大领域模型
- Protocol 服务域:是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
- Invoker 实体域:是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
- Invocation 会话域:它持有调用过程中的变量,比如方法名,参数等。
- dubbo 的十层架构
- 三大层
- business (service) 业务层
- rpc (Config . proxy registry . cluster . monitor . protcol) rpc调用
- remoting (exchange --> dubbo的同步和netty的异步进行转换 transport serialize) 网络传输层
- ReferenceConfig reference-config 标签 . ServiceConfig service-config 标签
- Proxy . 消费者代理对象 . Invoker 提供者代理对象
- Registry 注册中心, Invoker需要注册到注册中心
- 将Invoker包装为Exporter服务暴露对象(封装了Filter过滤器) Invoker是Directory的列表
- DubboExporter . dubbo服务暴露协议,存到缓存map,key为服务名称,value为DubboExporter
- DubboProtocol . DubboHandler
- Proxy 代理是用消费端的Invoker委托对象包装的
- RegistryProtocol 注册协议 RegistryDirectory 注册目录 . NotifyListener . 注册中心发生变化的通知 (zookeeper 的watcher 机制)
- Directory 拉取到消费端的注册中心的目录
- Router . 地址过滤 LoadBalance 负载均衡侧略 RouterFactory
- Retry 重试策略
- MonitorFilter . Monitor . MonitorFactory . 监控
- 服务端和注册端的协议要对称的 Protocol . DubboInvoker
- ExchangeClient (同步和异步的转换) Exchanger . ExchangeServer . ExchangeHandler . 端口绑定 .
- Client . Transporter Server . ChannelHandler . Netty(Nio和非阻塞,异步)
- Codec 编解码 Dispatcher 分发
- 三大层
Dubbo SPI (获取接口实现类的方式)
概念
- Dubbo 内核的工作原理由四部分构成:服务发现机制 SPI、自适应机制 Adaptive、包装机制 Wrapper 与激活机制 Activate。Dubbo 通过这四种机制实现了对插件的 IoC、AOP,实现了对自动生成类的动态编译 Compile。
- jdk中不使用spring容器也可以发现接口的实现类
1.jdk spi
- 步骤
- 代码利用ServiceLoader.load(接口.class)
- 在META-INF/services创建与接口全路径名相同的文件
- 内容为实现类的全路径名
- 代码
public static void main(String[] args) {
// load()中放的是业务接口,其就相当于要加载的配置文件名
ServiceLoader<SomeService> loader = ServiceLoader.load(SomeService.class);
}
- 在META-INF.services目录下添加与接口全路径名相同的文件
com.abc.service.OneServiceImpl
com.abc.service.TwoServiceImpl
- 不合理的地方,加载的时候会将全部的类全部加载回来
dubbo spi
- 测试类
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());
}
- 接口(接口上加SPI注解)
@SPI("wechat")
public interface Order {
// 支付方式
String way();
}
- 在META-INFO.dubbo.internal文件夹下添加与接口名相同的文件
alipay=com.abc.spi.AlipayOrder
# key值可以定义多个,用逗号隔开
wechat,wechat2=com.abc.spi.WechatOrder
- 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串
- 提供者配置文件路径:在依次查找的目录为
- META-INF/dubbo/internal, META-INF/dubbo,META-INF/services
- ExtensionLoader.getExtensionLoader 获取指定接口的loader,保证指定接口的扩展名不重复
Adaptive类. (Adaptive 是选择要使用哪一个具体的实现类)
- Adaptive 机制,即扩展类的自适应机制。即其可以指定想要加载的扩展名,也可以不指定。若不指定,则直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive注解实现的。
- 有些 SPI 接口中的方法不需要 URL 相关的参数,此时就可以直接让@Adaptivate 来修饰某个 SPI 接口的实现类,由该类实现对 SPI 扩展类的自适应。
- dubbo的两个自适应类AdaptiveExtensionFactory, AdaptiveCompiler
- 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();
}
}
- 在扩展类的文件中添加adaptive 为key, value为adaptive类全路径的配置
alipay=com.abc.spi.AlipayOrder
wechat=com.abc.spi.WechatOrder
adaptive=com.abc.spi.AdaptiveOrder
- 测试类
@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());
}
- getAdaptiveExtension() 没有指定内容,获取到的是AdaptiveOrder 扩展类,该扩展类会getDefaultExtension获取实现类
- ((AdaptiveOrder)order).setDefaultName(“alipay”); 获取别的实现类
- Adaptive 类不是直接扩展类
Adaptive 方法(自适应)
- 被@Adapative 修饰的 SPI 接口中的方法称为 Adaptive 方法。在 SPI 扩展类中若没有找到Adaptive 类,但系统却发现了 Adapative 方法,就会根据 Adaptive 方法自动为该 SPI 接口动态生成一个 Adaptive 扩展类,并自动将其编译。例如 Protocol 接口中就包含两个 Adaptive方法
- 其对于要加载的扩展名的指定方式是通过URL 类型的方法参数指定的。所以对于 Adaptive 方法的定义规范仅一条:其参数包含 URL 类型的参数,或参数可以获取到 URL 类型的值。方法调用者是通过 URL 传递要加载的扩展名的。
- String extName = url.getParameter(“order”, “wechat”);
- order 是 接口的驼峰命名转为xx.xx, wechat 是接口spi注解中的值
- GoodsOrder 转为goods.order
- 生成自适应类的格式
- 接口 接口方法上加Adaptive 注解
@SPI("wechat")
public interface Order {
String way();
//在方法上添加Adaptive 注解,方法参数必须有URL参数,或者能获取到URL参数
@Adaptive
String pay(URL url);
}
- 实现类
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 微信支付";
}
}
- 在META-INFO.dubbo.internal文件夹下添加与接口名相同的文件
alipay=com.abc.spi.AlipayOrder
wechat=com.abc.spi.WechatOrder
- 测试类
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());
}
- 生成的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类
- Wrapper 机制,即扩展类的包装机制。就是对扩展类中的 SPI 接口方法进行增强,进行包装,是 AOP 思想的体现,是Wrapper 设计模式的应用。一个 SPI 可以包含多个 Wrapper。
- 规范
- 该类要实现 SPI 接口
- 该类中要有 SPI 接口的引用
- 该类中 SPI 接口实例是通过仅包含一个 SPI 接口参数的带参构造器传的
- 在接口实现方法中要调用 SPI 接口引用对象的相应方法
- 该类名称以 Wrapper 结尾
- 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;
}
}
- 在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 "支付宝支付";
}
}
- 接口
@SPI("wechat")
public interface Order {
String way();
}
- 在META-INFO.dubbo.internal文件夹下添加与接口名相同的文件
alipay=com.abc.spi.AlipayOrder
wechat=com.abc.spi.WechatOrder
- 测试
@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());
}
}
- 激活类是直接扩展类
四类扩展
- 普通扩展类
- adaptive类 类上有@Adaptive 注解的
- wrapper类 构造方法中有接口的
- activate类 实现类上有@Activate注解的
- 在配置文件中可能会存在四种类:普通扩展类、Adaptive 类、Wrapper 类,及 Activate 类
- 共同点 : 都实现了 SPI 接口。
- 不同点:
- 定义方式:Adaptive(自适应) 类与 Activate(激活) 类都是通过注解定义的。
- 数量:一个 SPI 接口的 Adaptive 类(无论是否是自动生成的)只会有一个;Wrapper 类与 Activate 类是可以有多个的。
- 直接扩展类:只有普通扩展类与 Activate 类是直接扩展类,Adaptive 类与 Wrapper 类不 是直接扩展类
调用的的方法名
- Order order = getExtension(“”) 获取到指定名称的实现类
- Order order = getAdaptiveExtension(Url url) 根据Url自适应获取到自适应类,一个接口只能对应一个自适应类,如果配置文件中定义了Adaptive类,则返回自定义的自适应类,如果没有自己定义,dubbo会自动生成自适应类,在dubbo生成的自适应类中,调用具体的方法时,会根据URL中参数内容获取到具体的实现类,然后调用具体实现类的方法
- List orderList = getActivateExtension(Url url, String key ,String group) 根据条件获取到实现类集合 ,key 对应的是Activate 注解中的value, group对应的是Activate 注解中的group
SPI源码
- ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Order.class); 获取实现类加载器
- !type.isInterface() 若type不是接口,则抛出异常
- !withExtensionAnnotation(type) 若type接口没有被@SPI注解修饰,则抛出异常
- ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS. extension缓存map
- EXTENSION_LOADERS.get(type). 从缓存map中获取extension对象
- loader == null ==> EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)) 创建ExtensionLoader并放入缓存map
- new ExtensionLoader ==> this.type = type. 设置type属性
- objectFactory. 设置对象工厂, 用于创建当前type中指定“功能前辍”扩展名的实例
- type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension
- 如果type为ExtensionFactory , 则返回null,如果type不为null返回ExtensionFactory 的自适应类
- ExtensionFactory 的实现类.
- AdaptiveExtensionFactory
- SpiExtensionFactory
- SpringExtensionFactory
- getAdaptiveExtension()
- Holder cachedAdaptiveInstance = new Holder<>(). cachedAdaptiveInstance. 缓存者自适应实例的缓存. 双重检测锁
- Object instance = cachedAdaptiveInstance.get(). 从缓存中获取实例
- instance = createAdaptiveExtension() 创建自适应实例
- injectExtension((T) getAdaptiveExtensionClass().newInstance())
- getAdaptiveExtensionClass() 获取adaptive类,是个.class
- newInstance() 是调用这个.class的无参构造器创建一个adaptive实例
- injectExtension() 完成adaptive实例的IoC注入
- getAdaptiveExtensionClass. ==> Class<?> 获取自适应类,返回结果是一个class
- getExtensionClasses. 将当前SPI接口的所有扩展类(四类:普通扩展类、adaptive类、wrapper类及activate类). 全部加载并进行缓存
- Holder<Map<String, Class<?>>> cachedClasses. 直接扩展类的缓存 (普通扩展类和activate 类激活类) key为类前缀key,value为class
- Map<String, Class<?>> classes = cachedClasses.get() 获取直接扩展类的缓存
- classes = loadExtensionClasses() 加载并缓存直接扩展类
- cacheDefaultExtensionName(). 加载并缓存SPI接口的默认扩展类名称
- final SPI defaultAnnotation = type.getAnnotation(SPI.class). 从SPI接口中获取到SPI注解
- String value = defaultAnnotation.value(). 获取SPI注解的value属性
- String[] names = NAME_SEPARATOR.split(value). 使用逗号分隔value属性值
- names.length > 1 throw exception 如果分割出了多个路径抛出异常
- cachedDefaultName = names[0] 设置默认的名称(key) SPI注解中的value
- for (LoadingStrategy strategy : strategies). 从三种路径中将配置文件中的类加载并缓存
- loadDirectory 加载目录
- String fileName = dir + type; 拼接出文件名
- Enumeration<java.net.URL> urls. 将配置文件加载并转换为URL,加载出来的文件是多个在不同的模块下是可能存在相同文件的
- while (urls.hasMoreElements()) 遍历urls
- loadResource. 加载配置文件内容
- while ((line = reader.readLine()) != null). 逐行解析配置文件
- name = line.substring(0, i).trim(). 解析出功能性扩展名
- clazz = line.substring(i + 1).trim(). 解析出扩展类名
- loadClass(extensionClasses, resourceURL, …) 加载这个类
- !type.isAssignableFrom(clazz). 判断当前clazz是否实现了当前的SPI接口类型type
- clazz.isAnnotationPresent(Adaptive.class). 判断当前clazz类上是否出现了@Adaptive注解
- cacheAdaptiveClass(clazz, overridden). 缓存这个clazz
1. if (cachedAdaptiveClass == null || overridden)
2. cachedAdaptiveClass = clazz; 自适应扩展类的缓存,只能有1个,多的话抛出异常
3. 若当前缓存中的类与clazz不相同,则抛出异常,为什么?一个SPI接口只允许有一个Adaptive类,无论是自定义的,还是自动生成的 - isWrapperClass(clazz). 判断当前类是否是wrapper类
1. clazz.getConstructor(type). 获取当前clazz的单参构造器,且这个参数为SPI类型,若没有抛出,则说明当前clazz不是个wrapper类 - cacheWrapperClass(clazz). 缓存这个clazz
1. Set<Class<?>> cachedWrapperClasses
2. cachedWrapperClasses = new ConcurrentHashSet<>()
3. cachedWrapperClasses.add(clazz). 一个SPI允许有多个wrapper类 - 对直接扩展类(普通扩展类与activate类)情况的处理
- clazz.getConstructor(). 验证当前clazz是否具有无参构造器。若没有,则直接抛出异常。若有,则当前clazz是扩展类,SPI直接扩展类要求,必须要有无参构造器
- StringUtils.isEmpty(name). 若功能性扩展名为空,则为其找一个
- 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结尾,则直接返回类名,全小写字母 - String[] names = NAME_SEPARATOR.split(name); 使用逗号将扩展名分隔为多个
- 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激活类注解放入缓存 - for (String n : names) 遍历所有扩展名
- cacheName(clazz, n) 缓存名称
1. ConcurrentMap<Class<?>, String> cachedNames. 类名称的缓存,key为class,value为第一个直接扩展类的名称
2. cachedNames.put(clazz, name); 将第一个扩展名与clazz配对后缓存 - 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. 一个扩展名不能对应多个扩展类 - cachedAdaptiveClass != null ==> return cachedAdaptiveClass 如果缓存的自适应class不为空,返回缓存的自适应扩展类
- cachedAdaptiveClass = createAdaptiveExtensionClass(). 若缓存中没有adaptive类,则创建一个,创建adaptive类(要求必须要有adaptive方法)???
- getExtension(String name) 根据名称获取扩展类
- T extension = getExtension(name, true). 第二个参数 boolean wrap, 是否使用增强
- getExtension(String name, boolean wrap)
- “true”.equals(name). 若name指定为“true”,则加载SPI默认扩展名的实例
- return getDefaultExtension(). 获取默认扩展名的实例
- getExtensionClasses(); 加载并缓存“四类”,获取并缓存SPI接口的默认扩展名
- getExtension(cachedDefaultName). 加载默认扩展名的实例
- ConcurrentMap<String, Holder> cachedInstances. key名称和对应实例的缓存
- Holder holder = cachedInstances.get(name). 从缓存中获取指定name对应的扩展类实例的持有者holder
- holder == null ==> cachedInstances.putIfAbsent(name, new Holder<>()). holder = cachedInstances.get(name);
- instance = createExtension(name, wrap) 创建直接扩展类的实例
- Class<?> clazz = getExtensionClasses().get(name); 获取name的直接扩展类class
- ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES 实例的缓存,key为class,value为该class对应的实例对象
- T instance = (T) EXTENSION_INSTANCES.get(clazz); 从缓存中获取该clazz对应的instance实例
- EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance()); 实例为null,则创建一个实例,放入缓存
- injectExtension(instance). 完成实例的IoC注入,设置属性
**插件的 IOC 注入源码解析 **
- dubbo 的注入: set方法设置的对象为SPI接口,就会自动在系统中寻找该对象的自适应对象设置进去
- 两种注册方式,通过dubbo的SPI找到自适应类,设置到属性上,通过Spring从容器中获取到实例,然后设置到属性上
- 以注册中心通信协议 RegsitryProtocol 实例注入具体通信协议 Protocol 为例来解析IoC 的过程
- InterfaceCompatibleRegistryProtocol. 为Protocol 的实现类, 有setProtocol方法,该set方法需要将Protocol接口通过SPI自适应,将实现类对象设置到RegistryProtocol 的属性上
- ReferenceConfig #. createProxy
- invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)). REF_PROTOCOL==> Protocol$Adaptive
- InterfaceCompatibleRegistryProtocol. register 注册中心的实现类
- Protocol$Adaptive ==> getExtension(extName). extName == register
- injectExtension(instance). 完成实例的IoC注入,设置属性
- for (Method method : instance.getClass().getMethods()) 遍历当前实例类的所有方法
- if (!isSetter(method)) {. 当前方法不是set方法,跳过
- method.getAnnotation(DisableInject.class) != null 若当前setter方法上被@DisableInject修饰,则说明当前setter不会自动注入,则跳过
- Class<?> pt = method.getParameterTypes()[0]. 获取当前setter的参数类型
- ReflectUtils.isPrimitives(pt) ==> continue; 若当前参数类型为基本数据类型,则跳过, 即Dubbo的自动注入,只会注入对象
- String property = getSetterProperty(method). 获取setter方法的参数名
- Object object = objectFactory.getExtension(pt, property). 创建当前setter的实参对象值。其依次尝试着使用SPI与Spring容器方式创建。扩展类工厂获取扩展类
- AdaptiveExtensionFactory. ==> getExtension.
- for (ExtensionFactory factory : factories). 依次通过SPI与Spring两种方式获取实例
- SpiExtensionFactory. ==> getExtension(Class type, String name). 利用Dubbo的SPI获取实例
- type.isInterface() && type.isAnnotationPresent(SPI.class). 若当前type为SPI接口
- ExtensionLoader loader = ExtensionLoader.getExtensionLoader(type); 获取当前type的扩展类加载器
- !loader.getSupportedExtensions().isEmpty() 如果当前类的直接扩展类不为空
- loader.getAdaptiveExtension() 返回当前类的自适应实现类
- SpringExtensionFactory. ==> getExtension(Class type, String name) 通过spring容器获取到实例
- if (type.isInterface() && type.isAnnotationPresent(SPI.class)). ==> return null; 若当前type为SPI接口,则直接结束
- for (ApplicationContext context : CONTEXTS) {. 遍历所有Spring容器
- T bean = BeanFactoryUtils.getOptionalBean(context, name, type); 先从当前遍历容器中尝试着获取指定name的bean,若没有,则再尝试着获取指定type的bean
- method.invoke(instance, object). 调用setter,完成注入
Dubbo的AOP包装源码解析
- Dubbo 的 AOP 是对 SPI 扩展类进行增强的方式,而 Wrapper 机制就是对 SPI 扩展类的增强。不同 SPI 的不同 Wrapper,其增强的功能不同
- getExtensionLoader 会加载配置文件中的内容,将普通扩展类,自适应扩展类,wrapper包装类和activate激活类进行缓存
- getAdaptiveExtension 获取到的是自适应扩展类
- getExtension 根据名称获取到具体的实现类
- Class<?> clazz = getExtensionClasses().get(name). 根据名称获取class
- clazz.getDeclaredConstructor().newInstance() 创建class对应的实例
- if (wrap) 是否增强
- List<Class<?>> wrapperClassesList = new ArrayList<>(). 将缓存中的wrapper set集合写入到list,并重新排序
- wrapperClassesList.addAll(cachedWrapperClasses) 将缓存的包装类放入到集合中
- for (Class<?> wrapperClass : wrapperClassesList). 遍历所有wrapper
- instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
- wrapperClass.getConstructor(type) 获取当前wrapper的单参构造器
- newInstance(instance) 调用单参构造器创建实例
- injectExtension() 调用wrapper的setter完成IoC注入
自动生成类的动态编译源码解析
- 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[]{});
}
}
- cachedAdaptiveClass = createAdaptiveExtensionClass(). 创建自适应类
- String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate() 生成自适应的code数据
- !hasAdaptiveMethod() 若没有@Adaptive,则直接抛出异常
- StringBuilder code = new StringBuilder(). 拼接代码字符串
- compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension() 获取compile的自适应类
- compiler.compile(code, classLoader). 调用自适应compiler完成自动编译
- AdaptiveCompiler # compile
- ExtensionLoader.getExtensionLoader(Compiler.class). 获取Compiler接口的扩展类加载器
- compiler = loader.getDefaultExtension() 获取默认的实现类. ==>. JavassistCompiler
- compiler.compile(code, classLoader) 调用AbstractCompiler的compile()
- AbstractCompiler # compile. 父类的compile方法
- 获取package信息. ==> 获取class信息 ==> 拼接出类的全限定性类名. ==> 加载指定的类. ==>如果没有该Class类
- doCompile(className, code). 调用子类的doCompile
- JavassistCompiler # doCompile
- CtClass cls = builder.build(classLoader). 使用构建器构建出ctClass实例
- cls.toClass. 完成真正的编译,形成字节码
ExtensionLoader 中的缓存
- Map<String, Object> cachedActivates 缓存激活类
- Set<Class<?>> cachedWrapperClasses wrapper 类缓存
- ConcurrentMap<Class<?>, String> cachedNames class 和名称key的缓存
- Holder<Map<String, Class<?>>> cachedClasses . 名称和class的缓存
- Map<String, Class<?>> extensionClasses 名称和class(cachedClasses 中存放的内容)
- Class<?> cachedAdaptiveClass 自适应类的缓存
- ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES 扩展实例 key为class,value为实例
- ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS . loader的cache key为class对象,value 为 ExtensionLoader(里边有各种缓存)
Dubbo 源码
Dubbo与Spring整合
- spring.handlers. 存放的是命名空间的处理器
- spring.schemas 存放的是命名空间的dtd,xsd文件
- DubboNamespaceHandler # init() 注册了各种标签的解析器
- DubboBeanDefinitionParser # parse
- element. 当前要解析的标签
- parserContext. 解析上下文,其中包含了当前配置文件中所有其它标签的解析信息
- beanClass. 当前标签解析出的内容要封装的类,其中就是存放的从标签中读出的属性。读到了什么就录下什么
- registered. 解析出的标签是否需要注解到注册中心
- return 解析对象。将读取出的数据最终要构建出一个“逻辑”上的对象。例如,构建出一个“注册中心”对象、“协议”对象等
- RootBeanDefinition beanDefinition = new RootBeanDefinition(); 创建并初始化解析对象
- String configId = resolveAttribute. 解决id问题: 为空与重复问题
- String beanName = configId; bean名称取id属性值
- String prefix = beanClass.getName(). 如果Id属性为空,取类名与数字的拼接,用# 进行拼接
- beanDefinition.setAttribute(BEAN_NAME, beanName). 设置beanName
- ProtocolConfig.class.equals(beanClass). 对特殊标签的处理,对dubbo:protocol/标签的处理
- ServiceBean.class.equals(beanClass). 对dubbo:service/标签的处理
- Map<String, Class> beanPropTypeMap = beanPropsCache.get(beanClass.getName()). 对普通标签的普适性处理
Dubbo中的中的重要接口
- Invocation. 调用信息. 方法名,参数, attachments 附件信息,键值对
- Invoker<?> getInvoker().
- Invoker 在提供者端为提供者的代理对象,消费端,为提供者的委托对象,从注册中心获取到接口进行调用
- Class getInterface(). 代理的接口Class
- Result invoke(Invocation invocation). 进行方法调用
- Node. ===> URL getUrl() url参数,用作自适应
- Exporter. 服务暴露对象,里边封装了Inovker. ==> getInvoker. ===> unexport. 和服务暴露协议有关
- DubboExporter. dubbo服务暴露
- Map<String, Exporter<?>> exporterMap 服务暴露map key 为group/interface:version.value 为Exporter 暴露对象,里边封装了Invoker
- InjvmExporter jvm服务暴露协议
- Map<String, Exporter<?>> exporterMap. ==> exporterMap.put(key, this). 将当前exporter写入到缓存map
- Directory. 维护着相同服务的invoker列表
- Class getInterface()
- List<Invoker> list(Invocation invocation) 列出interface对应服务的提供者(进行了过滤)
- List<Invoker> getAllInvokers(). 获取所有的提供者
- DynamicDirectory implements NotifyListener 动态列表,实现了NoticeListener ,通过watcher机制,服务提供者发生变更,能够通知到
- RegistryDirectory. ServiceDiscoveryRegistryDirectory 属于动态列表
- StaticDirectory 静态列表, 启动后,注册中心获取到的invoker列表是不会变的
- MockDirectory 属于静态列表
- MockDirectory 属于静态列表
服务发布
- 主要两个内容: 一个是注册到注册中心,一个是把provider封装为DubboExporter放到本地缓存
- 本地服务暴露,消费者和提供者都在本地
- 服务暴露对象
- DubboExporter
- InjvmExporter
- DubboBootstrapApplicationListener. # onApplicationEvent. 当Spring容器创建时会触发该方法的执行
- event instanceof ContextRefreshedEvent. 容器刷新事件
- dubboBootstrap.start(). 启动Dubbo
- exportServices() 服务者, 服务暴露
- exportMetadataService() 暴露元数据信息
- registerServiceInstance() 注册服务实例
- referServices() 消费者, 服务引用
- ServiceConfigBase sc : configManager.getServices(). 遍历当前配置文件中的所有dubbo:service/标签
- sc.shouldExportAsync(). 判断是否是异步暴露
- Boolean shouldExportAsync = getExportAsync(). 获取dubbo:service/中的export-async属性
- shouldExportAsync == null. 消费端的该属性为空,获取提供者端的属性
- shouldExportAsync = provider != null && provider.getExportAsync() != null && provider.getExportAsync(). 获取dubbo:provider/标签中的export-async属性
- dubbo 中属性的优先级
- 消费端中没有获取服务端中的值
- sc.export(); 服务暴露
- this.shouldExport() && !this.exported. 若dubbo:service/的export属性为true,且当前服务尚未暴露
- doExport(). 服务暴露
- StringUtils.isEmpty(path). 若dubbo:servcie/的path属性为空,则取interface属性值, URL的格式 protocol://ip:port/path?a=b&b=c&…
- doExportUrls(); 假设有3个注册中心,2个服务暴露协议,为每个服务暴露协议在每个注册中心中进行暴露
- dubbo 支持多服务暴露协议
- Dubbo也支持多注册中心
- 为每个暴露协议在每个注册中心进行暴露
- List registryURLs = ConfigValidationUtils.loadRegistries(this, true). 获取所有注册中心的【标准化地址URL】与【兼容性地址URL】, true 表示为provider
- ApplicationConfig application = interfaceConfig.getApplication(). 获取dubbo:application/标签
- List registries = interfaceConfig.getRegistries(). 获取dubbo:registry/标签
- for (RegistryConfig config : registries) { 遍历所有dubbo:registry/标签
- String address = config.getAddress(). 获取dubbo:registry/标签的address属性
- Map<String, String> map = new HashMap<String, String>(). 创建并初始化一个map,这个map中的值为dubbo:registry/标签及相当标签中的属性值
- AbstractConfig.appendParameters(map, application); 将dubbo:application/标签属性写入map
- AbstractConfig.appendParameters(map, config); 将dubbo:registry/标签属性写入map
- List urls = UrlUtils.parseURLs(address, map) 构建注册中心标准URl
- String[] addresses = REGISTRY_SPLIT_PATTERN.split(address). 使用分号分隔address,分隔出多个注册中心地址
- for (String addr : addresses) { 遍历所有注册中心地址
- parseURL(String address, Map<String, String> defaults) 解析注册中心地址
- address.contains(“😕/”) || address.contains(URL_PARAM_STARTING_SYMBOL). 若address本身包含://或?,那么其本身就是一个URL,是一个标准地址形式.
- 标准的地址形式 zookeeper://localhost1:2181?backup=localhost2:2181,localhost3:2181
- String[] addresses = COMMA_SPLIT_PATTERN.split(address); 使用逗号分隔出一个注册中心集群中的所有主机地址
- addParameter(REGISTRY_KEY, url.getProtocol()). 向URL中添加registry属性,例registry=zookeeper
- setProtocol(extractRegistryType(url)). 将URL的协议修改为registry. 将zookeeper,redis…注册中心,开头变为registry,将具体的注册中心放到registry 地址参数中
- registryList.add(url) 将url添加到registryList 注册地址list中
- 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"
- for (ProtocolConfig protocolConfig : protocols) 遍历所有服务暴露协议(dubbo:protocol/)
- doExportUrlsFor1Protocol(protocolConfig, registryURLs). 使用当前遍历的服务暴露协议与所有注册中心配对进行服务暴露 ,1个暴露协议对应多个注册中心
- Map<String, String> map = buildAttributes(protocolConfig). 构建服务暴露URL要使用的map
- serviceMetadata.getAttachments().putAll(map). 元数据注册
- URL url = buildUrl(protocolConfig, registryURLs, map). 构建服务暴露URL
- exportUrl(url, registryURLs). 服务暴露
- 本地暴露: 将InjvmExporter 写入到缓存map中
- 有注册中心的远程暴露: 将DubboExporter 写入到缓存map中(与netty进行关联),连接zookeeper并向注册中心创建临时节点
- 没有注册中心的远程暴露: 将DubboExporter 写入到缓存map中(与netty进行关联)
- String scope = url.getParameter(SCOPE_KEY). 获取dubbo:service/的scope属性
- !SCOPE_NONE.equalsIgnoreCase(scope). 若scope的值不等于none,则进行暴露
- !SCOPE_REMOTE.equalsIgnoreCase(scope). 若scope的值不等于remote,则进行本地暴露
- exportLocal(url). 本地暴露
- setProtocol(LOCAL_PROTOCOL). 将URL的protocol设置为injvm
- setPort(0) URL没有端口号
- doExportUrl(local, false). 本地暴露
- Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url); 构建出invoker
- Exporter<?> exporter = PROTOCOL.export(invoker). 本地暴漏
- Protocol$Adaptive. # extension.export(arg0)
- ProtocolListenerWrapper # export
- InjvmProtocol. # export
- new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap). 创建InjvmExporter 并将该对象放入exporterMap中
- exporterMap.put(key, this). 将当前exporter写入到缓存map
- InjvmProtocol. 中维护着. Map<String, Exporter<?>> exporterMap. key 为serviceKey ,value 为InjvmExporter
- !SCOPE_LOCAL.equalsIgnoreCase(scope). 若scope的值不等于local,则进行远程暴露
- url = exportRemote(url, registryURLs) 远程暴露
- if (CollectionUtils.isNotEmpty(registryURLs)) {. 处理有注册中心的情况
- for (URL registryURL : registryURLs) 遍历所有注册中心URL
- doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true) 远程暴露(仅跟踪registryURL地址为 registry://… 的地址)
- Invoker<?> invoker = PROXY_FACTORY.getInvoker 构建出invoker
- Protocol$Adaptive # extension.export(arg0)
- ProtocolFilterWrapper # export
- UrlUtils.isRegistry(invoker.getUrl()). 地址是注册
- protocol.export(invoker). 进行服务注册
- RegistryProtocol # export. 注册中心进行注册
- URL registryUrl = getRegistryUrl(originInvoker) 获取注册中心URL,将地址复原zookeeper 开头
- String protocol = registryUrl.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY); 获取regsitry属性值,这里的值为zookeeper
- registryUrl = registryUrl.setProtocol(protocol).removeParameter(REGISTRY_KEY); 将URL中的protocol由registry替换为zookeeper,并将registry属性删除
- URL providerUrl = getProviderUrl(originInvoker) 获取服务暴露URL,要写到zookeeper中provider的地址
- Object providerURL = originInvoker.getUrl().getAttribute(EXPORT_KEY); 获取URL中的export属性值,即要暴露的服务的URL.
- final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl). 服务暴露,即将服务的exporter写入到缓存map
- protocol.export(invokerDelegate)
- Protocol$Adaptive # extension.export
- extName = dubbo
- ProtocolListenerWrapper # export ==> protocol.export(invoker)
- DubboProtocol # export
- DubboExporter exporter = new DubboExporter(invoker, key, exporterMap) 将invoker封装为了DubboExporter. DubboExporter 需要与NettyServer进行绑定,别的服务需要通过它进行提供者的调用
- exporterMap.put(key, exporter). 将exporter写入到缓存map
- openServer(url). 创建并启动Netty Server
- String key = url.getAddress(); key格式 ip:暴露协议端口 例如,192.168.59.1:20881
- boolean isServer = url.getParameter(IS_SERVER_KEY, true). 表示当前应用是否是provider
- ProtocolServer server = serverMap.get(key) 从缓存map中获取当前key对应的同异步转换对象。从key的值可知,一个应用中每个服务暴露协议都会对应一个同异步转换对象,而一个同异步转换对象会对应一个Netty Server ProtocolServer 与Exchange 对应
- 一个主机中每个服务暴露协议会对应创建一个Netty Server
- serverMap.put(key, createServer(url)). 创建同异步转换对象
- Exchangers.bind(url, requestHandler)
- new HeaderExchangeServer. 转换服务器
- Transporter$Adaptive # extension.bind(). extName 默认为netty
- new NettyServer(url, handler)
- AbstractServer # 构造方法. ==> doOpen() 创建并启动Netty Server
- NettyServer. #. doOpen. netty的代码
- ChannelFuture channelFuture = bootstrap.bind(getBindAddress()). 启动Netty Server
- 20881 netty服务绑定的地址
- Registry registry = getRegistry(registryUrl). 获取注册中心实例
- RegistryFactory$Adaptive # extension.getRegistry(arg0)
- AbstractRegistryFactory # getRegistry
- Registry registry = REGISTRIES.get(key). 从缓存获取当前服务注册中心URL对应的实例。 key为zookeeper://zookeeperOS:2181/org.apache.dubbo.registry.RegistryService
- registry = createRegistry(url). 创建注册中心实例
- ZookeeperRegistryFactory # createRegistry
- new ZookeeperRegistry(url, zookeeperTransporter)
- ZookeeperRegistry # 构造方法
- zkClient = zookeeperTransporter.connect(url). 创建zk客户端,与zk进行连接
- REGISTRIES.put(key, registry) 放入到缓存中
- register(registry, registeredProviderUrl). 注册到注册中心
- doRegister(url) 进行注册
- zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true)). 创建一个临时节点
- /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. 注册的节点信息
- 处理没有注册中心的情况
- doExportUrl(url, true) 远程暴露(与上面的暴露相比,仅没有向注册中心注册)直连的方式,消费端把url地址写死的情况,会通过DubboExporter 与NettyServer进行关联让其它服务进行调用
服务订阅-创建consumer的代理对象
- DubboBootstrapApplicationListener. # onApplicationEvent. 当Spring容器创建时会触发该方法的执行
- event instanceof ContextRefreshedEvent. 容器刷新事件
- dubboBootstrap.start(). 启动Dubbo
- referServices()
- configManager.getReferences(). 遍历所有dubbo:reference/标签
- rc.shouldInit(). 判断当前dubbo:reference/标签引用的实例是否需要立即初始化
- Boolean shouldInit = isInit(); 获取标签的init属性
- shouldInit == null && getConsumer() != null. shouldInit = getConsumer().isInit(). 若init为空,则获取dubbo:consumer/中的init属性
- cache.get(rc); 处理同步引用
- String key = generator.generateKey(referenceConfig). 生成服务标识key,其格式为 group/interface:version
- Class<?> type = referenceConfig.getInterfaceClass(). 获取业务接口class
- ConcurrentMap<String, Object> proxiesOfType = proxies.computeIfAbsent(type, _t -> new ConcurrentHashMap<>())
- 从缓存map中获取当前业务接口的内层map,即获取到当前业务接口的所有服务
- proxies为一个缓存map,其为一个双层map。该缓存map中存放的是业务接口对应的所有服务
- 外层map的key为业务接口class,value为一个内层map
- 内层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;
});
- Object proxy = referenceConfig.get(). 创建代理对象
- init(). 创建代理对象
- Map<String, String> map = new HashMap<String, String>(); ==> map.put(SIDE_KEY, CONSUMER_SIDE). 创建并初始化一个URL使用的map
- serviceMetadata.getAttachments().putAll(map). 注册元数据
- ref = createProxy(map). 创建代理对象
- invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)). 获取provider的委托对象
- (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic)) 创建代理对象
- ProxyFactory$Adaptive # getProxy #. extension.getProxy(arg0, arg1) extName = javassist
- proxyFactory.getProxy(invoker, generic). 创建代理对象
- Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker))
- getProxy() 创建代理类proxy的class
- newInstance() 创建这个class的实例,即代理对象.
- 创建InvokerInvocationHandler,consmer 的动态代理调用类
- Proxy # getProxy
- proxy = (Proxy) pc.newInstance(). 创建proxy的class
服务订阅-createProxy 方法
- ReferenceConfig # createProxy
- shouldJvmRefer(map) 判断是否是本地引用
- isInjvm() == null. 若dubbo:reference/的injvm属性为null
- url != null && url.length() > 0 ==> return false. 若dubbo:reference/的url属性不空,则为直连引用,不属于本地引用
- isJvmRefer = InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl). 若dubbo:reference/的injvm与url属性均为空,则继续判断
- isJvmRefer = isInjvm() 返回injvm 配置
- invoker = REF_PROTOCOL.refer(interfaceClass, url) 从本地的缓存中获取到Invoker返回, InjvmExporter
- 处理远程引用
- url != null && url.length() > 0 若dubbo:reference/的url属性不空,则当前对provider的调用为“直连”调用,无需注册中心
- 处理有注册中心的调用情况
- checkRegistry() 处理有注册中心的调用情况
- convertRegistryIdsToRegistries. 获取合并dubbo:application/ 和dubbo:reference/ 标签中的registry属性值,注册中心的ID值
- for (RegistryConfig registryConfig : registries). 遍历所有指定的注册中心
- !registryConfig.isValid() ==>. !StringUtils.isEmpty(address)只要有一个注册中心不可用,就抛出异常
- List us = ConfigValidationUtils.loadRegistries(this, false). 获取注册中心的标准URL,其仅仅包含registry://…格式的URL, 没有service-discovery-registry://…的URL
- for (URL u : us). 遍历所有注册中心URL
- URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u)获取所有监控中心URL
- u = u.putAttribute(MONITOR_KEY, monitorUrl). 以monitor属性的形式将监控中心URL添加到注册中心URL中
- urls.add(u.putAttribute(REFER_KEY, map)). 以refer属性的形式将消费者URL添加到注册中心URL中
- 注册中心地址既可以获取到注册中心的地址,也可以通过refer获取到消费者的信息
- urls.size() == 1. 如果只有1个注册中心,创建1个Invoker
- invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0))
- 1个Invoker代表1个注册中心的所有provider
- 有多个注册中心,会创建多个Invoker
- for (URL url : urls) { 遍历注册中心的地址
- invokers.add(REF_PROTOCOL.refer(interfaceClass, url)) 创建注册中心对应的Invoker对象并放入到list中
- if (registryURL != null) {. 若有可用的注册中心
- invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
- 将所有注册中心的Invoker封装为1个Invoker
- 由于服务启动后,注册中心的地址个数就不可变了,所以这里的Directory是静态的(StaticDirectory)
- 注册中心Invoker中对应的provider是可变的,所以注册中心里边的Directory是动态的
服务订阅-REF_PROTOCOL.refer 从注册中心地址获取provider
- invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0))
- Protocol$Adaptive # extension.refer(arg0, arg1). extName # register
- RegistryProtocol # refer
- Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY)). 获取cluster属性,即集群容错策略,默认failover策略. 获取消费者标签dubbo:reference/ 中的failover属性
- doRefer(cluster, registry, type, url, qs) 继续订阅 返回的Invoker中有集群容错的内容
- ClusterInvoker migrationInvoker = getMigrationInvoker. 创建一个具有“注册中心迁移”功能的invoker,允许一个Invoker从一个注册中心迁移到另一个注册中心
- interceptInvoker(migrationInvoker, url, consumerUrl, url). 拦截invoker,为invoker添加迁移监听器
- for (RegistryProtocolListener listener : listeners) 遍历所有监听器,为订阅过程添加这些监听功能
- listener.onRefer
- MigrationRuleListener # onRefer
- MigrationRuleHandler<?> migrationRuleHandler = handlers.computeIfAbsent. 从缓存中获取迁移规则处理器
- migrationRuleHandler.doMigrate(rule) 将迁移规则应用于迁移规则处理器
- refreshInvoker(step, threshold, rule). 根据注册中心更新本地invoker委托对象
- switch (step) {. 根据不同迁移步骤,采用不同刷新方式
- migrationInvoker.migrateToApplicationFirstInvoker(newRule). 应用优先
- refreshInterfaceInvoker(latch). 刷新invoker
- invoker = registryProtocol.getInvoker(cluster, registry, type, url). 根据注册中心更新本地委托对象invoker
- DynamicDirectory directory = new RegistryDirectory<>(type, url). 创建了一个动态列表(里边维护了provider)
- ClusterInvoker invoker = doCreateInvoker(directory, cluster, registry, type). 创建invoker
- registry.register(directory.getRegisteredConsumerUrl()). 将当前consumer注册到注册中心
- 给zookeeper接口节点下边的子节点consumers中添加consumer的url
- directory.buildRouterChain(urlToRegistry). 将所有RouterFactory激活扩展类创建的router添加到directory
- directory.subscribe(toSubscribeUrl(urlToRegistry)). 服务订阅
- toSubscribeUrl(urlToRegistry). 地址中添加属性category=providers,configurators,routers
- ZookeeperRegistry # doSubscribe
- if (ANY_VALUE.equals(url.getServiceInterface())). 处理dubbo:reference/的interface属性为"*"的情况
- 处理dubbo:reference/的interface属性为普通值的情况
- for (String path : toCategoriesPath(url)) 遍历configurators、routers与providers三个路径
- zkClient.create(path, false); 创建持久节点
- List children = zkClient.addChildListener(path, zkListener). 为创建的节点添加子节点列表变更的watcher监听
- urls.addAll(toUrlsWithEmpty(url, path, children))
- toUrlsWithEmpty. 如果节点下没有子节点会创建空的url地址
- boolean isProviderPath = path.endsWith(PROVIDERS_CATEGORY). 判断当前path是否以providers结尾
- if (isProviderPath) { 如果是providers
- urls = toUrlsWithoutEmpty(consumer, providers). 将providers节点的所有子节点变为url
- 处理configurators与routers节点情况
- urls = toConfiguratorsWithoutEmpty(consumer, providers). 将节点的所有子节点变为url
- if (urls.isEmpty()) ==> urls.add(empty); 为没有子节点的节点创建一个 empty://…的URL
- notify(url, listener, urls). 主动调用notify更新本地invoker
- AbstractRegistry # notify
- Map<String, List> result = new HashMap<>() map的key为configurators、routers与providers value为这些节点下对应的子节点Url
- listener.notify(categoryList). 主动调用各分类节点的notify(),更新到本地
- RegistryDirectory # notify
- refreshOverrideAndInvoker(providerURLs) 刷新invoker
- (ClusterInvoker) cluster.join(directory). 将多个invoker伪装为一个具有复合功能的invoker ,cluster 具有容错功能, directory 为一个动态列表,返回的Invoker 有一堆provider和一些功能
- MockClusterWrapper # join
- new MockClusterInvoker(directory, this.cluster.join(directory)).
- this.cluster 为 FailoverCluster
- AbstractCluster # join
- new FailoverClusterInvoker<>(directory)
- registry.register(directory.getRegisteredConsumerUrl()). 将当前consumer注册到注册中心
- calcPreferredInvoker(newRule). this.currentAvailableInvoker在该方法中进行的赋值
- this.currentAvailableInvoker = invoker;
- this.currentAvailableInvoker = invoker;
服务订阅-RegistryDirectory.refreshOverrideAndInvoker 刷新invoker
- refreshInvoker(urls). 刷新invoker, 更新本地invoker列表, urls zk中的url地址
- EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol()). 判断是否有empty://开头
- this.forbidden = true. 禁止远程调用
- destroyAllInvokers(). 将缓存中的所有invoker删除
- 正常的url
- this.forbidden = false; 允许远程访问
- Map<URL, Invoker> oldUrlInvokerMap = this.urlInvokerMap. 先将缓存map发生变更前的值进行暂存
- Map<URL, Invoker> urlInvokerMap
- 缓存map,更新本地invoker列表,就是更新这里
- key为URL,value为该URL对应的invoker实例(invoker委托对象)
- invokerUrls zk中的provider的url cachedInvokerUrls 本地缓存的url
- invokerUrls.addAll(this.cachedInvokerUrls). 如果zk的url为空,本地缓存不为空,将本地的添加到zk中的url,如果zk中的地址没有了,将本地缓存的地址添加进去,增加可用性 提供者端与zk的连接掉了,消费端依然可以从本地缓存获取到提供者端的地址进行调用
- zk的url不为空,将zk的url添加到本地的url中
- invokerUrls.isEmpty(). ==> return 走到这里invokerUrls仍为空,则说明真的是没有任何可用的invoker
- Map<URL, Invoker> newUrlInvokerMap = toInvokers(invokerUrls) 从缓存map中获取相应的invoker,若存在,则返回,并将其从缓存map中删除,若不存在,则创建一个invoker, 保证了缓存中剩下的invoker是有问题的
- 对urls的各种检测
- URL url = mergeUrl(providerUrl). 将provider相关的配置进行合并. 优先级 override > -D >Consumer > Provider 例子:如果provider和consumer中都配置了相同的属性,合并为consumer中的属性
-
- List<Invoker> newInvokers = Collections.unmodifiableList(newUrlInvokerMap). 获取到最新的可用invoker列表
- this.urlInvokerMap = newUrlInvokerMap; 将缓存map更新为新map
- Map<URL, Invoker> localUrlInvokerMap = this.urlInvokerMap; 本地的缓存map
- Invoker invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.remove(url)
- 若缓存map为空,则直接返回null,说明缓存map中没有当前url对应的invoker
- 若缓存map不空,则获取缓存map中该url对应的invoker,同时将该entry删除,剩下的就是没有用的Invoker
- if (invoker == null). 如果invoker为空,创建新的Invoker
- enabled = url.getParameter(ENABLED_KEY, true). 从url中获取disabled或enabled属性
- if (enabled) ==> invoker = protocol.refer(serviceType, url). 若当前url中没有禁用该invoker, 创建invoker委托对象, 该Invoker对象会和提供端的Invoker进行联系调用
- if (invoker != null)
- newUrlInvokerMap.put(url, invoker). 将创建的委托对象写入到新map
- destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); 删除老map中剩余的invoker(不可用的Invoker)
- if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) 若最新的invoker列表为空,则说明当前已经没有任何可用的invoker了,此时将缓存map中的所有invoker全部删除
- destroyAllInvokers()
- for (Map.Entry<URL, Invoker> entry : oldUrlInvokerMap.entrySet()) 将原来缓存map中的invoker全部删除,因为它们中的这些已经没用了,最新可用的都在新map中
- invoker.destroy();
- invoker.destroy();
服务订阅-RegistryDirectory.refer 刷新invoker
- refreshInvoker(urls). 刷新invoker, 更新本地invoker列表, urls zk中的url地址
- EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol()). 判断是否有empty://开头
- this.forbidden = true. 禁止远程调用
- destroyAllInvokers(). 将缓存中的所有invoker删除
- 正常的url
- this.forbidden = false; 允许远程访问
- Map<URL, Invoker> oldUrlInvokerMap = this.urlInvokerMap. 先将缓存map发生变更前的值进行暂存
- Map<URL, Invoker> urlInvokerMap
- 缓存map,更新本地invoker列表,就是更新这里
- key为URL,value为该URL对应的invoker实例(invoker委托对象)
- invokerUrls zk中的provider的url cachedInvokerUrls 本地缓存的url
- invokerUrls.addAll(this.cachedInvokerUrls). 如果zk的url为空,本地缓存不为空,将本地的添加到zk中的url,如果zk中的地址没有了,将本地缓存的地址添加进去,增加可用性 提供者端与zk的连接掉了,消费端依然可以从本地缓存获取到提供者端的地址进行调用
- zk的url不为空,将zk的url添加到本地的url中
- invokerUrls.isEmpty(). ==> return 走到这里invokerUrls仍为空,则说明真的是没有任何可用的invoker
- Map<URL, Invoker> newUrlInvokerMap = toInvokers(invokerUrls) 从缓存map中获取相应的invoker,若存在,则返回,并将其从缓存map中删除,若不存在,则创建一个invoker, 保证了缓存中剩下的invoker是有问题的
- 对urls的各种检测
- URL url = mergeUrl(providerUrl). 将provider相关的配置进行合并. 优先级 override > -D >Consumer > Provider 例子:如果provider和consumer中都配置了相同的属性,合并为consumer中的属性
-
- List<Invoker> newInvokers = Collections.unmodifiableList(newUrlInvokerMap). 获取到最新的可用invoker列表
- this.urlInvokerMap = newUrlInvokerMap; 将缓存map更新为新map
- Map<URL, Invoker> localUrlInvokerMap = this.urlInvokerMap; 本地的缓存map
- Invoker invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.remove(url)
- 若缓存map为空,则直接返回null,说明缓存map中没有当前url对应的invoker
- 若缓存map不空,则获取缓存map中该url对应的invoker,同时将该entry删除,剩下的就是没有用的Invoker
- if (invoker == null). 如果invoker为空,创建新的Invoker
- enabled = url.getParameter(ENABLED_KEY, true). 从url中获取disabled或enabled属性
- if (enabled) ==> invoker = protocol.refer(serviceType, url). 若当前url中没有禁用该invoker, 创建invoker委托对象, 该Invoker对象会和提供端的Invoker进行联系调用
- if (invoker != null)
- newUrlInvokerMap.put(url, invoker). 将创建的委托对象写入到新map
- destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); 删除老map中剩余的invoker(不可用的Invoker)
- if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) 若最新的invoker列表为空,则说明当前已经没有任何可用的invoker了,此时将缓存map中的所有invoker全部删除
- destroyAllInvokers()
- for (Map.Entry<URL, Invoker> entry : oldUrlInvokerMap.entrySet()) 将原来缓存map中的invoker全部删除,因为它们中的这些已经没用了,最新可用的都在新map中
- invoker.destroy();
- invoker.destroy();
服务订阅-Protocol.refer 创建invoker委托对象
- invoker = protocol.refer(serviceType, url)
- Protocol$Adaptive # extension.refer.==> extName = dubbo
- DubboProtocol # refer. ==>. protocolBindingRefer
- DubboInvoker invoker = new DubboInvoker(serviceType, url, getClients(url), invokers);
- 创建一个与Dubbo协议相绑定的委托对象invoker,这个invoker可以与provider间建立很多的连接。
- 而第一个连接就会对应一个Netty Client,通过getClients(url)可以获取到其绑定的这些连接
- 一个消费端Invoker可以与多个provider建立连接,因为可以并行消费
- getClients(url). 获取到绑定的这些连接
- boolean useShareConnect = false. 标识连接是否中可共享的(即一个连接是否可以并行提交多个请求)
- int connections = url.getParameter(CONNECTIONS_KEY, 0) 获取connections属性,该属性指定了当前consumer与provider间创建的连接数量(非共享的),默认值为0,表示只有一个连接dubbo:reference 标签中配置的connections 属性,表示的可以有多少个物理连接
- List shareClients = null 表示可共享连接client
- ReferenceCountExchangeClient. # 构造方法
- this.client = client; 物理连接client
- referenceCount.incrementAndGet(); referenceCount 表示当前共享连接被共享的次数,只要创建一个共享连接client,该count就会增一,该count默认值为0,即共享连接的count值最小为1
- if (connections == 0) {. useShareConnect = true 当前连接只有一个,所以该连接是可共享的
- String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null) 从xml的配置文件的dubbo:consumer/中获取shareconnections属性,该属性表示共享连接最多可共享的次数
- connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) 若xml中的该属性配置不空,则直接取该值,否则从属性中获取。此时的connections 表示一个物理连接上可以共享多少个连接
- shareClients = getSharedClient(url, connections). 创建相应的共享连接
- ExchangeClient[] clients = new ExchangeClient[connections]. connections的值大于0的,只不过,其可能代表的是共享连接数量或非共享连接数量
- if (useShareConnect) { clients[i] = shareClients.get(i); 直接获取共享连接
- else clients[i] = initClient(url). 创建非共享连接
- getSharedClient(url, connections). 创建相应的共享连接
- String key = url.getAddress(). key为consumer的ip:服务协议port
- Object clients = referenceClientMap.get(key). 从缓存中获取clients
- 检测clients可用性
- if (clients instanceof List) {. 遍历所有的client
- if (checkClientCanUse(typedClients)) {
- 判断clients的可用性:只有当clients中的所有client均可用时,该clients才称为可用的,checkClientCanUse()返回true。只要有一个client不可用,就返回false
- referenceCountExchangeClient == null || referenceCountExchangeClient.getCount() <= 0 || referenceCountExchangeClient.isClosed() ==>. return false 只要有一个client不可用,就直接结束,返回false
- batchClientRefIncr(typedClients). ==> referenceCountExchangeClient.incrementAndGetCount(). 为每个client的count增一
- 代码走到这里,说明clients中一定存在不可用client
- referenceClientMap.put(key, PENDING_OBJECT). 若当前key对应的clients不可用,则将一个Object对象写入该value
- else if (clients == PENDING_OBJECT). ==>. referenceClientMap.wait() 若当前clients为Object,则阻塞当前线程
- else. referenceClientMap.put(key, PENDING_OBJECT). 若当前clients为其它情况,则将Object写入到当前key对应的value
- connectNum = Math.max(connectNum, 1). 共享连接数
- CollectionUtils.isEmpty(typedClients). 若clients为空,则创建clients
- buildReferenceCountExchangeClientList(url, connectNum); 创建client
- for (int i = 0; i < connectNum; i++) { 遍历共享连接数
- clients.add(buildReferenceCountExchangeClient(url)). 创建共享client
- 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 连接 - new ReferenceCountExchangeClient(exchangeClient). 将该client封装为一个共享连接client
-
- } else {. 走到这里,说明当前clients一定是包含不可用client的列表
- for (int i = 0; i < typedClients.size(); i++) {. 遍历client
- if (referenceCountExchangeClient == null || referenceCountExchangeClient.isClosed()) 若当前遍历的client不可用,则为其创建一个client
- typedClients.set(i, buildReferenceCountExchangeClient(url)). 若当前遍历的client不可用,则为其创建一个client
- referenceCountExchangeClient.incrementAndGetCount(). 若当前遍历的client就是可用的,则为其count增一
- 放入缓存map
- synchronized (referenceClientMap) {
- if (typedClients == null) {. ==> referenceClientMap.remove(key);
- referenceClientMap.put(key, typedClients). 将clients写入缓存map
- referenceClientMap.notifyAll(). 唤醒所有阻塞的线程
- initClient(url). 创建非共享连接,创建共享连接时也会创建非共享连接,逻辑用上边蓝色标记
- client = Exchangers.connect(url, requestHandler). 正常连接
- 1个Consumer会创建几个NettyClient总结:
- dubbo:reference 标签中的connections属性大于0,创建NettyClient的个数以此为准,并且创建的NettyClient是不可共享的,dubbo:consumer 标签中的shareconnections属性不起作用
- dubbo:reference 标签中的connections属性等于0或者不存在该属性,则表示创建的NettyClient是共享的,创建NettyClient的个数由dubbo:consumer 标签中的shareconnections属性决定,如果shareconnections属性不存在,则从配置属性中获取,如果没有配置,则默认值为1
- 例子:
- dubbo:reference 标签中connections 属性为5 ,dubbo:consumer 标签中的 shareconnections 属性为8,会创建5个nettyClient,连接不是共享的
- dubbo:reference 标签中connections 属性为1 ,dubbo:consumer 标签中的 shareconnections 属性为8,会创建1个nettyClient,这1个是不可共享的
- dubbo:reference 标签中connections 属性为0 ,dubbo:consumer 标签中的 shareconnections 属性为8, 会创建8个nettyClient,是共享的
消费端调用服务端
- greetingService.hello(). 消费端调用
- InvokerInvocationHandler # invoke
- if (method.getDeclaringClass() == Object.class) { ==> method.invoke(invoker, args). 若当前调用方法为Object的方法,则直接调用该本地方法
- RpcInvocation rpcInvocation = new RpcInvocation(method, invoker.getInterface().getName(), protocolServiceKey, args). 生成RpcInvocation,调用的信息
- invoker.invoke(rpcInvocation).recreate();
- currentAvailableInvoker.invoke(invocation)
- MockClusterInvoker # invoke 带有降级功能的invoker
- result = this.invoker.invoke(invocation) 远程调用
- AbstractClusterInvoker # invoke
- List<Invoker> invokers = list(invocation). 通过路由策略,将不符合路由规则的invoker过滤掉
- LoadBalance loadbalance = initLoadBalance(invokers, invocation). 获取负载均衡策略,并创建相应的负载均衡实例
- doInvoke(invocation, invokers, loadbalance). 调用具体的集群容错策略中的doInvoke()
- FailoverClusterInvoker # doInvoke
- Invoker invoker = select(loadbalance, invocation, copyInvokers, invoked). 负载均衡
- Result result = invokeWithContext(invoker, invocation). 远程调用
- AbstractInvoker # invoke
- AsyncRpcResult asyncResult = doInvokeAndReturn(invocation). 远程调用
- DubboClient # doInvoke
- ExchangeClient currentClient; 一个client连接着一个Netty Client
- if (clients.length == 1) { ==> currentClient = clients[0]; 若只有一个client,则直接选择
- currentClient = clients[index.getAndIncrement() % clients.length]. 若有多个client,则轮询选择一个
- waitForResultIfSync(asyncResult, invocation). 同步调用,这里将阻塞
- if (InvokeMode.SYNC != invocation.getInvokeMode()) ==> return 如果是异步调用则返回
- asyncResult.get(Integer.MAX_VALUE, TimeUnit.MILLISECONDS). 同步调用,获取结果
- boolean isOneway = RpcUtils.isOneway(getUrl(), invocation). 若只发请求,无需server给出响应,则为oneway,否则为twoway
- currentClient.request(inv, timeout, executor). 通过client提交请求
- HeaderExchangeChannel # request
- channel.send(req)
- channel.send(message, sent). 发送请求
- ChannelFuture future = channel.writeAndFlush(message). netty发送请求
服务端处理消费端请求
- 从DubboExporter 缓存mapper中找到Invoker,然后调用本地的方法
- NettyServerHandler # channelRead
- handler.received(channel, msg)
- MultiMessageHandler # received. 判断请求是否为multipart请求
- HeartbeatHandler # received. 判断是否为心跳请求
- AllChannelHandler # received. 请求分发器Dispatcher
- ExecutorService executor = getPreferredExecutorService(message). 线程池
- executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message)). 将对端请求/响应封装为一个任务,从线程池中拿到一个线程,来处理这个任务。
- ChannelEventRunnable # run()
- handler.received(channel, message)
- decode(message) 解码message
- handler.received(channel, message). 处理解码后的message
- if (message instanceof Request) { 处理client请求的情况
- if (request.isTwoWay()) {. ==> handleRequest(exchangeChannel, request). 处理双向请求
- CompletionStage future = handler.reply(channel, msg). 处理调用
- future.whenComplete((appResult, t) -> { 添加监听,一旦异步操作完成,就会触发该回调
- Invoker<?> invoker = getInvoker(channel, inv); 获取invoker
- DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey); 从缓存map中获取exporter
- exporter.getInvoker(). 获取exporter中封装的invoker
- Result result = invoker.invoke(inv). 完成invoker的本地调用计算
- result.thenApply(Function.identity()); 将result构建为一个异步结果
消费端处理服务端响应
- NettyClientHandler # channelRead
- handleResponse(channel, (Response) message) 处理server响应的情况
- future.doReceived(response)
- this.complete(res.getResult())
- CompletableFuture # complete
服务路由
服务路由的使用
- 服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter(基于脚本) 和标签路由(给provider打标) TagRouter。其中条件路由是我们最常使用的
- 路由规则的设置: 我们通过 Dubbo 管控平台为某服务/应用设置的路由规则,其不仅会设置到注册中心该服务下,同时也会设置到配置中心该服务下(如果搭建了配置中心的话)当然,我们也可以直接通过配置中心GUI 管理端来设置和修改路由规则。例如,通过Apollo 可视化平台来设置 Apollo 中的动态配置,通过ZooInspector 来设置 Zookeeper 中的动态配置等。
- 不过,通过配置中心管理端所进行的修改,是不会修改到注册中心中的路由规则的。所以,在设置了配置中心后,配置中心中的动态配置优先级是高于注册中心中的。
- Dubbo 管控平台设置路由规则(黑白名单和条件路由)
- 规则体: 路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。
- [服务消费者匹配条件] => [服务提供者匹配条件]
- 当消费者的 URL 满足匹配条件时,对该消费者执行后面的过滤规则
- => 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表
- 服务消费者匹配条件为空,表示不对服务消费者进行限制,所有消费者均将被路由到行后面的提供者。
- 服务提供者匹配条件为空,表示对符合消费者条件的消费者将禁止调用任何提供者。
- 参数符号
- method:将调用方法作为路由规则比较的对象
- argument:将调用方法参数作为路由规则比较的对象
- protocol:将调用协议作为路由规则比较的对象
- host:将 IP 作为路由规则比较的对象
- port:将端口号作为路由规则比较的对象
- address:将 IP:端口号作为路由规则比较的对象
- application:将应用名称作为路由规则比较的对象
- zk中dubbo节点下的内容
- meatdata 元数据中心
- GettingService 注册中心
- consumers. 消费者信息
- providers 提供者信息
- configurations 配置信息
- routes 路由信息
- config 配置中心
- dubbo dubbo协议
- 接口路由信息
- dubbo dubbo协议
** 添加激活 RouterFactory 创建的 Router 到 Directory**
- 将dubbo-cluster项目下org.apache.dubbo.rpc.cluster.RouterFactory 和org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory 文件中指定的RouterFactory的实现类进行加载
- FileRouterFactory. ConditionRouterFactory. ServiceRouterFactory. AppRouterFactory. TagRouterFactory. MockRouterFactory. MeshRuleRouterFactory. ==> RouterFactory
- TagDynamicStateRouterFactory. TagStaticStateRouterFactory. ==> StateRouterFactory
- RegistryProtocol # doCreateInvoker
- directory.buildRouterChain(urlToRegistry). 将所有RouterFactory激活扩展类创建的router添加到directory
- DynamicDirectory # buildRouterChain
- this.setRouterChain(RouterChain.buildChain(url)). 创建一个RouterChain,并设置到directory
- new RouterChain<>(url); 创建RouterChain
- RouterChain # 构造方法
- 对普通路由的处理
- List extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class).getActivateExtension. 获取激活RouterFactory实例
- initWithRouters(routers). 将routers写入到routerChain
- 对状态路由的处理
- ExtensionLoader.getExtensionLoader(StateRouterFactory.class)
读取配置中心的路由配置
- actory.getRouter(url). 由factory映射为了该factory创建出的router
- CacheableRouterFactory # getRouter
- routerMap.computeIfAbsent(url.getServiceKey(), k -> createRouter(url)); 从缓存map中获取当前服务标识key对应的router,若没有,则创建一个router,放入到缓存map后返回新创建的router
- ServiceRouterFactory # createRouter
- new ServiceRouter(url)
- ListenableRouter # 构造方法
- this.init(ruleKey). 从配置中心读取路由规则,初始化到这里
- String routerKey = ruleKey + RULE_SUFFIX; 由服务标识与Router后辍构成router key 例: org.apache.dubbo.demo.GreetingService:1.0.0:greeting.condition-router
- String rule = this.getRuleRepository().getRule(routerKey, DynamicConfiguration.DEFAULT_GROUP); 从配置中心读取动态路由规则
- DynamicConfiguration dynamicConfiguration = getDynamicConfiguration() 获取动态配置
- dynamicConfiguration.getConfig(key, group, timeout). 读取动态配置数据
- AbstractDynamicConfiguration. ==>. getConfig. ==> execute(() -> doGetConfig(key, group), timeout)
- TreePathDynamicConfiguration. ==> doGetConfig
- String pathKey = buildPathKey(group, key). 构建出要读取配置节点名称
- zkClient.getContent(pathKey) 读取zk节点中指定path的节点内容
- pathKey =/dubbo/config/dubbo/org.apache.dubbo.demo.GreetingService:1.0.0:greeting.configurators
读取注册中心的路由配置
- RegistryDirectory # notify
- toRouters(routerURLs).ifPresent(this::addRouters). 读取routers子节点下的路由规则,并添加到directory中
- RegistryDirectory # toRouters
- for (URL url : urls) {. 遍历routers子节点下的所有Url
- String routerType = url.getParameter(ROUTER_KEY); 获取router属性值,这里就是condition
- url = url.setProtocol(routerType). 将url的protocol由router://…变为condition://…
- Router router = ROUTER_FACTORY.getRouter(url). 创建相应的router
- RouterFactory$Adaptive # extension.getRouter(arg0). extName == condition
- ConditionRouterFactory # getRouter. ==>new ConditionRouter
- routers.add(router). 添加到routers中
- this::addRouters. ==> routerChain.addRouters(routers) 添加到路由过滤器链中
路由过滤使用
- AbstractClusterInvoker # invoke()
- List<Invoker> invokers = list(invocation). 通过路由策略,将不符合路由规则的invoker过滤掉
- DynamicDirectory # doList
- invokers = routerChain.route(getConsumerUrl(), invocation). 进行路由
- RouterChain # route
- for (StateRouter stateRouter : stateRouters) {. 处理状态路由
- for (Router router : routers) {. 处理普通路由
- ListenableRouter(ServiceRoute) # route
- for (Router router : conditionRouters) { 遍历所有条件路由
- ConditionRouter # route
- if (!enabled) { ==> return invokers; 若router不可用,则直接返回所有invoker
- if (!matchWhen(url, invocation)) {. ==> return invokers;
- matchWhen() 判断当前消费者与条件规则中前半部分(规则体中=>之前的内容)是否匹配,若没有匹配上,则返回所有invoker
- 没有匹配上说明当前路由规则不起作用,返回所有invokers
- List<Invoker> result = new ArrayList<Invoker>(). 用于存放过滤出的invoker
- if (thenCondition == null) {. ==> return result;
- thenCondition 代表规则体=>之后的表达式,若该表达式为空,说明当前路由规则为黑白名单
- 返回result,空的Invoker集合
- for (Invoker invoker : invokers) {. 代码走到这里,说明thenCondition不为null,matchThen() 用于判断invoker与thenCondition是否匹配, 遍历所有invoker,查找到所有可用的invoker
- matchThen(invoker.getUrl(), url). ==> result.add(invoker). 添加匹配到invoker
服务降级
- 服务降级常见设置:
- mock=”force:return null” 表示消费方对该服务的方法调用都直接强制性返回 null 值,不 发起远程调用,即使远程的提供者没有出现问题。不过需要注意,这里的 return null, 并不一定必须是 null,可以是任意想看到的内容。用来屏蔽不重要服务。
- mock=”fail:return null” 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。不过需要注意,这里的 return null,并不一定必须是 null,可以是任意想看到的 内容。例如,123456,例如某个字符串等。用来容忍不重要服务不稳定时的影响
- mock=”true”表示消费方对该服务的方法调用在失败后,会调用消费方定义的服务降级Mock 类实例的相应方法。而该 Mock 类的类名为“业务接口名+Mock”,且放在与接口相同的包中。
- mock=降级类的全限定性类名 与 mock=”true”功能类似,不同的是,该方式中的降级类 名可以是任意名称,在任何包中
- InvokerInvocationHandler # invoker
- MockClusterInvoker # invoke
- String value = getUrl().getMethodParameter(invocation.getMethodName()…) 获取mock属性值
- if (value.length() == 0 || “false”.equalsIgnoreCase(value)) {. 若没有设置mock属性,或mock属性设置为false,则不进行服务降级
- else if (value.startsWith(“force”)) 若mock属性值以force开头,则进行强制降级(无论是否有可用的invoker,都不管,直接降级)
- result = doMockInvoke(invocation, null); 直接服务降级
- 其它情况:当远程调用过程发生异常时,进行降级
- catch (RpcException e) {. 捕获rpcException, 如果是业务异常直接抛出
- result = doMockInvoke(invocation, e) 降级
- List<Invoker> mockInvokers = selectMockInvoker(invocation); 获取最新的invoker列表
- minvoker = (Invoker) new MockInvoker(getUrl(), directory.getInterface()). 没有可用的invoker,则创建一个降级invoker
- result = minvoker.invoke(invocation) 若minvoker为可用invoker,那么这里的调用就是正常的远程调用,否则这里走的是降级处理
- if (getUrl().hasMethodParameter(invocation.getMethodName())) {. 获取dubbo:method/中的mock
- if (StringUtils.isBlank(mock)) {. 若没有,则获取dubbo:reference/中的mock
- mock = normalizeMock(URL.decode(mock)). 规范化mock取值
- 若mock为return,则返回returnnull
- 若mock以fail:开头,则mock取其子串:将前面的fail:去掉
- if (mock.startsWith(RETURN_PREFIX)) {
- Object value = parseMockValue(mock, returnTypes). 获取return的结果
- return AsyncRpcResult.newDefaultAsyncResult(value, invocation). 将结果封装为异步结果
- Invoker invoker = getInvoker(mock); 调用本地降级类
- Invoker invoker = (Invoker) MOCK_MAP.get(mockService); 从缓存maInvoker
- Class serviceType. 获取业务接口class
- T mockObject = (T) getMockObject(mockService, serviceType); 创建本地降级类实例
- mockService = serviceType.getName() + “Mock”; 拼接本地降级类名
- mockClass = ReflectUtils.forName(mockService). 将降级类加载到内存
- return mockClass.newInstance(); 创建降级类实例
- invoker = PROXY_FACTORY.getInvoker(mockObject, serviceType, url). 将实例封装为invoker
- invoker.invoke(invocation). 执行降级类的方法
集群容错
- 消费者调用提供者服务异常,先按照容错处理,最终抛出RpcException异常,并且配置了降级,再进行降级.先进行容错,容错失败,再进行降级
- 容错配置 dubbo:reference 标签中的 cluster属性,默认为failover, 下边实例属性为failback
- dubbo-cluster 项目下的org.apache.dubbo.rpc.cluster.Cluster 文件
- FailoverCluster. FailfastCluster. FailsafeCluster. FailbackCluster. ForkingCluster. AvailableCluster. MergeableCluster. BroadcastCluster. ZoneAwareCluster. MockClusterWrapper
- ReferenceConfig # createProxy
- invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)). 获取provider的委托对象
- Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY)). 获取cluster属性,即集群容错策略,默认failover策略
- getCluster(name, true)
- (ClusterInvoker) cluster.join(directory); 将多个invoker伪装为一个具有复合功能的invoker
- new MockClusterInvoker(directory, this.cluster.join(directory)). cluster ==> FailbackCluster. 具有服务容错的cluster
- doJoin ==> new FailbackClusterInvoker<>(directory)
- result = this.invoker.invoke(invocation). 远程调用
- return doInvoke(invocation, invokers, loadbalance). 调用具体的集群容错策略中的doInvoke
- FailbackClusterInvoker # doInvoke
- 集群容错策略
- failover: 故障转移策略。当消费者调用提供者集群中的某个服务器失败时,其会自动尝试着调用 其它服务器。而重试的次数是通过 retries 属性指定的。
- FailoverClusterInvoker # doInvoke
- checkInvokers(copyInvokers, invocation). 检测invokers列表是否为空
- String methodName = RpcUtils.getMethodName(invocation) 获取RPC调用的方法名
- int len = calculateInvokeTimes(methodName). 获取retries属性值
- List<Invoker> invoked = new ArrayList<Invoker>(copyInvokers.size()). 存放所有已经尝试调用过的invoker,这些invoker中,除了最后一个外,其它的都是不可用的
- for (int i = 0; i < len; i++) {. 遍历尝试的次数次
- Invoker invoker = select(loadbalance, invocation, copyInvokers, invoked); 负载均衡, invoked 为需要排除的机器
- invoked.add(invoker). 将选择出的invoker写入到invoked集合
- failfast 快速失败策略。消费者端只发起一次调用,若失败则立即报错。通常用于非幂等性的写操作,比如新增记录。
- failsafe: 失败安全策略。当消费者调用提供者出现异常时,直接忽略本次消费操作。该策略通常用于执行相对不太重要的服务
- failback: 失败自动恢复策略。消费者调用提供者失败后,Dubbo 会记录下该失败请求,然后会定时发起重试请求,而定时任务执行的次数仍是通过配置文件中的 retries 指定的。该策略通 常用于实时性要求不太高的服务。
- forking: 并行策略。消费者对于同一服务并行调用多个提供者服务器,只要一个成功即调用结束并返回结果。通常用于实时性要求较高的读操作,但其会浪费较多服务器资源。
- ForkingClusterInvoker # invoker
- final List<Invoker> selected; 存放的是挑选出的用于进行并行运行的invoker
- final int forks = getUrl().getParameter(FORKS_KEY, DEFAULT_FORKS). 获取forks属性值
- final int timeout = getUrl().getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT). 获取timeout属性值,远程调用超时时限
- final AtomicInteger count = new AtomicInteger(); 计数器,记录并行运行异常的invoker数量
- final BlockingQueue ref = new LinkedBlockingQueue<>(). 队列:存放并行运行结果
- for (final Invoker invoker : selected) {. 并行运行
- executor.execute(() -> { 使用线程池中的线程执行,
- ref.offer(result); 将当前invoker执行结果写入到队列
- int value = count.incrementAndGet(); 若invoker执行过程中出现异常,则计数器加一
- if (value >= selected.size()) {. ==> ref.offer(e); 代码走到这里说明,没有任何一个并行远程调用是成功的。为了能够唤醒后面的poll(),这里就将异常信息写入到ref队列
- Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
- poll()是一个阻塞方法,等待ref中具有一个元素。
- 只要ref中被写入了一个元素,阻塞马上被唤醒。或一直等待到timeout超时
- 注意,该poll()方法的执行与前面的并行远程调用的执行也是并行的
- broadcast: 广播策略。广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
- available: 首个可用策略。从所有 invoker 中查找,选择第一个可用的 invoker。
- mergeable: 合并策略。将多个 group 的 invoker 的执行结果进行合并。
- zone-aware : 当有多个注册中心可供订阅时,该容错机制提供了一种策略,用于决定如何在它们之间分配流量:
- 标记为“preferred=true”的注册表具有最高优先级。
- 检查当前请求所属的区域,首先选择具有相同区域的注册表。
- 根据每个注册表的权重均衡所有注册表之间的流量。
- 挑选任何有空的人。
负载均衡
- 负载均衡策略 dubbo:reference dubbo:method 标签中都可以设置loadbalance属性
- random: 加权随机算法,是 Dubbo 默认的负载均衡算法。权重越大,获取到负载的机率就越大。
- leastactive: 加权最小活跃度调度算法。活跃度越小,其优选级就越高,被调度到的机率就越高。活跃度相同,则按照加权随机算法进行负载均衡
- shortestresponse: 加权最短响应时间算法。从 Invoker 列表中查找平均响应时间最短的作为要选择的 Invoker。
- roundrobin: 双权重轮询算法,是结合主机权重与轮询权重的、方法级别的轮询算法。
- consistenthash: 一致性 hash 算法。其是一个方法参数级别的负载均衡。对于同一调用方法的、相同实参的、远程调用请求,其会被路由到相同的 invoker。其是以调用方法的指定实参的 hash 值为 key 进行 invoker 选择的。
- InvokerInvocationHandler # invoke
- LoadBalance loadbalance = initLoadBalance(invokers, invocation). 获取负载均衡策略,并创建相应的负载均衡实例
- ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension 获取负载均衡的扩展类
- doInvoke(invocation, invokers, loadbalance). 调用具体的集群容错策略中的doInvoke()
- Invoker invoker = select(loadbalance, invocation, copyInvokers, invoked). 负载均衡
- select(LoadBalance loadbalance, Invocation invocation, List<Invoker> invokers, List<Invoker> selected)
- invoker 这是包含所有invoker的集合,这些invoker可能有问题,也可能是没问题的
- selected. 这里放的invoker是已经被选择过的,但都是有问题的invoker
- boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, CLUSTER_STICKY_KEY, DEFAULT_CLUSTER_STICKY). 获取sticky属性,粘连连接属性,所谓粘连连接是指,让所有对同一服务相同方法的访问请求,尽可能由同一个invoker提供服务,在消费端的dubbo:reference 的sticky属性,默认为false
- if (stickyInvoker != null && !invokers.contains(stickyInvoker)) ==> stickyInvoker = null
- 若当前粘连连接invoker不空,但其没有包含在所有invoker集合中,
- 说明这个粘连连接invoker已经挂了,则将缓存粘连连接清空
- if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
- 代码走到这里,说明粘连连接invoker没有挂掉,然后再进行判断:
- 若当前开启了粘连连接功能,粘连连接invoker也不空,且粘连连接invoker也没有包含在有问题invoker集合,则粘连连接invoker可能是可用的。是否可用,需要再进一步判断
- if (availablecheck && stickyInvoker.isAvailable()) {. ==> return stickyInvoker;
- 若可用性检测功能开启了,且粘连连接invoker是可用的,则直接返回粘连连接invoker,无需再进行负载均衡选择了
- Invoker invoker = doSelect(loadbalance, invocation, invokers, selected). 负载均衡选择
- Invoker invoker = loadbalance.select(invokers, getUrl(), invocation); 负载均衡选择
- if ((selected != null && selected.contains(invoker)). || (!invoker.isAvailable() && getUrl() != null && availablecheck)). 若当前选择出的invoker包含在有问题invoker集合中,或当前选择出的invoker直接就是不可用的,则进行重新负载均衡选择
- Invoker rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck); 重新负载均衡选择
- if (rInvoker != null) {. ==> invoker = rInvoker; 若重新选择的invoker不空,则直接返回,不再判断是否可用了
- else {==> invoker = invokers.get((index + 1) % invokers.size()); 若重新选择的invoker为空,则采用轮询方式再选择一个,轮询选择一个invoker,其是否为空,是否可用,不再进行判断
- if (sticky) {. ==> stickyInvoker = invoker. 将选择出的invoker缓存到粘连连接invoker中
- reselect. 重新负载均衡选择
- 先从不包含selected(之前有问题的Invoker)集合中选择可用的Invoker集合,如果有进行负载均衡
- 如果上边没有可用集合为空,从selected集合中选择可用Invoker,如果有,进行负载均衡
- 也没有,返回null
- List<Invoker> reselectInvokers = new ArrayList<>(). 其将来存放的invoker,就是再进行负载均衡选择的总集合,相当于前面的invokers集合的作用
- for (Invoker invoker : invokers) {. 遍历所有invokers集合,从中挑选出所有可用的invoker
- if (availablecheck && !invoker.isAvailable()) { ==> continue. 若当前遍历invoker不可用,则直接跳过
- if (selected == null || !selected.contains(invoker)) {. ==> reselectInvokers.add(invoker); 能走到这里,说明当前invoker一定是可用的。判断当前遍历invoker是否在曾被选择过集合,即有问题invoker集合,若不在,则将其记录到reselectInvokers
- 走到这里,reselectInvokers中的invoker一定是可用的,且还未被选择过的
- if (!reselectInvokers.isEmpty()) {. ==> loadbalance.select(reselectInvokers, getUrl(), invocation); 若reselectInvokers不空,则负载均衡从reselectInvokers中选择一个invoker
- 走到这里,说明reselectInvokers为空
- if (selected != null) {. for (Invoker invoker : selected) {. 遍历有问题invoker集合,从中查找出可用的invoker,以前有问题,可能现在没有问题了. ==> if ((invoker.isAvailable()) ==> reselectInvokers.add(invoker)
- if (!reselectInvokers.isEmpty()) { ==> return loadbalance.select(reselectInvokers, getUrl(), invocation)若reselectInvokers不空,则负载均衡从reselectInvokers中选择一个invoker
- return null. 否则返回null
- loadbalance.select
- AbstractLoadBalance # select
- doSelect(invokers, url, invocation) 调用各种不同的负载均衡策略中的doSelect()
一致性hash算法原理
- m1为Invoker,o1为请求,问题,增加一个Invoker只会对最近的Invoker有影响
- 目的,增加1个物理Invoker对所有的Invoker有影响,能够分担请求,方案增加虚拟主机
- 配置方式
- hash.arguments,利用方法参数取hash值, (0,1)代表参数的位置, 逗号分割代表用第一个参数和第二个参数取hash值,可以继续逗号扩展
- hash.nodes 代表有多少个虚拟Invoker
一致性hash算法源码
- ConsistentHashLoadBalance # doSelect
- String methodName = RpcUtils.getMethodName(invocation); 获取RPC调用的全限定性方法名
- String key = invokers.get(0).getUrl().getServiceKey() + “.” + methodName; 计算一致性hash选择器的key.
- ConsistentHashSelector selector = (ConsistentHashSelector) selectors.get(key).
- ConcurrentMap<String, ConsistentHashSelector<?>> selectors;
- selectors是一个缓存map,其key为前面的key,value为一致性hash选择器
- 获取当前调用方法对应的选择器
- 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++) {.- 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
双权重轮询算法原理(主机权重 + 轮询权重)
- 刚开始轮询权重都为0
- 计算本次轮询权重 = 上一次的轮询权重 + 主机权重
- 选择轮询权重最大的主机,选择后 该主机的轮询权重 = 该主机的轮询权重 - 轮询权重的和
- 主机权重越大,从最小变为最大越快
双权重轮询算法源码
- 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);
}
- 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));
}
加权随机算法原理
- 5,8,14 为 前边主机权重的和
- 生成0到14的随机数,寻找生成的随机数小于数组中最小的元素
加权随机算法源码
- RandomLoadBalance # doSelect
- int length = invokers.size(); length invoker的数量
- if (!needWeightLoadBalance(invokers,invocation)){
- return invokers.get(ThreadLocalRandom.current().nextInt(length)); 若不需要加权负载均衡(配置xml中没有weight属性),则直接生成一个[0, length)的随机数,选择出invoker
- int[] weights = new int[length]; 记录当前invoker及其前面所有invoker的权重之和
- for (int i = 0; i < length; i++) {.
- int weight = getWeight(invokers.get(i), invocation); 获取当前主机的权重
- totalWeight += weight; 当前的权重和 = 当前主机的权重 + 之前的权重和
- weights[i] = totalWeight; 设置当前 主机下标对应的权重和
- if (sameWeight && totalWeight != weight * (i + 1)) {. ==> sameWeight = false; 判断Invoker的权重不是相同的
- if (totalWeight > 0 && !sameWeight) {. 处理各invoker的权重不同的情况
- int offset = ThreadLocalRandom.current().nextInt(totalWeight); 生成一个随机权重[0, totalWeight)
- for (int i = 0; i < length; i++) {
- if (offset < weights[i]) {. ==> return invokers.get(i); 找到权重和大于随机权重的第一个下标,返回对应的invoker
- return invokers.get(ThreadLocalRandom.current().nextInt(length)); 处理各invoker权重相同的情况(与无权重的处理方式相同)
加权最小活跃度算法
- 先选择最小活跃度的主机,如果最小活跃度的主机有相同的,利用权重随机算法 挑选一个主机
- LeastActiveLoadBalance # doSelect
- int[] leastIndexes = new int[length]; 最小活跃度的主机集合
- int[] weights = new int[length]; 记录每个invoker的权重
- for (int i = 0; i < length; i++) {. 挑选出具有最小活跃度的所有invoker
- int active = RpcStatus.getStatus(invoker.getUrl(), …). 获取当前遍历invoker的最小活跃度,默认为0
- if (leastActive == -1 || active < leastActive) {. 如果之前的leastActive = = -1 表明刚开始,或者当前的活跃度比保存的最小活跃度还小,则更新最小活跃度的信息
- else if (active == leastActive) { 说明最小活跃的主机相等
- leastIndexes[leastCount++] = i; 添加集合
- if (!sameWeight && totalWeight > 0) {. 权重随机算法
- 7,对应的在B的范围内,则选择B主机,weights中存放的是主机对应的权重
- i = 0 随机数 - i对应主机的权重
- 7-5-3 < 0 ,对应的3就是当前7所在的主机
- 判断是否小于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);
}
}
}
最短响应时间算法
- 先寻找最短响应时间的主机,如果找到了就返回,如果有最短响应时间相同的主机,利用加权随机算法选择一个主机返回
- ShortestResponseLoadBalance # doSelect
- long shortestResponse = Long.MAX_VALUE; 记录最短响应时间RT
- int[] shortestIndexes = new int[length]; 最短响应时间的invoker集合
- int[] weights = new int[length]; 所有invoker的权重
- RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl() 获取rpc的状态
- 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();
}
- int active = rpcStatus.getActive(); 当前invoker正在处理的活动连接数量
- long estimateResponse = succeededAverageElapsed * active; 计算出当前invoker总的响应时间
- 总的响应时间 = 平均响应时间 * 正在处理的连接数量
- 寻找最小的响应时间
基础
consumer端
-
xml中的标签
-
. dubbo:reference version=“1.0.0” group=“greeting” id=“greetingService” check=“false”
interface="org.apache.dubbo.demo.GreetingService" />
-
key ==> greeting/org.apache.dubbo.demo.GreetingService:1.0.0
-
注册中心地址==> zookeeper://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&pid=43073×tamp=1715836962379
-
修改后注册中心的地址===> registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&pid=43073®istry=zookeeper×tamp=1715836962379
-
failoverCluster
-
Failovercluster 重试集群
-
MigrationInvoker 迁移执行器 .
-
MigrationRuleListener . 迁移规则监听器
-
MigrationRuleHandler 迁移规则处理器
-
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×tamp=1715836819420&version=1.0.0
-
消费端三个节点 providers ,configurators, routers
-
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×tamp=1715836819420&version=1.0.0
-
/dubbo/org.apache.dubbo.demo.GreetingService/providers
-
/dubbo/org.apache.dubbo.demo.GreetingService/configurators
-
/dubbo/org.apache.dubbo.demo.GreetingService/routers
-
消费端子节点变更通知URL ==> zookeeper://localhost:2181/ConfigCenterConfig?check=true&config-file=dubbo.properties&group=dubbo&highest-priority=false&namespace=dubbo&timeout=3000
-
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
-
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×tamp=1715836819420&version=1.0.0
-
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×tamp=1715836819420&version=1.0.0
-
this.currentAvailableInvoker = serviceDiscoveryInvoker;
-
AbstractClusterInvoker # invoke
provider端
- 提供者标签
- <dubbo:service version=“1.0.0” group=“greeting” interface=“org.apache.dubbo.demo.GreetingService” ref=“greetingService”/>
- 注册中心的两个地址==>
- service-discovery-registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=57588®istry=zookeeper×tamp=1715844424181
- registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=57588®istry=zookeeper×tamp=1715844424181
- 服务暴漏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×tamp=1715844571772&version=1.0.0
- 本地暴露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×tamp=1715844571772&version=1.0.0
- 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×tamp=1715849100690&version=1.0.0
- 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×tamp=1715849100690&version=1.0.0
- 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×tamp=1715849909146&version=1.0.0