spring · 18 8 月, 2021 0

spring cloud 服务注册之Eureka Server(四) – 三级缓存

通过前面章节的介绍, 可以明确知道在InstanceRegistry初始化的时候, 会初始化ResponseCacheImpl的类,而这个类就是对于三级缓存的重要实现. 这章节主要介绍三级缓存的工作原理,以代码的形式明确三级缓存的实现。

三级缓存工作模式

eureka 三级缓存

缓存初始化

缓存对象初始化中, 对缓存初始化,具体源码如下:

ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
        // eureka server配置信息, 主要以eureka.server开头, 在spring中,主要以EurekaServerConfigBean实现
        this.serverConfig = serverConfig;
        this.serverCodecs = serverCodecs;

        // 当前配置定义了是否开启readOnlyResponseCache, 默认值为true
       // 配置信息为:eureka.server.read-only-response-cache  = true
        this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();

        // 当前InstanceRegistry对象, 内部存储instance信息采用的ConccurentHashMap
        this.registry = registry;

        // 更新responseCache缓存的时间, 该事件默认值为: 30秒
        long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();

        // readWriteCacheMap二级缓存初始化, 采用的guava的是LoadingCache的实现
        // 在build方法中,定义了CacheLoader对象,用于在通过Key信息获取实例失败时,将会从InstanceRegistry中获取注册的实例信息
        this.readWriteCacheMap =
                CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
                        .expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
                        .removalListener(new RemovalListener<Key, Value>() {
                            @Override
                            public void onRemoval(RemovalNotification<Key, Value> notification) {
                                Key removedKey = notification.getKey();
                                if (removedKey.hasRegions()) {
                                    Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
                                    regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
                                }
                            }
                        })
                        .build(new CacheLoader<Key, Value>() {
                            @Override
                            public Value load(Key key) throws Exception {
                                if (key.hasRegions()) {
                                    Key cloneWithNoRegions = key.cloneWithoutRegions();
                                    regionSpecificKeys.put(cloneWithNoRegions, key);
                                }
                                Value value = generatePayload(key);
                                return value;
                            }
                        });

        // 该处判断是否启用readOnlyResponseCache, 如果启用,通过定时任务的方式更新readOnlyCache缓存信息, 每隔30秒执行一次
        if (shouldUseReadOnlyResponseCache) {
            timer.schedule(getCacheUpdateTask(),
                    new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
                            + responseCacheUpdateIntervalMs),
                    responseCacheUpdateIntervalMs);
        }

        try {
            // 注册JMX监控信息
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register the JMX monitor for the InstanceRegistry", e);
        }
    }

readWriteCacheMap

readWriteCacheMap作为二级缓存实现, 在构建时,主要包含了两个部分的定义:

  • 数据过期时间: 通过eureka.server.response-cache-auto-expireation-in-seconds , 默认值为180
  • 加载Registry数据到readWriteCacheMap: key的加载触发时机主要在于通过Key获取Value时,如果Key不存在,则从InstanceRegistry中加载
  • 通过Guava的LoadingCache实现, 缓存Key过期通过expireAfterWrite方式设置, 则从Key写入写入之后, 180秒后过期

eureka 三级缓存

readOnlyCacheMap

ReadOnlyCache本身是一个ConcurrentHashMap,存储内容为Key -> Value. readOnlyCacheMap中本身包含了定时任务清理缓存中的内容. UpdateTask有以下几点:

  • ReadOnlyCacheMap中的数据过期为30秒
  • ReadOnlyCacheMap可以通过配置文件关闭: eureka.server.read-only-response-cache = true. 关闭之后,则不开启定时任务
this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
this.registry = registry;

long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
       
...

if (shouldUseReadOnlyResponseCache) {
    // 如果开启了readOnlyResponseCache, 则创建定时任务,每30秒执行一次
    timer.schedule(getCacheUpdateTask(),
            new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
                    + responseCacheUpdateIntervalMs),
            responseCacheUpdateIntervalMs);
}

updateTask

private TimerTask getCacheUpdateTask() {
        return new TimerTask() {
            @Override
            public void run() {
                logger.debug("Updating the client cache from response cache");

                // 遍历当前readOnlyCacheMap中的所有Key
                for (Key key : readOnlyCacheMap.keySet()) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Updating the client cache from response cache for key : {} {} {} {}",
                                key.getEntityType(), key.getName(), key.getVersion(), key.getType());
                    }
                    try {
                        CurrentRequestVersion.set(key.getVersion());
                       
                       // 从readWriteCacheMap中获取对应key的value值
                        Value cacheValue = readWriteCacheMap.get(key);

                        // 从readOnlyCacheMap中获取value值
                        Value currentCacheValue = readOnlyCacheMap.get(key);

                        // 两者Value不相等,说明readWriteCacheMap中的值已被更新,以readWriteCacheMap为准
                        if (cacheValue != currentCacheValue) {
                            // 更新readOnlyCacheMap
                            readOnlyCacheMap.put(key, cacheValue);
                        }
                    } catch (Throwable th) {
                        logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), th);
                    }
                }
            }
        };
    }

更新任务中,只是将当前readOnlyCacheMap中的数据与readWriteCacheMap中的数据进行比对, 不相等,就更新readOnlyCacheMap中的数据。

eureka 三级缓存

因此完整三级缓存流程图如下:

eureka 三级缓存