springcloud源码之eureka-server缓存设计

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 取数据,避免了大量的加锁操作,这样使得大部分请求都会被前二级缓存拦截,并且读读无并发,请求跑到只读缓存可以不用同步

发布了164 篇原创文章 · 获赞 81 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/LiuRenyou/article/details/104904341