Eureka Server缓存机制概述
Eureka是Netflix开源的服务发现框架,它具有高可用、可扩展、易于部署等优点,被广泛应用于微服务架构中。其中一个重要的组件就是Eureka Server,它负责维护服务注册表,以及向客户端提供服务注册信息。
在Eureka Server中,缓存机制是一个非常重要的优化点,它可以显著提升服务注册表的性能和可靠性。本文将深入剖析Eureka Server的缓存机制,并通过源码分析,探讨其实现原理和优化策略。
三级缓存
一级缓存:只读缓存
readOnlyCacheMap
,数据结构 ConcurrentHashMap。相当于数据库。
二级缓存:读写缓存
readOnlyCacheMap
,Guava Cache。相当于 Redis 主从架构中主节点,既可以进行读也可以进行写。
三级缓存:本地注册表
registry
,数据结构 ConcurentHashMap。相当于 Redis 主从架构的从节点,只负责读。
多级缓存的意义
这里为什么要设计多级缓存呢?原因很简单,就是当存在大规模的服务注册和更新时,如果只是修改一个ConcurrentHashMap数据,那么势必因为锁的存在导致竞争,影响性能。
而Eureka又是AP模型,只需要满足最终可用就行。所以它在这里用到多级缓存来实现读写分离。注册方法写的时候直接写内存注册表,写完表之后主动失效读写缓存。
获取注册信息接口先从只读缓存取,只读缓存没有再去读写缓存取,读写缓存没有再去内存注册表里取(不只是取,此处较复杂)。并且,读写缓存会更新回写只读缓存
- responseCacheUpdateIntervalMs :readOnlyCacheMap 缓存更新的定时器时间间隔,默认为30秒
- responseCacheAutoExpirationInSeconds : readWriteCacheMap 缓存过期时间,默认为 180 秒 。
Eureka Server内存缓存实现原理
Eureka Server的内存缓存机制是通过EurekaRegistry类实现的。EurekaRegistry类是Eureka Server中最核心的组件之一,它负责维护服务注册表,并提供服务注册、注销、查询等接口。在EurekaRegistry类中,内存缓存主要通过以下两个类实现:ReadOnlyClusterRegistry和PeerAwareInstanceRegistry。
ReadOnlyClusterRegistry类是EurekaRegistry的一个内部类,它主要用于向客户端提供服务注册信息。当客户端向Eureka Server发起查询请求时,Eureka Server会从ReadOnlyClusterRegistry中获取服务注册信息,并返回给客户端。同时,ReadOnlyClusterRegistry还负责监控服务注册表的变化,并及时更新缓存中的服务注册信息。
PeerAwareInstanceRegistry类则是EurekaRegistry的另一个内部类,它主要用于维护服务注册表。当Eureka Server收到客户端的服务注册请求时,PeerAwareInstanceRegistry会将服务注册信息添加到内存缓存中。当客户端发起注销请求时,PeerAwareInstanceRegistry会将服务注册信息从内存缓存中删除。同时,PeerAwareInstanceRegistry还负责定时从其他Eureka Server节点获取服务注册信息,并更新本地内存缓存。
PeerAwareInstanceRegistry的内存缓存是通过ConcurrentHashMap实现的。ConcurrentHashMap是Java中线程安全的HashMap实现,它可以支持多个线程同时对同一个HashMap进行操作,从而保证线程安全。在PeerAwareInstanceRegistry中,ConcurrentHashMap主要用于存储服务注册信息。当服务注册信息发生变化时,PeerAwareInstanceRegistry会通过ConcurrentHashMap的put、remove等方法更新内存缓存中的服务注册信息。
下面是一段示例代码,演示了如何使用Eureka Server的内存缓存机制:
public class EurekaServerDemo {
public static void main(String[] args) throws Exception {
// 创建Eureka Server实例
EurekaServerContext serverContext = EurekaServerContextHolder.getInstance().getServerContext();
EurekaServerConfig serverConfig = serverContext.getServerConfig();
EurekaServer eurekaServer = new EurekaServer(serverConfig);
// 启动Eureka Server
eurekaServer.start();
// 获取Eureka Server的内存缓存
PeerAwareInstanceRegistry registry = serverContext.getRegistry();
// 添加服务注册信息
InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()
.setAppName("demo")
.setInstanceId("demo-1")
.setHostName("localhost")
.setIPAddr("127.0.0.1")
.setPort(8080)
.build();
registry.register(instanceInfo);
// 查询服务注册信息
Applications applications = registry.getApplications();
Application application = applications.getRegisteredApplications("demo");
InstanceInfo registeredInstance = application.getInstances().get(0);
// 输出服务注册信息
System.out.println("Registered instance: " + registeredInstance);
// 关闭Eureka Server
eurekaServer.shutdown();
}
}
Eureka客户端缓存实现原理
Eureka客户端的缓存机制是通过DiscoveryClient类实现的。DiscoveryClient类是Eureka客户端中最核心的组件之一,它负责向Eureka Server发起查询请求,并将获取到的服务注册信息缓存到内存中,以便后续快速访问。
在DiscoveryClient中,内存缓存主要通过以下两个类实现:Applications和InstanceInfo。
Applications类是Eureka客户端缓存的服务注册表,它包含了所有已注册的服务信息。当DiscoveryClient向Eureka Server发起查询请求时,Eureka Server会返回完整的服务注册表信息,并由DiscoveryClient将其缓存到Applications中。同时,DiscoveryClient还会定时从Eureka Server获取服务注册表的增量信息,并更新Applications中的缓存。
InstanceInfo类则是服务注册信息的抽象表示,它包含了服务的基本信息,例如应用名、实例ID、主机名、IP地址、端口号等。当DiscoveryClient从Eureka Server获取服务注册信息时,会将其转换成InstanceInfo,并缓存到内存中。此外,InstanceInfo还包含了服务的健康状态、元数据信息等,这些信息也会被缓存到内存中,以便后续快速访问。
下面是一段示例代码,演示了如何使用Eureka客户端的缓存机制:
public class EurekaClientDemo {
public static void main(String[] args) throws Exception {
// 创建Eureka客户端实例
DiscoveryClientConfig clientConfig = new DefaultEurekaClientConfig();
EurekaClient eurekaClient = new DiscoveryClient(new MyInstanceConfig(), clientConfig);
// 获取服务注册信息
List<ServiceInstance> instances = eurekaClient.getInstancesById("demo");
ServiceInstance instance = instances.get(0);
// 输出服务注册信息
System.out.println("Service instance: " + instance);
// 关闭Eureka客户端
eurekaClient.shutdown();
}
private static class MyInstanceConfig extends MyDataCenterInstanceConfig {
@Override
public String getAppname() {
return "demo-client";
}
}
}
Eureka缓存优化策略
Eureka缓存机制的性能和可靠性对于微服务架构的稳定运行至关重要。为了提升缓存的性能和可靠性,Eureka Server采用了以下优化策略:
- 客户端缓存时间:Eureka客户端会将从Eureka Server获取的服务注册表信息缓存在本地内存中。为了避免本地缓存的信息过期,Eureka客户端会定时从Eureka Server获取服务注册表的增量信息,以更新本地缓存。这个定时更新的时间间隔可以通过配置项eureka.client.registryFetchIntervalSeconds来设置,默认为30秒。
- 服务端缓存时间:Eureka Server会将服务注册信息缓存在内存中,以便快速响应客户端的查询请求。为了避免缓存的信息过期,Eureka Server会定时清理过期的缓存信息,并从其他Eureka Server节点获取最新的服务注册信息。这个定时清理的时间间隔可以通过配置项eureka.server.responseCacheAutoExpirationInSeconds来设置,默认为180秒。
- 客户端缓存大小:Eureka客户端缓存的大小对于性能和可靠性都有很大的影响。如果缓存太小,会影响客户端的查询性能;如果缓存太大,会占用过多的内存资源。Eureka客户端的缓存大小可以通过配置项eureka.client.fetchRegistry=true和eureka.client.registryRefreshSingleVipAddress来设置。
- 服务端缓存大小:Eureka Server缓存的大小也对于性能和可靠性有很大的影响。如果缓存太小,会影响服务端的响应性能;如果缓存太大,会占用过多的内存资源。Eureka Server的缓存大小可以通过配置项eureka.server.responseCacheMaxSize来设置,默认为1000。
缓存同步
在
ResponseCacheImpl
这个类的构造实现中,初始化了一个定时任务,这个定时任务每个
ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
//省略...
if (shouldUseReadOnlyResponseCache) {
timer.schedule(getCacheUpdateTask(),
new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
+ responseCacheUpdateIntervalMs),
responseCacheUpdateIntervalMs);
}
}
默认每30s从readWriteCacheMap更新有差异的数据同步到readOnlyCacheMap中
private TimerTask getCacheUpdateTask() {
return new TimerTask() {
@Override
public void run() {
logger.debug("Updating the client cache from response cache");
for (Key key : readOnlyCacheMap.keySet()) { //遍历只读集合
if (logger.isDebugEnabled()) {
logger.debug("Updating the client cache from response cache for key : {} {} {} {}",
key.getEntityType(), key.getName(), key.getVersion(), key.getType());
}
try {
CurrentRequestVersion.set(key.getVersion());
Value cacheValue = readWriteCacheMap.get(key);
Value currentCacheValue = readOnlyCacheMap.get(key);
if (cacheValue != currentCacheValue) { //判断差异信息,如果有差异,则更新
readOnlyCacheMap.put(key, cacheValue);
}
} catch (Throwable th) {
logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), th);
} finally {
CurrentRequestVersion.remove();
}
}
}
};
}
缓存失效
在AbstractInstanceRegistry.register这个方法中,当完成服务信息保存后,会调用
invalidateCache
失效缓存
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
//....
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
//....
}
最终调用ResponseCacheImpl.invalidate方法,完成缓存的失效机制
public void invalidate(Key... keys) {
for (Key key : keys) {
logger.debug("Invalidating the response cache key : {} {} {} {}, {}",
key.getEntityType(), key.getName(), key.getVersion(), key.getType(), key.getEurekaAccept());
readWriteCacheMap.invalidate(key);
Collection<Key> keysWithRegions = regionSpecificKeys.get(key);
if (null != keysWithRegions && !keysWithRegions.isEmpty()) {
for (Key keysWithRegion : keysWithRegions) {
logger.debug("Invalidating the response cache key : {} {} {} {} {}",
key.getEntityType(), key.getName(), key.getVersion(), key.getType(), key.getEurekaAccept());
readWriteCacheMap.invalidate(keysWithRegion);
}
}
}
}
版权归原作者 创作小达人 所有, 如有侵权,请联系我们删除。