图文详述Eureka的缓存机制/三级缓存

网友投稿 229 2022-08-24

图文详述Eureka的缓存机制/三级缓存

前言

1、为什么说Eureka是CAP理论中的AP?

从CAP理论看,Eureka是一个AP系统,其优先保证可用性(A)和分区容错性§,不保证强一致性©,但能做到最终一致性。

因为只要集群中​​任意一个实例不​​出现问题,Eureka服务就是可用的;即Eureka Client 在向某个 Eureka Server 注册时,如果发现连接失败,则会自动切换至其它节点;另外Eureka集群中没有主从的概念,各个节点都是平等的,节点间采用Replicate异步的方式来同步数据;

也正因为Eureka 本身不保证数据的强一致性,所以在架构又设计了很多缓存。

2、为什么要设计那么多缓存(三级缓存)?

这个和MySQL用主从做读写分离很相似,数据库层面的读写分离是为了分摊主数据库的读压力;而Eureka的三级缓存也是为了做读写分离,使写操作不阻塞读操作。

因为在​​写​​的时候,线程会持有ConcurrentHashmap相应Hash桶节点对象的锁,阻塞同一个Hash桶的其他读线程。这样可以有效的降低读写并发,避免读写读写争抢资源所带来的压力。

那么加了那么多缓存,如何保证缓存数据的最终一致性?

我们下面就详细聊一下Eureka是如何做的。

缓存机制

Eureka Server中有三个变量用来保存服务注册信息,分别是:​​registry​​​、​​readWriteCacheMap​​​、​​readOnlyCacheMap​​;默认情况下定时任务每30s将二级缓存readWriteCacheMap同步至三级缓存readOnlyCacheMap;

1、Eureka Server每60s清理超过90s * 2(官方彩蛋)未续约的节点; 2、Eureka Client每30s从readOnlyCacheMap更新服务注册信息; 3、UI界面则从registry获取最新的服务注册信息。

1、三级缓存分别是什么?

缓存

缓存类型

所处类

概述

registry

一级缓存

ConcurrentHashMap

AbstractInstanceRegistry

实时更新,又名​​注册表​​,UI界面从这里获取服务注册信息;

readWriteCacheMap

二级缓存

Guava Cache(LoadingCache)

ResponseCacheImpl

实时更新,缓存时间180秒;

readOnlyCacheMap

三级缓存

ConcurrentHashMap

ResponseCacheImpl

周期更新,默认每30s从二级缓存readWriteCacheMap中同步数据更新;

Eureka Client默认从这里获取服务注册信息,可配为直接从readWriteCacheMap获取

二级缓存又称读写缓存、三级缓存又称只读缓存;

1)Eureka Server缓存相关配置

配置名

默认值

概述

eureka.server.useReadOnlyResponseCache

true

Client从readOnlyCacheMap更新数据,false则跳过readOnlyCacheMap直接从readWriteCacheMap更新

eureka.server.responsecCacheUpdateIntervalMs

30000

readWriteCacheMap更新至readOnlyCacheMap的周期,默认30s

2、缓存之间的数据同步

​​AbstractInstanceRegistry​​​类中的​​registry​​​字段为注册表、并与保存一级缓存(实时最新数据);​​ResponseCacheImpl​​​类中的​​readWriteCacheMap​​​字段和​​readOnlyCacheMap​​字段分别表示二级缓存和三级缓存;下面我们就围绕这两个类、三个字段的数据同步展开讨论。

1)注册一个服务实例

Eureka Server中做的操作:

向注册表registry中写入服务实例信息,并使得二级缓存失效;

Eureka client第一次向Eureka Server注册服务或者发送心跳续约时,会进去到Eureka Serve中​​ApplicationResource#addInstance()​​方法中:

最终进入到​​AbstractInstanceRegistry#register()​​​方法中,往其​​registry​​字段中添加服务注册信息:

这里以服务注册为例,我们知道了registry的数据来源;注意,在做服务注册的最后会将二级缓存清空/失效;

下面我们接着来看一下Eureka的二级缓存和三级缓存是如何运作的?

2)二级/三级缓存什么时候初始化?

Eureka Server启动的时候会根据SpringBoot自动装配的特性,初始化​​EurekaServerContext​​​接口的实现类​​DefaultEurekaServerContext​​​;在​​DefaultEurekaServerContext​​​初始化时会执行构造器后置逻辑​​initialize()​​,其中会初始化注册中心;

接着进入到​​PeerAwareInstanceRegistry​​​接口的实现类​​PeerAwareInstanceRegistryImpl​​​#init()方法中,其中会通过调用​​initializedResponseCache()​​方法初始化二级/三级缓存;

3)二级/三级缓存初始化都做了什么?

这里可以看到initializedResponseCache()方法中直接new了一个​​ResponseCacheImpl​​类;

我们接着进入到ResponseCacheImpl类中,看一下它的构造函数:

总的来说,在初始化ResponseCacheImpl类时:

会设置初始化二级缓存​​readWriteCacheMap​​(过期时间180s),设置二级缓存往三级缓存​​readOnlyCacheMap​​同步的时间间隔(默认30s)。设置是否使用三级缓存(默认使用),如果使用则启动一个定时任务,默认每隔30s从二级缓存中同步数据到三级缓存(只更新三级缓存中已存在的key);

4、发现/寻找一个服务

针对Eureka Client和UI界面,他们读取的服务注册信息的方式略有不同:

针对Eureka Client:

1、如果使用只读缓存(三级缓存)<默认使用>,则先从只读缓存中获取;如果获取不到,则从读写缓存(二级缓存)中获取,并将数据缓存到只读缓存; 2、不使用只读缓存,则直接从读写缓存中获取;如果获取不到则触发guava的回调函数从注册表registry中同步数据(即从一级缓存 – 注册表registry中取)

而对于UI界面,则是实时从一级缓存(注册表registry)中取。

对于Eureka Client无论是获取一个Application的信息(入口为​​ApplicationResource#getApplication()​​​)还是获取所有Application的信息(入口为​​ApplicationsResource#getContainers()​​)都会进入到ResponseCacheImple#get(Key key)方法,然后进过此路径《get(key, shouldUseReadOnlyResponseCache) --> getValue(final Key key, boolean useReadOnlyCache)》最终走到真正从缓存中读取数据的逻辑:

总结

1、Eureka Server 在接收Eureka Client注册的时候,会将读写缓存(二级缓存)清空; 2、Eureka Server启动时会做两件事:会初始化读写缓存(​​二级缓存​​),从注册表registry(一级缓存)中实时加载数据,默认180s过期;判定是否使用只读缓存(三级缓存),默认开启;如果使用则开启一个定时任务,默认每30s做一次读写缓存到只读缓存的数据同步;3、Eureka Client获取服务信息时,默认先从只读缓存获取;获取不到再从读写缓存中获取,并将数据缓存到只读缓存;获取不到,再触发guava的回调函数从注册表中同步(即从一级缓存 – 注册表registry中取)。

1、多级缓存带来的好处?

尽可能的避免服务注册出现频繁的读写冲突,写阻塞读;提高Eureka Server服务的读写性能。

面对频繁读写资源争抢、写阻塞读等情况,我可以考虑借鉴Eureka的多级缓存方案做读写分离。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【云原生&微服务>SCG网关篇十四】Spring Cloud Gateway如何实现负载均衡
下一篇:我们的营销面对?(我们在营销什么)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~