Dubbo得以成立的三个组件:
准备
Provider初始化
第一步,启动Netty服务端,包括但不限于实例化各种类,实例化编码和解码器,创建IO线程池等。
第二步,启动Dubbo,实例化过滤器链等Dubbo核心类。
第三步,为将要提供服务的接口创建代理类:
package com.dubbo.test;
public class Hello {
public void sayHello(String name) {
System.out.println("hello " + name);
}
}
通过反射生成的代理类Wrapper:
public class Wrapper {
public Object invokeMethod(Object obj, String methodName, Object[] args) throws java.lang.reflect.InvocationTargetException {
com.dubbo.test.Hello w = (com.dubbo.test.Hello) obj;
try {
if ("sayHello".equals(methodName)) {
w.sayHello((java.lang.String) args[0]);
return null;
}
} catch (Throwable e) {
throw new java.lang.reflect.InvocationTargetException(e);
}
throw new NoSuchMethodException("Not found method \"" + methodName + "\" in class com.test.Hello");
}
}
当provider接收到的consumer的调用请求消息后,会从消息里面解析出类名、方法名、参数,然后找到类对应的Wrapper,调用invokeMethod方法。
第四步,生成包括这一个接口所有信息的URL,并通过URL在注册中心注册这一个provider:
dubbo://127.1.1.1:20880/com.dubbo.test.Hello?anyhost=true&application=sas-provider&dubbo=2.0.2&generic=false&interface=com.dubbo.test.Hello&methods=sayHello
Comsumer初始化
第一步,从注册中心拉取provider的信息,订阅Provider,同样通过URL的方式注册一个consumer。
第二步,启动Netty客户端,包括但不限于实例化各种类,实例化编码和解码器,创建IO线程池等。
第三步,启动Dubbo,包括实例化集群容错、负载均衡、过滤器链等Dubbo核心类。
第四步,为想要调用的接口创建代理类:
package com.dubbo.test;
public interface Hello {
void sayHello(String name);
}
通过反射生成代理类Proxy,在Spring的视角中Proxy就是接口的实现类:
package com.dubbo.test;
public class Proxy implements com.dubbo.test.Hello {
public static java.lang.reflect.Method method;
// Dubbo的核心类,通过invoke进入Dubbo框架
private Invoker invoker;
public void sayHello(String name) {
Object[] args = new Object[1];
args[0] = name;
invoker.invoke(this, method, args);
}
}
请求
第一步,通过生成的代理类,发起对sayHello方法的调用。在使用者视角里,调用别人提供的接口跟调用自己实现的接口一样。
第二步,进入FailOver的控制管理策略中,目的是指定本次调用是对单个provider发起调用还是多个发起调用,以及在本次调用失败的时候,应该怎么做。支持以下策略:
策略 | 说明 |
---|---|
AvailableClusterInvoker | 直接发起调用,不处理结果 |
FailoverClusterInvoker(default) | 失败时重试其他provider,重试n次,默认重试2次。 |
FailbackClusterInvoker | 固定时间间隔后重试 |
FailfastClusterInvoker | 直接抛出异常 |
FailsafeClusterInvoker | 打印日志并忽略异常 |
ForkingClusterInvoker | 同时对多个provider发起调用 |
BroadcastClusterInvoker | 同时对所有provider发起调用 |
第三步,负载均衡策略,从所有的provider中选出一个。支持五种策略:
策略 | 说明 |
---|---|
RandomLoadBalance(default) | 加权随机,按照设置的权重随机选一个,权重越大选中的概率越大。默认所有provider权重相同。 |
LeastActiveLoadBalance | 选择当前活跃连接数最小的provider,依赖过滤器链。 |
ConsistentHashLoadBalance | 一致性哈希。相同参数的请求总是发到同一个provider。 |
RoundRobinLoadBalance | 加权轮询。在轮流分配的基础上加上权重,最终每个provider的请求数比值接近权重比。权重相等时,等于轮流分配。 |
ShortestResponseLoadBalance | 选择预计等待时间最短的provider,依赖过滤器链。 |
第四步,执行过滤器链,按顺序依次执行每一个过滤器,过滤器会传入这一次调用的信息,包括接口、方法、参数等等。过滤器支持用户自定义,框架内置过滤器包括不限于:
类名 | 作用 | 类别 |
---|---|---|
ActiveLimitFilter | 1.限制consumer的并发调用数,可以设置并发数、被限制后的超时时间。 | |
2.记录调用数据:并发数,调用总数,成功次数、失败次数、调用耗时等。 | consumer | |
ExecuteLimitFilter | 限制provider的并发调用数,达到限制直接抛出异常。 | provider |
… | … | … |
第四步,对参数编码和发送Request。
通信
应用层默认使用Dubbo协议通信,框架内置支持http,redis,grpc等。
如果是Dubbo协议,对Request/Response组装一个Dubbo协议包发出去:
重要字段:
MagicHigh+MagicLow表示每一条消息的起始位置,通过Magic分割解决粘包拆包的问题。
Req/Res标识是请求还是响应。
SerializationID标识序列化方式,例如fastjson。
Status表示状态,包括OK、TimeOut、Error等。
RequestId唯一标识。
执行
第一步,把收到的Request消息从Netty的IO线程转移到执行任务的外部线程池中。
第二步,对收到的消息解码,提取请求的接口、调用的方法名、传入的参数值等,找到准备阶段已经生成好的Wrapper(里面保存了具体的实现类)。
第三步,执行包装在Wrapper外层的过滤器链。
第四步,执行provider的sayHello方法。
第五步,把方法的调用结果组装成Response消息,发送给consumer。
响应
同步调用:发送Request消息后,Dubbo内部线程马上执行Future.get()阻塞住,直到有结果后返回给用户。
异步调用:方法的返回值必须是CompletableFuture,发送完Request后直接把Future返回给用户。