二、Apache Dubbo学习整理---注册中心(3)

366 阅读5分钟

一、缓存机制

注册中心实现了通用的缓存机制,在抽象类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的值,并根据获取的值来调用不同的工厂类。

总结:

  • 主要介绍了注册中心的结构,注册流程,注册原理等。

  • 订阅发布功能,全量订阅和增量订阅的实现。

  • 缓存机制,重试机制和注册中心使用到的设计模式(模板模式、工厂模式)