当前位置: 首页 > 后端技术 > Java

Spring Cloud Eureka源码分析之三级缓存的设计原理及源码分析

时间:2023-04-01 14:14:44 Java

SpringCloudEureka源码分析之三级缓存设计原理及源码分析原理如下图所示。第一层缓存:readOnlyCacheMap,本质是ConcurrentHashMap,依赖定时从readWriteCacheMap同步数据,默认时间为30秒。readOnlyCacheMap:它是一个CurrentHashMap只读缓存。这主要是为了客户端获取注册信息。它的缓存更新依赖于定时器的更新。通过与readWriteCacheMap的值进行比较,如果数据不一致,则以readWriteCacheMap的数据为准。第二层缓存:readWriteCacheMap,本质上是一个Guava缓存。readWriteCacheMap:readWriteCacheMap的数据主要是和存储层同步的。获取缓存时,判断缓存中是否没有数据。如果数据不存在,则通过CacheLoader的load方法加载。加载成功后,将数据放入缓存中,同时返回数据。readWriteCacheMap缓存过期时间,默认180秒,当服务下线、过期、注册、状态改变时,这个缓存中的数据会被清空。EurekaClient在获取全量或增量数据时,会先从一级缓存中获取;如果一级缓存中不存在,则从二级缓存中获取;如果二级缓存不存在,那么存储层会先把数据同步到缓存中,再从缓存中取出。通过EurekaServer的两级缓存机制,可以非常有效的提升EurekaServer的响应时间。通过数据存储层和缓存层的数据切分,可以根据使用场景提供不同的数据支持。多级缓存的意义这里为什么要设计多级缓存呢?原因很简单,就是当有大规模的服务注册和更新时,如果只修改一个ConcurrentHashMap的数据,势必会因为锁的存在而造成竞争,影响性能。而Eureka是AP模型,只需要满足最终的可用性即可。所以这里使用了多级缓存来实现读写分离。注册方法写的时候直接写到内存注册表,写完表后主动让读写缓存失效。获取注册信息的API首先从只读缓存中获取。不从读写缓存中获取只读缓存。读写缓存不是从内存注册表中取的(不只是取,这个比较复杂)。另外,读写缓存会更新回写只读缓存responseCacheUpdateIntervalMs:readOnlyCacheMap缓存更新定时器间隔,默认30秒responseCacheAutoExpirationInSeconds:readWriteCacheMap缓存过期时间,默认180秒。缓存初始化readWriteCacheMap使用的是LoadingCache对象,它是guava中提供的实现内存缓存的api。创建方法如下LoadingCachecache=CacheBuilder.newBuilder()//缓存池的大小。当缓存项接近这个大小时,Guava开始回收旧的缓存项。maximumSize(10000)//设置时间对象不可读/写访问,对象从内存中删除(在另一个线程中不定期维护)。expireAfterAccess(10,TimeUnit.MINUTES)//移除监听,当缓存项被移除时触发rn){//执行逻辑操作}}).recordStats()//开启GuavaCache的统计功能.build(newCacheLoader(){@OverridepublicObjectload(Stringkey){//获取objectsfromSQLorNoSql}});//CacheLoader类实现自动加载其中CacheLoader用于实现缓存自动加载的功能,当触发readWriteCacheMap时。当使用get(key)方法时,会回调CacheLoader.load方法,根据key在服务注册信息中查找实例数据进行缓存。ResponseCacheImpl(EurekaServerConfigserverConfig,ServerCodecsserverCodecs,AbstractInstanceRegistryregistry){this.serverConfig=serverConfig;this.serverCodecs=serverCodecs;这个.shouldUseReadOnlyResponseCache=serverConfig.shouldUseReadOnlyResponseCache();this.registry=注册表;longresponseCacheUpdateIntervalMs=serverConfig.getResponseCacheUpdateIntervalMs();this.readWriteCacheMap=CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache()).expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(),TimeUnit.SECONDS).removalListener(newRemovalListener(){@OverridepublicvoidonRemoval(RemovalNotificationnotification){KeyremovedKey=notification.getKey();if(removedKey.hasRegions()){KeycloneWithNoRegions=removedKey.cloneWithoutRegions();regionSpecificKeys.remove(cloneWithNoRegions,removedKey);}}}).build(newCacheLoader(){@OverridepublicValueload(Keykey)throwsException{if(key.hasRegions()){KeycloneWithNoRegions=key.cloneWithoutRegions();}regionSpecificKeys.put(cloneWithNoRegions,键);}值value=generatePayload(key);//注意这里返回值;}});而缓存加载是基于generatePayload方法完成的,代码如下privateValuegeneratePayload(Keykey){秒表追踪器=null;尝试{字符串有效负载;switch(key.getEntityType()){caseApplication:booleanisRemoteRegionRequested=key.hasRegions();if(ALL_APPS.equals(key.getName())){if(isRemoteRegionRequested){tracer=serializeAllAppsWithRemoteRegionTimer.start();payload=getPayLoad(key,registry.getApplicationsFromMultipleRegions(key.getRegions()));}else{tracer=serializeAllAppsTimer.start();payload=getPayLoad(key,registry.getApplications());}}elseif(ALL_APPS_DELTA.equals(key.getName())){if(isRemoteRegionRequested){tracer=serializeDeltaAppsWithRemoteRegionTimer.start();versionDeltaWithRegions.incrementAndGet();versionDeltaWithRegionsLegacy.incrementAndGet();payload=getPayLoad(key,registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));}else{tracer=serializeDeltaAppsTimer.start();versionDelta.incrementAndGet();versionDeltaLegacy.incrementAndGet();payload=getPayLoad(key,registry.getApplicationDeltas());}}else{tracer=serializeOneApptimer.start();payload=getPayLoad(key,registry.getApplication(key.getName()));}休息;案例VIP:案例SVIP:tracer=serializeViptimer.start();payload=getPayLoad(key,getApplicationsForVip(key,registry));休息;默认值:logger.error("未识别的实体类型:{}在缓存键中找到。",key.getEntityType());有效载荷=“”;休息;}返回新值(有效载荷);}finally{if(tracer!=null){tracer.stop();}}}这个方法接受一个Key类型的参数,返回一个Value类型。Key中重要的字段有:KeyType,表示payload文本格式,有JSON和XML值。EntityType指示缓存的类型,并具有三个值:Application、VIP和SVIP。entityName,表示缓存的名称,可以是单个应用名称,也可以是ALL_APPS或ALL_APPS_DELTA。Value有一个String类型的负载和一个字节数组,代表gzip压缩的字节。缓存同步在ResponseCacheImpl类的构造和实现中,初始化了一个定时任务。每个ResponseCacheImpl(EurekaServerConfigserverConfig,ServerCodecsserverCodecs,AbstractInstanceRegistryregistry){//省略...if(shouldUseReadOnlyResponseCache){timer.schedule(getCacheUpdateTask(),newDate(((System.currentTimeMillis()/responseCacheUpdateIntervalMs)*responseCacheUpdateIntervalMs)+responseCacheUpdateIntervalMs),responseCacheUpdateIntervalMs);}}默认情况下,每隔30s从readWriteCacheMap更新不同的数据并同步到readOnlyCacheMaprenewtUpdgetCaskencheTask){TimerTask(){@Overridepublicvoidrun(){logger.debug("从响应缓存更新客户端缓存");for(Keykey:readOnlyCacheMap.keySet()){//遍历只读集合if(logger.isDebugEnabled()){logger.debug("从响应缓存更新客户端缓存forkey:{}{}{}{}",key.getEntityType(),key.getName(),key.getVersion(),key.getType());}try{CurrentRequestVersion.set(key.getVersion());值cacheValue=readWriteCacheMap.get(key);ValuecurrentCacheValue=readOnlyCacheMap.get(key);if(cacheValue!=currentCacheValue){//判断差异信息,有差异则更新readOnlyCacheMap.put(key,cacheValue);}}catch(Throwableth){logger.error("Errorwhileupdatetheclientcachefromresponsecacheforkey{}",key.toStringCompact(),th);}finally{CurrentRequestVersion.remove();}}}};}AbstractInstanceRegistry中的缓存失效。registerthis方法中,当保存服务信息时,会调用invalidateCache使缓存失效publicvoidregister(InstanceInforegistrant,intleaseDuration,booleanisReplication){//....invalidateCache(registrant.getAppName(),registrant.getVIPAddress(),registrant.getSecureVipAddress());//....}最终调用ResponseCacheImpl.invalidate方法,完成绑定存在的失效机制publicvoidinvalidate(Key...keys){for(Keykey:keys){logger.debug("Invalidatingtheresponsecachekey:{}{}{}{},{}",key.getEntityType(),key.getName(),key.getVersion(),key.getType(),key.getEurekaAccept());readWriteCacheMap.invalidate(key);集合keysWithRegions=regionSpecificKeys.get(key);if(null!=keysWithRegions&&!keysWithRegions.isEmpty()){for(KeykeysWithRegion:keysWithRegions){logger.debug("使响应缓存键无效:{}{}{}{}{}",key.getEntityType(),key.getName(),key.getVersion(),key.getType(),key.getEurekaAccept());readWriteCacheMap.invalidate(keysWithRegion);}}}}版权声明:除特别注明外,本博客所有文章均采用CCBY-NC-SA4.0许可协议转载。转载请注明,麦客带你学建筑!如果本文对您有帮助,请给个关注和点赞。您的坚持是我不断创作的动力。欢迎关注同名微信公众号获取更多技术干货!