本文基于dubbo 2.6.x
文章目录
- 一、ZookeeperRegistryFactory
- 二、ZookeeperRegistry
- 2.1 class与成员变量
- 2.2 构造
- 2.3 doRegister
- 2.4 doUnregister
- 2.5 doSubscribe
- 2.6 doUnsubscribe
- 三、总结
一、ZookeeperRegistryFactory
ZookeeperRegistryFactory是注册中心zookeeper工厂的实现,代码比较简单,我们直接来看下。
可以看到ZookeeperRegistryFactory继承AbstractRegistryFactory抽象类,需要实现createRegistry方法,在createRegistry方法中,创建了一个zookeeper注册中心对象,参数分别是注册中心url与zookeeperTransporter (这个是dubbo spi 自动注入的,zookeeperTransporter能够获取zk客户端对象,这个文章后面会有说到)
二、ZookeeperRegistry
ZookeeperRegistry注册中心zk的实现,我们来看看它的源码实现
2.1 class与成员变量
public class ZookeeperRegistry extends FailbackRegistry {...}
可以看出ZookeeperRegistry继承FailbackRegistry失败重试抽象类,ZookeeperRegistry就需要实现doRegister,doUnregister,doSubscribe,doUnsubscribe 这几个方法(这个我们后面会一一解析)
接下来我们来看成员变量
2.2 构造
接下来看下构造方法:
在构造方法中,先是调用父类的构造,接着判断注册中心url的host是不是0.0.0.0(也就是任意host),下面就是获取group参数值,缺省是dubbo,如果不是"/“开头的就在开头拼接个”/",将group 赋值给root属性(其实这里很重要的,这个root就是在zk里面的根路径,如果你配置了组信息,将以你那个组信息值开头),接着就是使用zookeeperTransporter获取zk客户端对象,给zk客户端添加一个重连监听器,如果状态是重连状态,就调用recover方法来进行恢复,这里使用zookeeperTransporter获取zk客户端对象需要说下,zk的java客户端有两种,一个是curator,一个是zkclient,同时dubbo将这两种客户端一系列操作抽象了一个接口ZookeeperClient,curator与zkclient分别实现ZookeeperClient,这样做的好处就是统一了操作,方便开发,同时也增加了灵活性扩展性,使用zookeeperTransporter就是为了获取用户配置的ZookeeperClient实现,这里先看下ZookeeperClient实现关系
再来看下ZookeeperClient 抽象了哪些操作:
public interface ZookeeperClient {
// 创建节点
void create(String path, boolean ephemeral);
// 删除节点
void delete(String path);
// 获取某个节点的子节点
List<String> getChildren(String path);
//为某个节点添加子节点监听器
List<String> addChildListener(String path, ChildListener listener);
// 移除某个节点的某个子节点监听器
void removeChildListener(String path, ChildListener listener);
// 添加 连接状态监听器
void addStateListener(StateListener listener);
// 移除 连接状态监听器
void removeStateListener(StateListener listener);
// 是否连接
boolean isConnected();
// 关闭
void close();
// 获取url
URL getUrl();
}
zk客户端具体实现我们会后面说下。
2.3 doRegister
接着我们看下ZookeeperRegistry的doRegister进行注册方法实现:
我们可以看到使用zk客户端创建一个节点,这个节点的路径是toUrlPath(url) , 然后持久还是动态是由url中配置的dynamic参数值决定的,缺省是使用动态(所谓的动态就是会话级别的,断开连接,这个路径就没了)
这里我们更关心toUrlPath(url) 这个方法的实现:
// 将url转成 urlPath
private String toUrlPath(URL url) {
// "/dubbo/xxx.xxx.xxx.Xxxx/gategory(providers)/urlfullString"
return toCategoryPath(url) + Constants.PATH_SEPARATOR + URL.encode(url.toFullString());
}
可以看到是toCategoryPath生成的一个path 拼接上 “/” ,再拼接上url的fullstring信息。接着在看下toCategoryPath方法
private String toCategoryPath(URL url) {
// "/dubbo/xxx.xxx.xxx.Xxxx/gategory(providers)"
return toServicePath(url) + Constants.PATH_SEPARATOR + url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
}
toCategoryPath方法是 toServicePath(url) 获取的path路径拼接上"/" ,在拼接上url的category分类信息,默认是providers
private String toServicePath(URL url) {
String name = url.getServiceInterface();// 接口全类名
if (Constants.ANY_VALUE.equals(name)) {
return toRootPath();
}
// "/dubbo/xxx.xxx.xxx.Xxxx"
return toRootDir() + URL.encode(name);
}
toServicePath 中,先是获取接口全类名,如果接口全类名是*, 直接返回 root的值,也就是/dubbo 或者/你设置的group, 如果不是 ,就返回/dubbo/接口全类名
这个toUrlPath 方法全部拼接出来就是:
/dubbo(或者你设置的group属性值)/接口全类名(如果你接口全类名是`*` ,就没有这部分了)/ providers(也可以是consumers,routers,configurators 这个是根据你的category 属性值来的)/url的fullstring(这里其实就是url的 protocol, host,port,service,parameter等等)
这个就是你某个serivce在zk中的存在结构,我这里举个例子
/dubbo
/com.xuzhaocai.dubbo.provider.IHelloProviderService
/providers
/dubbo%3A%2F%2F192.162.0.174%3A18109%2Fcom.xuzhaocai.dubbo.provider.IHelloProviderService%3Faccesslog%3Dtrue%26anyhost%3Dtrue%26application%3Ddubbo-consumer%26bean.name%3DServiceBean%3Acom.xuzhaocai.dubbo.provider.IHelloProviderService%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.xuzhaocai.dubbo.provider.IHelloProviderService%26methods%3DgetName%26pid%3D39753%26side%3Dprovider%26timestamp%3D1598241007805
在zk中大概就是下图这样一个结构(树结构):
2.4 doUnregister
接着我们再来看下doUnregister 下线方法实现:
这里就是调用zk客户端的delete方法删除url对应path的节点。
2.5 doSubscribe
接下来再来看下doSubscribe 进行订阅的方法(由于方法比较长,分为2部分):
第一部分是订阅所有
当url的service interface 是*
的时候,表示要订阅所有,先是获取root,订阅所有,其实就是订阅根节点下面的所有子节点,接着从缓存中获取url对应的listener map ,这里为啥是map呢,其实一个url可以多个订阅者(也就可以有多个监听器),每个监听器又对应一个具体的子节点监听器。如果缓存中没有存在,就创建。如果没有获取到子节点监听器,也创建ChildListener对象,我们可以看看它里面的实现:
@Override
public void childChanged(String parentPath, List<String> currentChilds) {
for (String child : currentChilds) {// 遍历child
child = URL.decode(child);
if (!anyServices.contains(child)) {
anyServices.add(child);
// 订阅
subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
}
当订阅节点下面的子节点发生变化的时候,就会调用childChanged 方法,parentPath 表示父节点,也就是该节点下面的子节点发生了变化,currentChilds表示当前parentPath 节点下面所有的子节点集合。在childChanged方法中,对子节点集合进行遍历,然后如果这个子节点没有在anyServices 集合中,就添加进去(anyServices 集合 就是存的所有的接口全类名,也就是根节点下的所有子节点),接着就是订阅这个子节点。
doSubscribe 方法接着往下看,使用zk客户端创建root节点,对root节点添加监听器listener,获取到root节点(也就是根节点 比如/dubbo)子节点集合,遍历集合添加到anyServices 集合中,然后订阅这堆子节点。
接着再看看第二部分,订阅某个接口的时候。
该部分中,先是通过toCategoriesPath获取paths,这个paths的个数其实是由url的category属性决定的 这个path就是上面说的这个,需要注意的是,这里path是没有url.tofullstring 那部分的,因为我们这订阅的是它的父节点,当下面子节点发生变动的时候就会通知。
/dubbo(或者你设置的group属性值)/接口全类名(如果你接口全类名是`*` ,就没有这部分了)/ providers(也可以是consumers,routers,configurators 这个是根据你的category 属性值来的)
如果你的category属性值是*
它就创建4个,分别是providers,routers,consumers,configurators,不是*
就去你设置的那个,缺省是providers。接着就是根据url从zkListeners缓存中获取,如果没有的话就创建,跟上面一样,但是它这里创建的子节点监视器ChildListener 对象与上面那部分不一样(这里很重要)
@Override
public void childChanged(String parentPath, List<String> currentChilds) {
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
当订阅的节点子类发生变化的时候,就会调用监听器的childChanged 方法,在childChanged 方法 中调用了notify方法进行通知。
再往下看,就是创建这个path,在path上面添加监听器,能够获取现在path下面的子节点,然后把这些子节点经过过滤后添加到list中。
最后遍历完成后,调用notify通知(这里这样的做的原因是第一次订阅的时候,如果不通知,需要子节点变动的时候才能通知,才能获取到服务提供者列表),这里面有个toUrlsWithEmpty(url, path, children) 方法,该方法将 字符串子节点转成url集合,其实这个方法中有个比较重要的点就是过滤,需要与consumerUrl的某些条件匹配上。我们来看下:
开始调用toUrlsWithoutEmpty(consumer, providers); 方法将字符串子节点转成url集合,我们来看下这个方法
这里就是遍历providers,然后需要两个条件才能添加到结果集合中,一个是判断 里面有没有://
这个东西就是放在协议protocol后面的,这个有之后 ,将字符串就转成了url,然后调用UrlUtils.isMatch(consumer, url)方法进行匹配,匹配的上才能添加到结果集合中,这个isMatch 方法主要比对双方的 接口,category,enable,group,version,classifier 这些信息:
再回到toUrlsWithEmpty 方法中,转换成urls之后,如果是空的,就生成一个protocol 是empty的url塞进去返回,为什么要生成一个empty的url(这玩意就是代表着注册中心没有该consumer的实现了,本地缓存也就需要清空),原因是listener实现类notify方法中,看到就这一条empty url的时候就会清空对应的category下的url缓存。
到这我们的执行订阅doSubscribe 方法就讲解完成了
2.6 doUnsubscribe
接下来我们看下doUnsubscribe 执行取消订阅的方法的实现:
这个比较简单,就是从缓存zkListeners 中获取对应的listener map集合,然后再获取listener对应的ChildListener监听器,如果不是空的话,判断接口是不是*
,如果是,就调用zk客户端的removeChildListener方法移除根节点下的对应listener。
如果不是,就循环移除分组path的对应listener。
三、总结
本文主要讲解了 注册中心zk工厂的实现 ZookeeperRegistryFactory 与 注册中心zk 的实现ZookeeperRegistry ,其中注册中心zk 实现ZookeeperRegistry 是非常重要的,在这个类中并不是直接操作的zk,而是dubbo抽象了zk客户端 curator与 zkClient两种实现的一些操作,通过dubbo spi 来根据配置获得具体的实现(我们会单独解析zk客户端实现的),ZookeeperRegistry 中主要是注册到zk里面的树结构,订阅方法doSubscribe是很重要的,尤其是一些细节,比如说创建的节点监听 ,当子节点变动的时候,就会调用notify方法进行通知。