Dubbo流程及源码分析(三)

        扑街前言:之前忘记说了,这个dubbo分析要结合我之前的文章,比如netty、zookeeper、rpc等文章一起看,dubbo太复杂了所以有些地方描述得也不是很详细,这边也是提供一个代码分析的思路,真正要读懂这个还需要自己去翻一下源码,注释版的源码可以在我的资源中下载。本篇文章主要说客户端的启动流程。


        还是要拿一下上面文章中的架构图,这个图是绝对的重点,先上图,我们再往下面讲。

consumer启动流程

        从上面的架构图结合之前分析服务端启动流程的代码逻辑,我们可以看出第一个找的就是config层,之前provider 找的是serviceConfig,那么consumer 的应该就是referenceConfig,至于是怎么分析的,我可以看到解析xml文件对象的ReferenceBean 对象,第一眼看到的就是这个对象实现了FactoryBean,那么以为这我们只需要看getObject() 方法就行了,然后一步一步就可以跟到ReferenceConfig 中的init() 方法。

ReferenceBean和ReferenceConfig

        接着分析上面说的ReferenceConfig 中的init() 方法,这里我们先可以结合之前的rpc文章知道consumer 启动就是为了给暴露的接口生成代理,那么我们就可以直接找createProxy(map)方法,跟进来其余的代码可以不看,大体意思就是组装URL对象,可以直接找到REF_PROTOCOL.refer 方法,REF_PROTOCOL 是一个扩展点,跟上篇文章说的Protocol 层的扩展点是同一个,有一点需要注意dubbo是支持多注册中心注册的,也就说提供者可以是多个,但是这些提供者最后都会被封装为一个invoker,在后面PROXY_FACTORY.getProxy 方法调用的时候使用,这个方法是为了真正的生成代理,PROXY_FACTORY 同样是扩展点,在Proxy 层。

// 单个注册中心或服务提供者(服务直连,下同)
if (urls.size() == 1) {
	// 调用 RegistryProtocol 的 refer 构建 Invoker 实例
	invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); // registry://192.168.200.129:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&pid=14276&qos.port=33333&refer=application%3Ddemo-consumer%26check%3Dfalse%26dubbo%3D2.0.2%26interface%3Dorg.apache.dubbo.demo.DemoService%26lazy%3Dfalse%26methods%3DsayHello%26pid%3D14276%26qos.port%3D33333%26register.ip%3D192.168.200.10%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1622603489641&registry=zookeeper&timestamp=1622603494381
} else {
	// 多个注册中心或多个服务提供者,或者两者混合
	List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
	URL registryURL = null;
	// 获取所有的 Invoker 到 invokers中
	for (URL url : urls) {
		// 通过 ref_protocol 调用 refer 构建 Invoker,refprotocol 会在运行时
		// 根据 url 协议头加载指定的 Protocol 实例,并调用实例的 refer 方法
		invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
		if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
			registryURL = url; // use last registry url
		}
	}
	if (registryURL != null) { // registry url is available
		// use RegistryAwareCluster only when register's CLUSTER is available
		// 如果注册中心链接不为空,则将使用 AvailableCluster
		URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
		// The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
		// 创建 StaticDirectory 实例,并由 Cluster 对多个 Invoker 进行合并  Cluster扩展点默认找的是FailoverCluster
		invoker = CLUSTER.join(new StaticDirectory(u, invokers));
	} else { // not a registry url, must be direct invoke.
		invoker = CLUSTER.join(new StaticDirectory(invokers));
	}
}

RegistryProtocol

        我们先看REF_PROTOCOL.refer 的调用,根据URL中的参数,可以找到RegistryProtocol 实现类,区别之前provider 端的逻辑,provider 端是先建立连接后注册服务信息,但是consumer 端是先拉去服务信息到缓存,然后根据服务信息建立连接,刚刚好是相反的。

        然后我们看到代码,首先获取的是registry 注册中心实例,这个逻辑跟之前的provider 端的代码逻辑是一样的,最后会获得一个你配置的注册中心实例,我这里就是zookeep的实例,然后接下来就是组装一些参数,最后doRefer 方法调用。

