Eureka源码(四)— 注册表抓取、多级缓存
2022-04-18 16:11:44 1 举报
Eureka源码 — 注册表抓取、多级缓存
作者其他创作
大纲/内容
ConcurrentHashMap 构建一级只读缓存 readOnlyCacheMap 会通过定时任务 TimerTask 从 LoadingCache 构建二级读写缓存 readWriteCacheMap 进行比对更新(每 30 秒执行一次,对比一二级缓存的数据,如果不一致,就用二级缓存的数据覆盖一级缓存的,回写到一级缓存中去)
Server端 获取注册表
3.initialize()
优点:1、尽可能保证内存注册表数据不会出现频繁的读写冲突2、保证对eureka服务端的请求读取的都是内存,性能高。在以后的开发工作中,面对频繁的读写资源争抢的情况,也可以考虑采用多级缓存这种方案来设计系统。
增量获取注册表流程
开始
Server端 服务注册
注册中心判断当前访问是否可以进行
定时过期
从上面的时序图当中可以看到,在 Eureka Server 进行初始化的时候会初始化注册中心PeerAwareInstanceRegistryImpl 这个对象会调用它的 init 方法并最终调用 PeerAwareInstanceRegistryImpl#initializedResponseCache 初始化注册信息列表的缓存。
如果格式是gzip,调用responseCache.getGZIP(cacheKey)获取
只读缓存无数据则从读写缓存中读取
Registry.getApplicationDeltasFromMultipleRegions()方法
通过 Guava 的 LoadingCache 构建二级读写缓存 readWriteCacheMap二级读写缓存会在 180 秒钟后就会过期
ApplicationsResource的getContainers()方法
抓取到的delta的注册表,就会跟本地的注册表进行合并,完成服务实例的增删改
增量获取走的是if else判断中的ALL_APPS_DELTA的逻辑全量获取走的是判断中的ALL_APPS的逻辑
hash值对比
ApplicationResource类getContainers方法
获取逻辑和全量获取注册表类似,唯一不同的是在readWriteCacheMap的从注册表获取数据不一样
调用jersey client,发送http请求获取全量注册表接口例如:http://localhost:8080/v2/apps GET请求
getAndStoreFullRegistry();
Applications applications = getApplications();
readWriteCacheMap.get(key)设置180s会自动过期并重新加载缓存
responseCache.get(cacheKey)
亮点:(1)增量数据的设计思路:如果要保存一份增量的最新变更数据,可以基于LinkedQueue,将最新变更的数据放入这个queue中,然后后台定时任务,每隔一定时间,将在队列中存放超过一定时间的数据拿掉,保持这个队列中就是最近几分钟内的变更的增量数据(2)数据同步的hash值比对机制:如果要在两个地方,一个分布式系统里,进行数据的同步,可以采用Hash值的思想,从一个地方的数据计算一个hash值,到另外一个地方,计算一个hash值,保证两个hash值是一样的,确保这个数据传输过程中,没有出什么问题
eureka client第一次启动的时候,必须从eureka server端一次性抓取全量的注册表的信息过来,在本地进行缓存每隔30秒从eureka server抓取增量的注册表信息,跟本地缓存进行合并如果配置了要抓取注册表的信息,那么就会在启动的时候进行全量的注册表的抓取过程:
readWriteCacheMap中获取数据的generatePayload(key)方法
eureka server 注册表多级缓存机制(只读缓存+读写缓存)
DiscoveryClient构造器中初始化调度任务initScheduledTasks()
updateDelta(delta)
结束
eureka client第一次启动时全量抓取注册表
通过 ConcurrentHashMap 构建一级只读缓存 readOnlyCacheMap
CacheRefreshThread里面调用fetchRegistry(remoteRegionsModified)方法中的getAndUpdateDelta(applications)调用Server端的增量获取注册表接口
对更新完合并完以后的注册表,会计算一个hash值:1、delta的注册表带了一个eureka server端的全量注册表的hash值;2、此时会将eureka client端的合并完的注册表的hash值,跟eureka server端的全量注册表的hash值进行一个比对;3、如果不一样,说明本地注册表跟server端不一样,此时就会重新从eureka server拉取全量的注册表到本地来更新到缓存里去
6.new ResponseCacheImpl() 初始化 构造器
如果没有指定 regions 信息的话,这个值默认会是空字符串然后EurekaMonitors.GET_ALL 监控指标会 +1
默认是30秒一次,逻辑是处理服务实例的变更记录,是否在队列里停留了超过180s(3分钟)如果超过了3分钟,就会从队列里将这个服务实例变更记录给移除掉。也就是recentlyChangedQueue队列,只会保留最近3分钟的服务实例变更记录
缓存的Key(ALL_APPS_DELTA)
普通获取responseCache.get(cacheKey)
eureka client每次30秒,抓取最近3分钟内发生过变化的服务实例
有服务实例注册、下线、故障,要调用这个服务的其他服务,可能会过30秒之后才能感知到因为这里在获取服务注册表的时候,有一个多级缓存的机制,最多是30秒才会去更新缓存
判断是否是GZIP格式
缓存中读取数据
DiscoveryClient初始化fetchRegistry(false);
4.init()
被动过期
readOnlyCacheMap.get(key)
ApplicationResource类addInstance方法
如果有新的实例注册,极端情况下难道要等读写缓存的key,180s后过期,才能获取到最新的服务列表数据吗?即在实例有变化的时候,服务端又是如何实现的?在接受客户端注册的时候,服务端会将读写缓存的key清掉,30s后只读缓存从读写缓存拉取数据的时候,该服务列表获取到的是最新的数据。如果客户端下线,同样地,读写缓存也会被清除掉。所以极端情况,最长30s后,客户端才能获取到最新的服务列表。
PeerAwareInstanceRegistryImpl
先从只读缓存中读取数据
先了解缓存是什么时候开始初始化的
eureka server 处理client抓取注册表请求
全量拉取注册表入口
主动过期服务实例注册信息有变化时,就更新缓存
2.initEurekaServerContext()
将这个注册请求转发给ApplicationResource的getContainerDifferential方法
全量获取注册表流程(包含大致服务注册流程,因为服务注册和获取注册表有关联部分)
1.contextInitialized()
generatePayload(key)
5.initializedResponseCache()
定时任务每隔30秒
主要是通过当前系统时间与启动时间的关系或者判断远程配置中心的数据是否可读PeerAwareInstanceRegistryImpl#shouldAllowAccess(boolean)
获取本地的Applications缓存Applications是所有的服务,Applicaiton是一个服务Application中包含了他自己的所有的InstanceInfo,就是一个服务包含了自己的所有的服务实例
ResponseCacheImpl构造方法中初始化一个默认30s定时任务定时对readOnlyCacheMap和readWriteCacheMap中的数据进行一个比对,如果两块数据是不一致,就将读写缓存数据更新到只读缓存
将获取的结果放入本地缓存中
DefaultEurekaServerContext
server端
invalidateCache()
创建缓存key通过ALL_APPS构建key
底层会从一个font color=\"#ff3333\
注册表最主要的是多级缓存的处理,这部分大致了解,没必要特别深究
在 ResponseCacheImpl 中还提供了 invalidate 方法进行手动过期,当 Eureka Server 发生了服务注册、下线、故障会自动过期该缓存
获取服务实例变更的实例里面有recentlyChangedQueue队列,存放最近有变化的服务实例,比如新注册、下线的等PeerAwareInstanceRegistryImpl在初始化的时候,会先构造父类AbstractInstanceRegistry,里面有一个定时调度的任务deltaRetentionTimer
为什么设计三层缓存?与主从数据库的读写分离同理,数据库的读写分离是为了分摊主数据库服务器的读写压力。而eureka所设计的缓存级别无疑也是为了读写分离,因为在写的时候,如ConcurrentHashmap会持有桶节点对象的锁,阻塞同一个桶的读写线程。这样设计的话,线程在写的时候,并不会影响读操作,避免了争抢资源所带来的压力。
client端
根据http://localhost:8080/v2/apps/,get请求进入获取全量注册表的方法
三级缓存缓存结构如何保证最终一致性?1、从只读map中获取key对应的值,如果只读map没有value值的时候,会从读写缓存里面获取,而读写缓存180s后过期,所以,它又会从本地注册表中获取到最新的实例信息。2、只读map中会每30s遍历,将读写map里面的key赋值到只读map中。
如果读写缓存无数据,则触发load方法从内存中读取数据
返回最近3分钟内发生过变化的服务实例
EurekaBootStrap
0 条评论
下一页