缓存机制
缓存机制就是用空间来换取时间的机制,如果每次都要从远程注册中心获取一次可以调用的服务列表,则会让注册中心承受巨大的网络压力和流量压力。因此Dubbo的注册中心实现了通用的缓存机制,在抽象AbstractRegistry中实现。AbstractRegistry类结构关系如下图所示
消费者或者服务治理中心获取注册信息后会做本地缓存。内存中会有一份,保存在Properties对象里,磁盘上也会持久化一份文件,通过file对象引用。在AbstractRegistry抽象类中有如下的定义。
// Local disk cache, where the special key value.registries records the list of registry centers, and the others are the list of notified service providers
private final Properties properties = new Properties();
private URL registryUrl;
// Local disk cache file
private File file;
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<>();
private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<>();
内存中的缓存notified是ConcurrentHashMap里面嵌套了一个Map,外层Map的key是消费者URL,内层Map是Key是分类,包含providers、consumers、routes、configurators四种。value则是对应的服务列表,对于没有服务提供者提供服务的URL,会以特殊的empty://前缀开头。
缓存的加载
在服务初始化的时候,AbstractRegistry构造函数里会从本地磁盘文件中把持久化的注册数据读取到Properties中,并且加载到内存中。
private void loadProperties() {
if (file != null && file.exists()) {
InputStream in = null;
try {
in = new FileInputStream(file);
properties.load(in);
if (logger.isInfoEnabled()) {
logger.info("Load registry cache file " + file + ", data: " + properties);
}
} catch (Throwable e) {
logger.warn("Failed to load registry cache file " + file, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
}
}
Properties保存了所有服务提供者的URL,使用URL#serviceKey()作为key,提供者列表、路由规则列表、配置规则列表等作为value。由于value是列表,当存在多个的时候使用空格隔开。还有一个特殊的key.registies,保存所有的注册中心地址,如果应用在启动的过程中,注册中心无法连接或者是宕机了,则Dubbo框架会自动通过本地缓存加载Invokers。
缓存的保存与更新
缓存的保存有同步和异步两种方式。异步会使用线程池异步保存,如果线程在执行过程中出现异常,则会再次调用线程池不断进行重试
private void saveProperties(URL url) {
if (file == null) {
return;
}
try {
StringBuilder buf = new StringBuilder();
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified != null) {
for (List<URL> us : categoryNotified.values()) {
for (URL u : us) {
if (buf.length() > 0) {
buf.append(URL_SEPARATOR);
}
buf.append(u.toFullString());
}
}
}
properties.setProperty(url.getServiceKey(), buf.toString());
long version = lastCacheChanged.incrementAndGet();
if (syncSaveFile) {
doSaveProperties(version);
} else {
registryCacheExecutor.execute(new SaveProperties(version));
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
AbstractRegistry#notify 方法中封装了更新缓存和更新文件的逻辑。当客户端第一次订阅获取全量数据,或者后续由于订阅得到新数据的时候,都会调用该方法进行保存
protected void notify(List<URL> urls) {
if (CollectionUtils.isEmpty(urls)) {
return;
}
for (Map.Entry<URL, Set<NotifyListener>> entry : getSubscribed().entrySet()) {
URL url = entry.getKey();
if (!UrlUtils.isMatch(url, urls.get(0))) {
continue;
}
Set<NotifyListener> listeners = entry.getValue();
if (listeners != null) {
for (NotifyListener listener : listeners) {
try {
notify(url, listener, filterEmpty(url, urls));
} catch (Throwable t) {
logger.error("Failed to notify registry event, urls: " + urls + ", cause: " + t.getMessage(), t);
}
}
}
}
}