@Override
@SuppressWarnings("unchecked")
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
	url = URLBuilder.from(url)
			.setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
			.removeParameter(REGISTRY_KEY)
			.build();
	//获取注册中心实例 ZookeeperRegistry  url=zookeeper://192.168.200.129:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=14276&qos.port=33333&timestamp=1622603494381
	Registry registry = registryFactory.getRegistry(url);// 获取实例并使用curator连接上了zk
	if (RegistryService.class.equals(type)) {
		return proxyFactory.getInvoker((T) registry, type, url);
	}

	// group="a,b" or group="*"  将 url 查询字符串转为 Map
	Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
	String group = qs.get(GROUP_KEY); // // 获取 group 配置
	if (group != null && group.length() > 0) {
		if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
			// 通过 SPI 加载 MergeableCluster 实例,并调用 doRefer 继续执行服务引用逻辑
			return doRefer(getMergeableCluster(), registry, type, url);
		}
	}
	return doRefer(cluster, registry, type, url);
}

        接着来就是拉去服务放入缓存,下面代码结合架构图其实我们就可以发现目前代码是在registry 注册中心层,也可以看到RegistryDirectory 对象的封装,代码中还可以看到consumer 端也是会将接口信息注册到注册中心上的,当完成注册之后,然后才是拉去服务放入缓存,也就是subscribe 方法的调用,跟进去之后重要的地方还是继续在调用父类的subscribe 方法,接着就是父类的subscribe 方法调用doSubscribe 方法,然后就是ZookeeperRegistry 的doSubscribe 方法了。

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);
	// all attributes of REFER_KEY
	Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
	// 生成服务消费者链接 consumer://192.168.200.10/org.apache.dubbo.demo.DemoService?application=demo-consumer&check=false&dubbo=2.0.2&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=12468&qos.port=33333&side=consumer&sticky=false&timestamp=1622604057385
	URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
	// 注册服务消费者,在 consumers 目录下创建新节点
	if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
		directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
		registry.register(directory.getRegisteredConsumerUrl());
	}
	directory.buildRouterChain(subscribeUrl);
	/**
	 * 订阅 providers、configurators、routers 等节点数据
	 * 拿到 providers 下的提供者信息后,会创建客户端(nettyclient),并连接服务端,重点 DubboProtocol 中的 protocolBindingRefer
	 * ********所以重点跟这个方法的调用****
	 */
	directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
			PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));

	Invoker invoker = cluster.join(directory); // 一个注册中心下可能有多个服务提供者,每个提供者都对应一个invoker,这里将多个invoker合并为一个
	ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
	return invoker;
}

        到了ZookeeperRegistry 的doSubscribe 方法之后,会先经过zk客户端的订阅,然后就是notify 方法,然后一步一步跟最后到了父类AbstractRegistry 对象的notify 方法,下面图片中代码第一行会将URL解析出来,然后给到对应的listener.notify(categoryList),继续跟进可以看到首先是获取URL对象,然后依然继续跟进重点是refreshInvoker 方法,然后找到对应的toInvokers 方法的调用,然后就是new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl),到这一步我们就需要结合架构图来看了。

 

        我们需要看到的是protocol 层和exchange 层,这里的最终目的就是为了生成一个invoker 对象,invoker 对象中封装的就是DubboInvoker 对象,那么结合代码可以知道上面说protocol 扩展点的refer 方法,应该调用的就是dubbo的实现,一步一步跟进最后我们到了DubboProtocol 的protocolBindingRefer 方法中,在这里我们就可以看到dubboInvoker 的生成,再次结合架构图,发现dubboInvoker 封装的是exchangeClient,代码中也有这一句getClients(url) 方法调用,跟进。

         然后我们就找到了netty连接,注意这里有一个共享客户端,这个跟我们之前rpc编写时说的长连接的建立是一个意思。我们要看是initClient 方法,其实这里我们已经了解过很多变了创建Handel,创建连接netty,绑定等等一个套路,这里就不重复了。

         当netty 连接创建成功并封装到了dubboInvoker之后,我们再回头看到RegistryProtocol 对象的doRefer 方法,结合架构图我们可以看到,下层invoker 封装到了rpc层的时候,还会将其再次封装,这次还会将路由、负载均衡等对象封装到一个新的invoker 中,也就是上面代码中的cluster.join 方法的调用。到了这一个invoker 对象算是完全封装好了。

         当invoker 对象封装完成之后,我们再再回头看到ReferenceConfig 类的createProxy 方法,之前我们就说了生成的invoker 对象是为了创建代理对象用的,我们再次找到对应的getProxy 方法,这里一步步跟进到最后可以看到,其实生成代理就只有两个具体实现,一个是jdk的,一个是javassist的,默认是javassist,但是不管是javassist还是jdk,最后封装回去的代理对象都是InvokerInvocationHandler,结合之前我们编写rpc 时的经验,客户端调用服务端的实现类的时候,首先进入的是代理拦截,那么可以退出InvokerInvocationHandler 对象就是拦截对象。


         到目前为止,consumer 端的启动逻辑就已经结束了,后续我们再分析dubbo 的数据流转过程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值