Dubbo原理的实现之服务的调用
(一)大概描述一下整个服务调用的过程的准备工作和总体流程,后面一步一步分析
先说一下dubbo调用服务的原理需要的前置步骤,相当于准备工作:
- dubbo在引用服务的时候是分两种方式的,第一种是我们正常使用的懒汉式的加载方式的,其实就是我们用@Reference注解的时候使用的就是懒汉式的加载方式
- 第二种是当我们在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,这种调用方式其实是一种饿汉式的加载方式,然后两者调用的开始其实就是从ReferenceBean 的 getObject 方法开始
现在调用完事之后,下一步就是决定引用那种服务,有三种可供选择的嗲用服务的方式:
- 第一种是引用本地 (JVM) 服务
- 第二是通过直连方式引用远程服务
- 第三是通过注册中心引用远程服务
- 然后最终都会生成一个Invoker对象,此时的Invoker对象已经具备调用本地或远程服务的能力了,但是不能直接暴露给用户,会对一下业务造成侵入,然后需要使用代理工厂类代理对象去调用Invoker的逻辑。
总的概括一下总的调用流程:
首先是ReferenceConfig类先去调用init()方法,然后会在init方法中调用Protocol的refer()方法生成一个Invoker对象实例,这里是消费服务的关键,然后会在init方法中通过createProxy(map)方法生成一个代理对象,然后在CreateProxy方法中进行判断是采用哪种调用方式(就是上面三种方式之一),并去调用refer()方法生成一个Invoker对象,接下来会把Invoker对象转换为客服端需要的对象(UserService),
引用服务的时序图:
整个调用过程的简化图:
(二)源码分析的流程:
1. 处理配置
- 其实就是通过@Reference注解获取到消费者配置对象,然后主要用于检测 ConsumerConfig 实例是否存在
- 获取接口的版本信息
- 将 ApplicationConfig、ConsumerConfig、ReferenceConfig 等对象的字段信息添加到 map 中
- 遍历 MethodConfig 列表 看有没有给每个方法中配置调用方法失败的时候的重试次数,这里其实就是dubbo重试的最小粒度其实是方法,然后放到构建Invoker对象的URL参数中字段的map中去
- 获取服务消费者 ip 地址
2. 引用服务
- 前提是需要+
- 创建Invoker对象实例,在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来
- 是在DubboProtocol 的 refer () 方法中通过
创建的,这里走的是dubbo协议,也可能是走Injvm协议,或者是别的协议DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
- getClients(URL对象)里面主要就是这个方法用于获取客户端实例,实例类型为 ExchangeClient。ExchangeClient 实际上并不具备通信能力,它需要基于更底层的客户端实例进行通信,比如使用Netty的NettyClient通信、或者Mina的MinaClient 来进行通信
- 然后是RegistryProtocol 的 refer() 方法,首先为 url 设置协议头,然后根据 url 参数加载注册中心实例。然后获取 group 配置,根据 group 配置决定 doRefer 第一个参数的类型
- 是在DubboProtocol 的 refer () 方法中通过
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 取 registry 参数值,并将其设置为协议头
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
// 获取注册中心实例
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// 将 url 查询字符串转为 Map
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
// 获取 group 配置
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
// 通过 SPI 加载 MergeableCluster 实例,并调用 doRefer 继续执行服务引用逻辑
return doRefer(getMergeableCluster(), registry, type, url);
}
}
// 调用 doRefer 继续执行服务引用逻辑
return doRefer(cluster, registry, type, url);
}
- 然后是RegistryProtocol 的doRefer(Cluster cluster, Registry registry, Class type, URL url)方法,doRefer 方法创建一个 RegistryDirectory 实例,然后生成服务者消费者链接,并向注册中心进行注册。注册完毕后,紧接着订阅 providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在 providers 产生多个节点,这个时候就需要 Cluster 将多个服务节点合并为一个,并生成一个 Invoker
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 创建 RegistryDirectory 实例
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
// 设置注册中心和协议
directory.setRegistry(registry);
directory.setProtocol(protocol);
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
// 生成服务消费者链接
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
// 注册服务消费者,在 consumers 目录下新节点
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
// 订阅 providers、configurators、routers 等节点数据
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
// 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
- 然后创建Invoker成功。
- 创建代理,代理对象生成的入口方法为 ProxyFactory 的 getProxy,并对服务接口进行生成代理对象, Proxy 的 getProxy 方法获取 Proxy 子类,然后创建 InvokerInvocationHandler 对象,并将该对象传给 newInstance 生成 Proxy 实例
- 这个具体是用JDK的动态代理生成还是使用JavassistProxyFactory生成,下面是具体的实现,这个invoker和interfaces其实是在上一大步创建好的Invoker中,通过下面代码块获取到接口串,然后进行遍历切分
-
Class<?>[] interfaces = null; String config = invoker.getUrl().getParameter("interfaces");
- 然后把获取到的Invoker和Interfaces数组传给具体实现生成代理类的具体实现是JavassistProxyFactory还是JdkProxyFactory,然后都是通过下面的代码进行生成的,
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
- 然后就已经拿到了代理生成远程的接口的实现对象类了,然后就可以直接使用了