一、缓存机制
注册中心实现了通用的缓存机制,在抽象类AbstractRegistry中实现。

- 消费者或者服务治理中心获取注册信息后会做本地缓存。内存中存一份,保存在Properties对象里,磁盘上也会持久化一份文件,通过file对象引用。

- 内存中的缓存notified,是CouncurrentMap里面又嵌套了一个Map,外层Map的key是消费者的URL,内存Map的key是分类,包括providers,consumers,routes,configurations四种。value是对应的服务列表,对于没有服务提供者提供服务的URL,他会已特殊的empty://前缀开头。

-
此文件是在本地的缓存文件中找到的。
-
缓存文件地址:
this.setUrl(url);
this.syncSaveFile = url.getParameter("save.file", false);
//缓存文件
String filename = url.getParameter("file", System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getHost() + ".cache");
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
file = new File(filename);
if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists() && !file.getParentFile().mkdirs()) {
throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
}
}
this.file = file;
this.loadProperties();
this.notify(url.getBackupUrls());
1、缓存的加载
- 在服务初始化的时候,构造函数里会从本地磁盘文件中把持久化的注册数据读取到Properties对象里,并加载到内存缓存中。
private void loadProperties() {
if (this.file != null && this.file.exists()) {
FileInputStream in = null;
try {
//读取文件
in = new FileInputStream(this.file);
//加载进缓存
this.properties.load(in);
if (this.logger.isInfoEnabled()) {
this.logger.info("Load registry store file " + this.file + ", data: " + this.properties);
}
} catch (Throwable var11) {
this.logger.warn("Failed to load registry store file " + this.file, var11);
} finally {
....
}
}
- Properties保存了所以服务提供者的URL
- 使用URL:serviceKey 作为key,提供者列表,路由配置规则列表
- 配置规则列表等作为Value.由于Value是列表,当存在多个的时候,使用空格隔开。
- 还有一个特殊的key.registies,保存所有的注册中心的地址。
- 如果应用在启动过程中,注册中心无法连接或者宕机,则Dubbo框架会自动通过本地缓存加载Invokers.
this.properties.setProperty(url.getServiceKey(), buf.toString());
long version = this.lastCacheChanged.incrementAndGet();
if (this.syncSaveFile) {
//同步代码
this.doSaveProperties(version);
} else {
//异步保存,放入线程池。version是一个AtomicLong的版本号,保证最新的
this.registryCacheExecutor.execute(new AbstractRegistry.SaveProperties(version));
}
2、缓存的保存和更新
-
缓存的保存有同步和异步两种方式。异步会使用线程池异步保存,如果线程在执行过程中出现异常,则会再次调用线程池不断充实。
-
AbstractRegistry#notify方法中封装了更新内存缓存和更新文件缓存的逻辑。当客户端第一次订阅获取全量数据,或者后续由于订阅得到新数据时,都会调用该方法进行保存。
while(var11.hasNext()) {
Entry<String, List<URL>> entry = (Entry)var11.next();
String category = (String)entry.getKey();
List<URL> categoryList = (List)entry.getValue();
categoryNotified.put(category, categoryList);
this.saveProperties(url);
listener.notify(categoryList);
}
二、重试机制
- FailbackRegistry继承了AbstractRegistry,并且在此基础上增加了失败重试机制作为抽象能力。
- ZookeeperRegistry和RedisRegistry继承该抽象方法后,直接使用即可。
- FailbackRegistry抽象类中定义了一个SecheduledExecutorService,每经过固定间隔(默认为5s)调用FailbackRegistry#retry()方法。
- 该抽象类中还有五个比较重要的集合。
//发起注册失败的URL集合
private final Set<URL> failedRegistered = new ConcurrentHashSet();
//取消注册失败的URL集合
private final Set<URL> failedUnregistered = new ConcurrentHashSet();
//发起订阅失败的监听器集合
private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap();
//取消订阅失败的监听器集合
private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap();
//通知失败的URL集合
private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap();
- 在定时器调用retry方法的时候,会把这无法集合分别遍历和重试,重试成功则从集合中移除。
- FailbackRegistry实现了subscribe和unsubscribe等通用方法,调用了未实现的模板方法,通过子类实现。
- 通用方法会调用这些模板方法,如果捕获异常,则会把URL添加到对应的重试集合中,以供定时器重试。 代码比较长,就不贴出来,感兴趣的可以自行查看。
三、设计模式
1、模板模式
- AbstractRegistry实现了Registry接口中的注册、订阅、查询、通知等方法。
- 还实现了磁盘文件持久化注册信息的这一通用方法。
- 但是注册、订阅、查询和通知方法只是简单的把URL加入对应集合,没有具体的注册或者订阅逻辑。
- FailbackRegistry继承了AbstractRegistry,重写了父类的注册、订阅、查询和通知等方法,并且添加了重试机制。
- 此外还添加了四个未实现的抽象模板方法。
protected abstract void doRegister(URL var1);
protected abstract void doUnregister(URL var1);
protected abstract void doSubscribe(URL var1, NotifyListener var2);
protected abstract void doUnsubscribe(URL var1, NotifyListener var2);
- FailbackRegistry重写了subscribe方法,但只实现了订阅的大体逻辑及异常处理等通性的内容。具体如何订阅,交给继承的子类实现。
- ①在register和retry方法中,调用了doRegister。
- ②在unregister和retry方法中,调用了doUnregister
- ③在subscribe和retry方法中,调用了doSubscribe
- ④在unsubscribe和retry方法
2、工厂模式
- 所有注册中心的实现,都是通过对应的工厂创建的。
- AbstractRegistryFactory类的getRegistry方法
LOCK.lock();
Registry var4;
try {
Registry registry = (Registry)REGISTRIES.get(key);
if (registry == null) {
registry = this.createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
var4 = registry;
return var4;
}
var4 = registry;
} finally {
LOCK.unlock();
}
@SPI("dubbo")
public interface RegistryFactory {
@Adaptive({"protocol"})
Registry getRegistry(URL var1);
}
- Adaptive注解会自动生成代码实现一些逻辑,value参数会从URL中获取protocol的值,并根据获取的值来调用不同的工厂类。
总结:
-
主要介绍了注册中心的结构,注册流程,注册原理等。
-
订阅发布功能,全量订阅和增量订阅的实现。
-
缓存机制,重试机制和注册中心使用到的设计模式(模板模式、工厂模式)