Netflix Eureka原始碼分析(13)——eureka server的登錄檔多級快取過期機制:主動過期+定時過期+被動過期
(1)主動過期
readWriteCacheMap,讀寫快取
有新的服務例項發生註冊、下線、故障的時候,就會去重新整理readWriteCacheMap
比如說現在有一個服務A,ServiceA,有一個新的服務例項,Instance010來註冊了,註冊完了之後,其實必須是得重新整理這個快取的,然後就會呼叫ResponseCache.invalidate(),將之前快取好的ALL_APPS這個key對應的快取,給它過期掉
將readWriteCacheMap中的ALL_APPS快取key,對應的快取給過期掉,比如新例項註冊時的程式碼邏輯如下:
public abstract class AbstractInstanceRegistry implements InstanceRegistry { public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) { //註冊的一些程式碼邏輯省略。。。 //有新例項註冊時將之前的ALL_APPS對應的登錄檔快取過期掉 invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress()); } private void invalidateCache(String appName, @Nullable String vipAddress, @Nullable String secureVipAddress) { // invalidate cache responseCache.invalidate(appName, vipAddress, secureVipAddress); } }
public class ResponseCacheImpl implements ResponseCache { //操作快取過期的實現方法 @Override public void invalidate(String appName, @Nullable String vipAddress, @Nullable String secureVipAddress) { for (Key.KeyType type : Key.KeyType.values()) { for (Version v : Version.values()) { invalidate( new Key(Key.EntityType.Application, appName, type, v, EurekaAccept.full), new Key(Key.EntityType.Application, appName, type, v, EurekaAccept.compact), new Key(Key.EntityType.Application, ALL_APPS, type, v, EurekaAccept.full), new Key(Key.EntityType.Application, ALL_APPS, type, v, EurekaAccept.compact), new Key(Key.EntityType.Application, ALL_APPS_DELTA, type, v, EurekaAccept.full), new Key(Key.EntityType.Application, ALL_APPS_DELTA, type, v, EurekaAccept.compact) ); if (null != vipAddress) { invalidate(new Key(Key.EntityType.VIP, vipAddress, type, v, EurekaAccept.full)); } if (null != secureVipAddress) { invalidate(new Key(Key.EntityType.SVIP, secureVipAddress, type, v, EurekaAccept.full)); } } } } }
(2)定時過期
readWriteCacheMap在構建的時候,指定了一個自動過期的時間,預設值就是180秒,所以你往readWriteCacheMap中放入一個數據過後,自動會等180秒過後,就將這個資料給他過期了
public class ResponseCacheImpl implements ResponseCache { private final LoadingCache<Key, Value> readWriteCacheMap; ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) { this.readWriteCacheMap = CacheBuilder.newBuilder().initialCapacity(1000) .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; } }); } }
@Singleton
public class DefaultEurekaServerConfig implements EurekaServerConfig {
//返回快取自動過期時間的配置項
@Override
public long getResponseCacheAutoExpirationInSeconds() {
return configInstance.getIntProperty(
namespace + "responseCacheAutoExpirationInSeconds", 180).get();
}
}
(3)被動過期
readOnlyCacheMap怎麼過期呢?
預設是每隔30秒,呼叫getCacheUpdateTask方法,執行一個定時排程的執行緒任務,TimerTask,有一個邏輯,會每隔30秒,對readOnlyCacheMap和readWriteCacheMap中的資料進行一個比對,如果兩塊資料是不一致的,那麼就將readWriteCacheMap中的資料放到readOnlyCacheMap中來。
比如說readWriteCacheMap中,ALL_APPS這個key對應的快取沒了,那麼最多30秒過後,就會同步到readOnelyCacheMap中去
public class ResponseCacheImpl implements ResponseCache {
private final LoadingCache<Key, Value> readWriteCacheMap;
ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry)
{
if (shouldUseReadOnlyResponseCache) {
timer.schedule(getCacheUpdateTask(),
new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
+ responseCacheUpdateIntervalMs),
responseCacheUpdateIntervalMs);
}
}
private TimerTask getCacheUpdateTask() {
return new TimerTask() {
@Override
public void run() {
logger.debug("Updating the client cache from response cache");
for (Key key : readOnlyCacheMap.keySet()) {
if (logger.isDebugEnabled()) {
Object[] args = {key.getEntityType(), key.getName(), key.getVersion(), key.getType()};
logger.debug("Updating the client cache from response cache for key : {} {} {} {}", args);
}
try {
CurrentRequestVersion.set(key.getVersion());
Value cacheValue = readWriteCacheMap.get(key);
Value currentCacheValue = readOnlyCacheMap.get(key);
//如果兩塊資料是不一致的,那麼就將readWriteCacheMap中的資料放到readOnlyCacheMap中來。
if (cacheValue != currentCacheValue) {
readOnlyCacheMap.put(key, cacheValue);
}
} catch (Throwable th) {
logger.error("Error while updating the client cache from response cache", th);
}
}
}
};
}
}
(4)很重要的問題
假設有服務例項註冊、下線、故障,要呼叫這個服務的其他服務,可能會過30秒之後才能感知到,為什麼呢?因為這裡在獲取服務登錄檔的時候,有一個多級快取的機制,最多是30秒才會去更新快取
總結一張 eureka-server的多級快取過期機制 流程圖