dubbo源码-从客户端看Registry和Directory
Posted 小鱼堂
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dubbo源码-从客户端看Registry和Directory相关的知识,希望对你有一定的参考价值。
简介
站在dubbo客户端的角度,核心目的是服务调用。在调用服务前,需要做2个重要的事情。
这2件事情涉及到重要的2个类Registry和Directory,现分析他们的作用和原理。
Registry是和注册中心打交道的类,有多种实现,以zookeeper为例子进行分析。
结构
对应客户端的应用来说,一个注册中心对应一个ZookeeperRegistry对象。ZookeeperRegistry中封装了zkClient和zk交互,用来创建节点和监听节点变更。也会缓存一份客户端需要的服务端信息到本地文件。
一个要消费的服务(比如要调用远程服务UserService)对应一个RegistryDirectory对象,泛型T及是对应的服务接口。
RegistryDirectory中存放着所有可以调用的远程服务提供者机器列表invokers,会通过list方法按一定规则选择一起发起调用。
Registry的作用
注册中心为Zookeeper,Registry的实例就为ZookeeperRegistry
ZookeeperRegistry的核心作用是和注册中心(zk)交互。包括创建节点,监听节点变更。
ZookeeperRegistry中有和zkServer交互的zkClient,zkClient用的是Curator。用来实现对zk创建节点和添加监听
因为ZookeeperRegistry是共用的一个对象,所以zklisteners的key是各个需要消费的服务,不同的服务由不同的监听来处理。value就是要消费的服务对应的RegistryDirectory对象,RegistryDirectory正好实现了listener方法。
Directory的作用
他的registry属性就是ZookeeperRegistry的实例。
一个消费者就有一个RegistryDirectory对象,但是他们的ZookeeperRegistry都是同一个对象。
维护一个invokers列表,存放所有注册在zk上的服务提供者对应的invoker对象
2个核心方法
list用来获取可用调用服务的invoker。因为不是全部注册的服务机器都可以用来调用。要受到路由规则和负载均衡的过滤
代码实现
入口
RegistryProtocol.refer
这个是实例化消费者bean,创建消费者bean对应的invoker需要调用的方法,包含了ZookeeperRegistry和RegistryDirectory的核心过程。
ZookeeperRegistry
创建
通过registryFactory创建Registry。
因为用zookeeper做配置中心举例,所以这里的registryFactory最终会调用ZookeeperRegistryFactory的createRegistry方法,创建一个ZookeeperRegistry。
注:registryFactory具体是哪个对象,按SPI的规则来判断
无论哪个服务的URL,在创建Registry的时候,URL的path都会替换成RegistryService.class.getName(),所以同一个注册中心,无论客户端调用哪个服务,这里的Key都是相同的。
创建对象会先从缓存中按key获取(REGISTRIES是静态的map),没有才创建。由于同一个注册中心key是相同的,所以每次拿到的都是同一个ZookeeperRegistry对象。
因为每一个注册的消费者bean都要走到这里和创建ZookeeperRegistry以便和zk做交互(注册和监听),所以要保证只创建一次,大家都用同一个ZookeeperRegistry,而加了锁。
创建zkClient
dubbo对原生的Curator又做了一层包装,为ZookeeperClient。在ZookeeperClient中才找到原生的Curator对zk的client连接
上面的builder创建client就是最底层通过对Curator对zk的操作。创建连接,添加监听并保证连接成功。
注意用法:blockUntilConnected。因为start是个异步操作,然后再用blockUntilConnected阻塞到连接成功或者超时,来获取连接的结果。
RegistryDirectory
活动最开始的refer方法,创建完ZookeeperRegistry后,就要开始创建RegistryDirectory了
创建好的ZookeeperRegistry会继续传到doRefer中来和new出来的RegistryDirectory交互。这里调用new方法,可以知道每个消费者bean都会对应一个自己的RegistryDirectory。directory.setRegistry(registry)则是所有的RegistryDirectory都会共用同一个ZookeeperRegistry。
完成创建后,看2个重要的方法register和subscribe
register
register会先调用FailbackRegistry的register,再调用子类ZookeeperRegistry的doRegister。
核心作用是调用zkClient在注册中心创建一个consumer消费者的临时节点。
最底层调用的就是Curator的create().forPath()。
subscribe
subscribe其实调用的也是ZookeeperRegistry的subscribe。还是通过ZookeeperRegistry父类,最终调用到ZookeeperRegistry的doSubscribe
一般接口名不会为*,走else逻辑。
注册监听
toCategoriesPath会按类型将URL分解成3种url,分别是provider, configration, router
然后for循环给他们子节点变更添加监听ChildListener。ChildListener的逻辑是调用ZookeeperRegistry的notify方法
对他们都添加监听是在服务端ip发生变化(服务端机器上线或者下线),或者负载均衡和路由规则发生变化(通过管理控制台配置)的时候,都能及时触发ZookeeperRegistry.nofity的调用来更新RegistryDirectory的URL数据
通知
notify(url, listener, urls);
notify最终会调用到AbstractRegistry的notify方法
在循环中核心做2件事
调用RegistryDirectory的notify方法
saveProperties将服务,路由,负载均衡配置保存到本地文件
RegistryDirectory的notify方法
RegistryDirectory也是listener, 他实现的监听就在这里被ZookeeperRegistry调用。
同时以后每次zk节点发生变动的时候,都会触发ZookeeperRegistry调用到RegistryDirectory的nofity方法。
可以看到,他对3种类型的URL都做了对应数据的处理。包括负载均衡,路由和服务提供者。
最后刷新了invoker列表
private void refreshInvoker(List<URL> invokerUrls)
通过URL,创建出具体的invokers, 并赋值缓存在RegistryDirectory的urlInvokerMap中。
private Map<String, Invoker<T>> toInvokers(List<URL> urls)
具体的invoker对象有会缓存来减少性能开销,
创建的时候,使用protocol.refer(serviceType, url),创建出底层的invoker对象,为以后发起远程调用做好对象的准备。
这个protocol.refer,最终是由dubboProtocol(通过SPI实现)的protocolBindingRefer创建出来的DubboInvoker实例(其实DubboInvoker外面也被包了好几层)
完成ZookeeperRegistry和RegistryDirectory的创建和赋值,并监听注册中心变更事件来更新自身的数据,就为以后发起RPC调用做好准备了。
流程
收到变更后会同步通知变更URL对应的RegistryDirectory,并更新本地缓存。
总结
ZookeeperRegistry用来管理zk的注册和监听。一个注册中心全局只有一个ZookeeperRegistry对象。
RegistryDirectory同时也负责处理zk节点发生变化的事件。更新自己维护的url列表和invoker集合。
补充
本文只讲了ZookeeperRegistry和RegistryDirectory中的主线逻辑。包括注册和监听,维护url和invoker列表。ZookeeperRegistry和RegistryDirectory还有很多其他属性和方法,包括各种数据map的映射管理,时间轮询器,负载,路由策略的维护等等,就留到具体使用场景中再做介绍。
另外RegistryDirectory还有一个重要的list方法用来从invoker集合中按一定策略选取一个可用调用的对象,发起rpc调用,这个到客户端发起调用的时候再详细介绍了。
以上是关于dubbo源码-从客户端看Registry和Directory的主要内容,如果未能解决你的问题,请参考以下文章