Dubbo源码分析(3)—— 注册中心

267 阅读3分钟

前言

上一篇文章提到ServiceConfig处理url信息后交给DubboProtocol进行服务的暴露,开启了netty服务器,随后可以看到ServiceConfig的代码就结束。那么是如何将服务写入到注册中心呢?

 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        Exporter<?> exporter = protocol.export(wrapperInvoker);

ServiceConfig有一段这样的代码,就是核心export代码,但是在这之前有一句:

registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())

这段代码已经把url变成了这样的格式:

image.png

区别就是:没有修改之前的url是dubbo开头,按照spi逻辑会去调用DubboProtocol这个类,但是修改后是registry开头,实际上调用的类是RegistryProtocol

正文

zookeeper注册原理

Zookeeper是一个树形结构的注册中心,可以把它看作是目录结构。一共有四种类型节点:

  • 持久节点:保证节点不丢失,重启后依然存在
  • 持久顺序节点:持久基础上增加了顺序功能
  • 临时节点:连接丢失和session超时后会被移除
  • 临时顺序节点:同上解释

dubbo注册在zookeeper的树形结构和值如下:

----dubbo

---------service

----------------providers:dubbo://ip.端口/接口路径?key=..

----------------consumers:consumer://ip.端口/接口路径key=..

----------------routers:condition.....

----------------configurators:override.....

++注册中心主要作用是订阅发布,当一个新的服务发布的时候,其他节点可以自动跟新++

注册过程

rpc包下有三个实现类

image.png

其中有两个是Wrapper类,默认调用,根据前面SPI的介绍,wrapper类会按照顺序封装,进行链式调用

image.png

可以看到只要是registry的协议都会进入RegistryProtocol

进入RegistryProtocol的export方法

image.png

首先是doLocalExport方法

image.png

getCacheKey方法将URL解析成如下图所示,协议为dubbo,然后检查缓存

image.png

所以真正的本地暴露是进入的doLocalExport方法,然后再调用DubboProtocol,按照上一篇文章介绍的一样进行暴露并且开启netty服务器。

URL registryUrl = getRegistryUrl(originInvoker);

下一步获取regitryUrl,里面包含了RegistryService内部信息:

zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.168.115%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bean.name%3Dcom.alibaba.dubbo.demo.DemoService%26bind.ip%3D192.168.168.115%26bind.port%3D20880%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D90732%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1608539235916&pid=90732&qos.port=22222&timestamp=1608539235882

然后通过这个url去获取到Regitry实例

 final Registry registry = getRegistry(originInvoker);

image.png

image.png

RegistryFactory也是个SPI接口,这里使用的是ZookeeperRegistryFactory

image.png

内部的ZookeeperTransporter也是个SPI方法,可以选择使用curator或者zkclient,默认curator,里面封装了zookeeper的操作方法。

获取到了Registry的实例,然后解析注册中心的url,就可以通过url连接上注册中心并且操作API了

zkclient的注册方法

image.png

创建了一个path,值就是url

然后订阅该url

image.png

新建了一个OverrideListener,然后调用subscribe方法

image.png

这是全量订阅服务,表示所有类别都订阅

image.png

这是只订阅某个类别:providers、consumers等

image.png

当有新的服务跟新的时候,会回掉notify方法,对url执行变跟

image.png

这是注册中心的接口图,AbstractRegistry内部缓存了注册中心可调用的服务列表,以空间换时间。而FailbackRegistry添加了重试功能,内部维护发起注册失败、取消注册失败、发起订阅失败、取消订阅失败的SET,包装了接口方法。而真实的实现方法则是使用模板方法的设计模式,均由子类实现,而父类只调用该方法。

例子

image.png

父类调用Do开头的方法,子类负责实现


最终的过程就是:

  1. ServiceConfig将URL封装成registry协议,交给RegistryProtocol
  2. RegistryProtocol调用doLocalExport,使用自定义的协议(默认Dubbo),进行本地调用
  3. 解析注册中心地址,根据配置的client获取操作类(默认curator)
  4. 将url注册上zookeeper(创建一个目录),然后订阅,如果注册中心有改变,调用notify方法通知本地

可以看到注册中心的代码是比较简单的,如果配置多个注册中心,在ServiceConfig会进行循环注册,注册的过程并不涉及到集群有关的处理。