缓存机制
缓存的存在就是用空间换取时间,如果每次远程调用都要先从注册中心获取一次可调用的服务列表,则会让注册中心承受巨大的流量压力。另外,每次额外的网络请求也会让整个系统的性能下降。因此Dubbo的注册中心实现了通用的缓存机制,在抽象类AbstractRegistry中实现。AbstractRegistry类结构关系如图3-5所示。
消费者或服务治理中心获取注册信息后会做本地缓存。内存中会有一份,保存在Properties对象里,磁盘上也会持久化一份文件,通过file对象引用。在AbstractRegistry抽象类中有如下定义,如代码清单3-10所示。
内存中的缓存notified是ConcurrentHashMap里面又嵌套了一个Map,外层Map的key是消费者的 URL,内层 Map 的 key 是分类,包含 providers> consumers> routes> configurators四种。value则是对应的服务列表,对于没有服务提供者提供服务的URL,它会以特殊的empty://前缀开头。
缓存的加载
在服务初始化的时候,AbstractRegistry构造函数里会从本地磁盘文件中把持久化的注册数据读到Properties对象里,并加载到内存缓存中,如代码清单3-11所示。
代码清单3-11 Properties缓存初始化
private void loadProperties() {
if (file != null && file.exists。)(
InputStream in = null;
try {
in = new FilelnputStream(file);
properties.load(in);
}
} catch (Throwable e) (
) finally (
}
}
}
Properties保存了所有服务提供者的URL,使用URL#serviceKey()作为key,提供者列表、路由规则列表、配置规则列表等作为value。由于value是列表,当存在多个的时候使用空格隔开。还有一个特殊的key.registies,保存所有的注册中心的地址。如果应用在启动过程中,注册中心无法连接或宕机,则Dubbo框架会自动通过本地缓存加载Invokerso
缓存的保存与更新
缓存的保存有同步和异步两种方式。异步会使用线程池异步保存,如果线程在执行过程中出现异常,则会再次调用线程池不断重试,如代码清单3.12所示。
AbstractRegistry#notify方法中封装了更新内存缓存和更新文件缓存的逻辑。当客户端第一次订阅获取全量数据,或者后续由于订阅得到新数据时,都会调用该方法进行保存。
重试机制
由图 3-5 我们可以得知
com.alibaba.dubbo.registry.support.FailbackRegistry 继承了AbstractRegistry,并在此基础上增加了失败重试机制作为抽象能力。ZookeeperRegistry和RedisRegistry继承该抽象方法后,直接使用即可。
FailbackRegistry抽象类中定义了一个ScheduledExecutorService,每经过固定间隔(默认为5秒)调用FailbackRegistry#retry()方法。另外,该抽象类中还有五个比较重要的集合,如表3-3所示。
在定时器中调用retry方法的时候,会把这五个集合分别遍历和重试,重试成功则从集合中移除。FailbackRegistry实现了 subscribe> unsubscribe等通用方法,里面调用了未实现的模板方法,会由子类实现。通用方法会调用这些模板方法,如果捕获到异常,则会把URL添加到对应的重试集合中,以供定时器去重试。
设计模式
Dubbo注册中心拥有良好的扩展性,用户可以在其基础上,快速开发出符合自己业务需求的注册中心。这种扩展性和Dubbo中使用的设计模式密不可分,本节将介绍注册中心模块使用的设计模式。学习完本节后,能降低读者对注册中心源码阅读的门槛。
模板模式
整个注册中心的逻辑部分使用了模板模式,其类的关系如图3-6所示。
AbstractRegistry实现了 Registry接口中的注册、订阅、查询、通知等方法,还实现了磁盘文件持久化注册信息这一通用方法。但是注册、订阅、查询、通知等方法只是简单地把URL加入对应的集合,没有具体的注册或订阅逻辑。
FailbackRegistry又继承了 AbstractRegistry,重写了父类的注册、订阅、查询和通知等方法,并且添加了重试机制。此外,还添加了四个未实现的抽象模板方法,如代码清单3-13所示。
代码清单3-13未实现的抽象模板方法
protected abstract void doRegister(URL url);
protected abstract void dollnregister(URL url);
protected abstract void doSubscribe(URL url. NotifyListener listener);
protected abstract void doUnsubscribe(URL url. NotifyListener listener);
以订阅为例,FailbackRegistry重写了 subscribe方法,但只实现了订阅的大体逻辑及异常处理等通用性的东西。具体如何订阅,交给继承的子类实现。这就是模板模式的具体实现,如代码清单3.14所示。
代码清单3-14模板模式调用
public void subscribe(URL url, NotifyListener listener) (
super.subscribe(urllistener);
removeFailedSubscribed(url> listener);
try (
doSubscribe(url, listener); <一此处调用了模板方法,由子类自行实现
} catch (Exception e) (
工厂模式
所有的注册中心实现,都是通过对应的工厂创建的。工厂类之间的关系如图3.7所示。
AbstractRegistryFactory 实现了 RegistryFactory 接口的 getRegistry(URL url)方法,是一个通用实现,主要完成了加锁,以及调用抽象模板方法createRegistry(URL url)创建具体实现等操作,并缓存在内存中。抽象模板方法会由具体子类继承并实现,如代码清单3-15所示。
虽然每种注册中心都有自己具体的工厂类,但是在什么地方判断,应该调用哪个工厂类实现呢?代码中并没有看到显式的判断。答案就在RegistryFactory接口中,该接口里有一个Registry getRegistry(URL url)方法,该方法上有@Adaptive({"protocol"))注解,如代码清单3-16所示。
代码清单 3-16 RegistryFactory 源码
@SPI("dubbo")
public interface RegistryFactory (
@Adaptive({"protocol"})
Registry getRegistry(URL url);
)
了解AOP的读者就会很容易理解,这个注解会自动生成代码实现一些逻辑,它的value参数会从URL中获取protocol键的值,并根据获取的值来调用不同的工厂类。例如,当url.protocol = redis时,获得RedisRegistryFactory实现类。具体Adaptive注解的实现原理会在第4章Dubbo加载机制中讲解。
小结
本章介绍了 Dubbo中已经支持的注册中心。重点介绍了 ZooKeeper和Redis两种注册中心。
讲解了两种注册中心的数据结构,以及订阅发布机制的具体实现。
然后介绍了注册中心中一些通用的关键特性,如数据缓存、重试等机制。最后,在对各种机制己经了解的前提下,讲解了整个注册中心源码的设计模式。