springcloud源码之eureka-server缓存设计
入口
com.netflix.eureka:eureka-core源码的resources包的ApplicationsResource类
//服务发现
ApplicationsResource#getContainers(){
responseCache.getGZIP(cacheKey)
}
————>
ResponseCacheImpl#getGZIP(){
getValue(key, shouldUseReadOnlyResponseCache);
}
————>
ResponseCacheImpl#getValue()
缓存设计
Value getValue(final Key key, boolean useReadOnlyCache) {
Value payload = null;
//是否使用只读缓存
if (useReadOnlyCache) {
//第一步从只读缓存拿数据
final Value currentPayload = readOnlyCacheMap.get(key);
if (currentPayload != null) {
payload = currentPayload;
}
//只读缓存没拿到去读写缓存拿数据
else {
payload = readWriteCacheMap.get(key);
//放入只读缓存
readOnlyCacheMap.put(key, payload);
}
}
//如果不用只读缓存直接从读写缓存拿数据
else {
payload = readWriteCacheMap.get(key);
}
return payload;
}
上面这段代码并没有看到两个缓存的put方法,并且也没有看到最终的数据registry,
首先我们先看看只读缓存的更新:
下面这段代码在ResponseCacheImpl的构造方法里
if (shouldUseReadOnlyResponseCache) {
//每隔30s去更新一次只读缓存
timer.schedule(getCacheUpdateTask(),
new Date((System.currentTimeMillis() + responseCacheUpdateIntervalMs),
responseCacheUpdateIntervalMs);
}
getCacheUpdateTask()
private TimerTask getCacheUpdateTask() {
return new TimerTask() {
@Override
public void run() {
//遍历只读缓存,如果和读写缓存不一致,就更新只读缓存
for (Key key : readOnlyCacheMap.keySet()) {
Value cacheValue = readWriteCacheMap.get(key);
Value currentCacheValue = readOnlyCacheMap.get(key);
if (cacheValue != currentCacheValue) {
readOnlyCacheMap.put(key, cacheValue);
}
}
}
};
}
下面我们再看看读写缓存的更新
下面这段代码在ResponseCacheImpl的构造方法里
//readWriteCacheMap 用到了guava的cache,我不太熟
this.readWriteCacheMap =
CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
.expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
.removalListener(new RemovalListener<Key, Value>() {
//每隔180s会把读写缓存的数据清除
@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>() {
//如果读写缓存没有拿到数据,会load
@Override
public Value load(Key key) throws Exception {
if (key.hasRegions()) {
Key cloneWithNoRegions = key.cloneWithoutRegions();
regionSpecificKeys.put(cloneWithNoRegions, key);
}
//去真实数据registry里拿,这里不展开说了
Value value = generatePayload(key);
return value;
}
});
总结
eureka-server的缓存采取三级缓存架构
第一级:只读缓存 (ConcurrentHashMap)
第二级:读写缓存 (Guava Cache)
第三级:registry (ConcurrentHashMap)
取数据:
先从只读缓存拿到了直接返回,拿不到拿读写缓存,拿到了回写只读缓存后返回,拿不到拿registry ,拿到后回写读写缓存
更新数据:
只读缓存 开启定时器每30s执行一次操作,如果只读缓存里面的值与读写缓存不一致,则更新只读缓存
读写缓存每180秒清除自己,如果在读写缓存中拿不到数据会去registry拿然后回写到读写缓存
读写三级缓存优点
避免了所有拿微服务数据全部跑到registry 取数据,避免了大量的加锁操作,这样使得大部分请求都会被前二级缓存拦截,并且读读无并发,请求跑到只读缓存可以不用同步