当前位置: 首页 > news >正文

Eureka 多层缓存机制详解

一、Eureka 多层缓存机制详解

客户端请求↓
[第一层] 只读缓存 (ReadOnlyCache)30秒同步一次↓
[第二层] 读写缓存 (ReadWriteCache) ← 实时更新  ↓
[第三层] 注册表内存 (Registry) ← 真实数据存储

具体工作流程示例,场景设置
服务实例:order-service-1, payment-service-1, user-service-1.
客户端:需要频繁拉取服务注册表进行服务发现

步骤1:客户端首次拉取注册表(全量)

// 客户端第一次请求服务列表
@RestController
public class OrderController {public void callPaymentService() {// 1. 客户端向Eureka Server发起请求List<ServiceInstance> instances = discoveryClient.getInstances("payment-service");// Eureka Server内部处理流程://   a. 检查只读缓存 → 未命中(首次请求)//   b. 检查读写缓存 → 未命中  //   c. 查询真实注册表 → 命中,返回50个payment-service实例//   d. 写入读写缓存 + 只读缓存String result = restTemplate.getForObject("http://payment-service/pay", String.class);}
}

数据流向:

客户端请求 → 只读缓存(未命中) → 读写缓存(未命中) → 真实注册表(命中)↓
客户端响应 ← 只读缓存(已填充) ← 读写缓存(已填充) ← 返回结果

步骤2:30秒内的重复请求(缓存命中)

// 在接下来的30秒内,其他客户端的相同请求
public class LoadBalancer {public Server chooseServer() {// 这些请求直接命中只读缓存,无需访问底层注册表// 性能提升:减少数据库压力90%+// 假设每秒133次请求(如图中计算):// - 缓存命中率:99% → 131次/秒从缓存返回// - 真实查询:仅2次/秒访问注册表}
}

步骤3:服务实例注册(缓存失效)

// 当新的payment-service-51实例注册时
@Component
public class EurekaRegistry {public void register(InstanceInfo instance) {// 1. 更新真实注册表(内存操作)registry.put(instance.getId(), instance);// 2. 立即失效读写缓存中的相关条目readWriteCache.invalidate("payment-service");// 3. 只读缓存暂时不变(保持旧数据30秒)//    这保证了高并发读取性能,牺牲了短暂的数据一致性log.info("新实例注册:{}, 读写缓存已失效", instance.getId());}
}

缓存状态变化:

真实注册表: [payment-service-1, ..., payment-service-50, payment-service-51] ✅ 最新
读写缓存:   [已失效] ❌
只读缓存:   [payment-service-1, ..., payment-service-50](30秒内保持旧数据)

步骤4:缓存同步(定时任务)

// Eureka Server的缓存同步任务
@Scheduled(fixedRate = 30000) // 每30秒执行一次
public class CacheSyncTask {public void syncReadOnlyCache() {// 1. 获取读写缓存中的最新数据Map<String, Object> latestData = readWriteCache.getAll();// 2. 批量更新只读缓存readOnlyCache.clear();readOnlyCache.putAll(latestData);// 3. 记录同步日志log.info("只读缓存同步完成,实例数量: {}", latestData.size());}
}

同步后的状态:

真实注册表: [payment-service-1, ..., payment-service-51] ✅
读写缓存:   [payment-service-1, ..., payment-service-51] ✅  
只读缓存:   [payment-service-1, ..., payment-service-51](已同步)

二、增量心跳机制,客户端不是每次全量拉取注册表

public class DiscoveryClient {public void getDelta() {// 首次全量拉取,后续只获取变更部分if (lastUpdateTimestamp == 0) {return fullRegistry; // 全量注册表} else {return getApplicationsSince(lastUpdateTimestamp); // 增量变更}}
}

三、Eureka高吞吐的原理

1、客户端缓存:Eureka客户端会缓存服务注册表信息。客户端(服务实例)并不每次需要服务发现时都去注册中心拉取,而是定期(默认30秒)从Eureka服务器拉取注册表信息并缓存到本地。这样,大部分服务发现请求可以直接从本地缓存获取,减轻了Eureka服务器的压力。
2、增量更新和压缩:Eureka服务器会存储增量的服务注册信息,并且支持压缩传输。客户端在拉取注册表信息时,可以使用增量更新,只获取发生变化的部分,减少网络传输的数据量。
3、多级缓存机制:Eureka服务器内部使用了多级缓存来提升读性能。包括:
只读缓存:Eureka服务器维护一个只读的缓存,定时从注册表中更新数据(默认30秒一次)。客户端拉取注册表时,直接返回只读缓存中的数据,这样避免了每次请求都去访问注册表,提高了读取速度。
读写缓存:Eureka服务器还维护一个读写缓存,当有服务注册、续约、注销等写操作时,会更新读写缓存,并定时同步到只读缓存。

4、集群部署和负载均衡:Eureka服务器通常以集群方式部署,多个Eureka节点之间通过异步复制数据来保证高可用。客户端可以配置多个Eureka服务器地址,实现负载均衡和故障转移。
5、自我保护机制:当网络分区或大规模故障时,Eureka进入自我保护模式,不再剔除失效的服务实例。这样可以避免在网络波动时频繁更新注册表,减少了写操作,保证了整个系统的稳定性。
6、异步操作:Eureka的很多操作都是异步的,比如客户端的注册、续约,以及服务器节点之间的数据复制等。通过异步化,可以避免阻塞主线程,提高吞吐量。
7、高效的序列化:Eureka使用JSON和XML等格式进行通信,但内部使用高效的数据结构,并且通过优化序列化/反序列化过程来提升性能。
8、心跳机制:服务实例通过心跳来维持注册,而不是每次重新注册,这减少了写操作。心跳是轻量级的操作,通常只包含实例ID等少量信息。
9、注册表的结构优化:Eureka的注册表使用ConcurrentHashMap等并发数据结构,保证读写的高性能。
10、限流和降级:Eureka服务器具备一定的限流能力,防止过多请求压垮服务器。在压力大时,可以通过降级策略来保证核心功能的可用性。

四、只读和读写缓存的总结

// 多级缓存机制(核心优化), Eureka Server 缓存架构伪代码
public class ResponseCache {private final ConcurrentMap<Key, Value> readOnlyCacheMap;  // 只读缓存private final ConcurrentMap<Key, Value> readWriteCacheMap; // 读写缓存// 客户端拉取注册表时,直接从只读缓存获取public String getPayload(Key key) {Value payload = readOnlyCacheMap.get(key);if (payload == null) {payload = readWriteCacheMap.get(key);if (payload != null) {readOnlyCacheMap.put(key, payload); // 填充只读缓存}}return payload;}// 注册表变更时,先失效读写缓存,定时同步到只读缓存public void invalidate(Key key) {readWriteCacheMap.remove(key); // 立即失效读写缓存// 只读缓存通过定时任务(30秒)更新,避免频繁同步开销}
}

缓存更新策略:
读写缓存:注册表变更时立即失效
只读缓存:定时30秒同步一次(可配置)
效果:99%的读请求直接命中缓存,极大降低数据库压力

问题:“读写缓存”的数据是什么时候更新的?

第一种更新"读写缓存"的场景

核心原理:懒加载(Lazy Loading),读写缓存采用按需加载策略,不是在失效时立即更新,而是在下次查询时才从真实注册表加载最新数据。

// Eureka Server 读写缓存的实现逻辑
public class ReadWriteCacheImpl {private final ConcurrentMap<Key, Value> cache = new ConcurrentHashMap<>();private final LoadingCache<Key, Value> loadingCache; // 加载缓存public Value get(Key key) {// 1. 先检查缓存中是否存在Value cachedValue = cache.get(key);if (cachedValue != null) {return cachedValue; // 缓存命中,直接返回}// 2. 缓存未命中(可能是首次查询或刚被失效)//    从真实注册表加载最新数据Value freshValue = loadFromRegistry(key);// 3. ⭐⭐⭐ 关键步骤:将最新数据写入读写缓存cache.put(key, freshValue);return freshValue;}public void invalidate(Key key) {// 只是移除缓存条目,不立即加载新数据cache.remove(key);// 注意:这里没有调用 loadFromRegistry()!}
}

步骤3的详细补充(您提到的缺失环节)

// 完整的注册和缓存更新流程
@Component
public class EurekaRegistryService {public void registerInstance(InstanceInfo newInstance) {// 步骤3.1: 更新真实注册表(内存操作)registry.put(newInstance.getId(), newInstance);log.info("真实注册表已更新,实例数: {}", registry.size());// 步骤3.2: ⭐ 立即失效读写缓存中的相关条目String appName = newInstance.getAppName();readWriteCache.invalidate(appName); // 移除缓存,但不加载新数据log.info("读写缓存已失效,等待下次查询时重新加载");// 步骤3.3: ⭐⭐⭐ 关键问题:什么时候写入最新数据?// 答案:在下一次客户端查询时!}// 当客户端查询服务列表时触发数据加载public Applications getApplications(String appName) {// 触发读写缓存的懒加载机制return readWriteCache.get(appName, () -> {// 这个lambda表达式在缓存未命中时执行// ⭐ 在这里从真实注册表加载最新数据并写入缓存Applications freshData = loadLatestFromRegistry(appName);log.info("读写缓存重新加载,实例数: {}", freshData.size());return freshData;});}
}

第二种更新“读写缓存”的场景

Eureka Server 缓存同步任务的实际实现,完整流程

@Scheduled(fixedRate = 30000) // 每30秒执行一次
public class CacheSyncTask {public void syncReadOnlyCache() {Map<Key, Value> latestData = new HashMap<>();// 1. 遍历所有应用程序,尝试从读写缓存获取数据for (String appName : getAllApplicationNames()) {Key key = new Key(Key.EntityType.Application, appName);// 2. ⭐⭐⭐ 关键:如果读写缓存为空,会触发懒加载Value data = readWriteCache.get(key);if (data != null) {// 缓存中有数据,直接使用latestData.put(key, data);} else {// 3. ⭐⭐⭐ 容错机制:缓存为空时,直接从真实注册表加载log.warn("读写缓存为空,从真实注册表直接加载: {}", appName);Value freshData = loadDirectlyFromRegistry(appName);latestData.put(key, freshData);// 4. 同时重新填充读写缓存(避免下次还是空)readWriteCache.put(key, freshData);}}// 5. 批量更新只读缓存readOnlyCache.clear();readOnlyCache.putAll(latestData);log.info("缓存同步完成,处理应用数: {}", latestData.size());}
}

也就是说,读写缓存被清理后,有定时任务缺失查询操作,对读写缓存的数据进行完善。

http://www.dtcms.com/a/512748.html

相关文章:

  • HarmonyOS 5 鸿蒙Context上下文机制与资源管理详解
  • wordpress播放器插件杭州百度seo
  • 网站维护费用2021国内军事新闻大事件
  • vue 中 directive 作用,使用场景和使用示例
  • Orleans 与 Kubernetes 完整集成指南
  • 珠海网站建设网有心学做网站
  • 网站建设 教学大纲wordpress 文章查询
  • 推广方案设计台州seo优化公司
  • 新浪微博 搭建网站建立网站的方案
  • 用易语言做抢购网站软件下载云搜索神器
  • C#上位机软件:2.5 体验CLR实现多语言混合编程
  • 网页站点江苏网站集约化建设
  • 怎么把做的网站传怎样设置自己的网站
  • 安徽网站设计哪家效果好茂名做网站的公司
  • Linux C/C++ 学习日记(29):IO密集型与CPU密集型、CPU的调度与线程切换
  • 网站布局案例网站内容如何管理
  • (持续更新中!!~)30、原来可以这样理解C语言_项⽬实践-贪吃蛇
  • 信息展示网站余姚企业网站建设公司
  • YOLOv4 核心技术解析与优势
  • 深入理解STL关联容器:map/multimap与set/multiset全解析
  • 【Linux应用开发·入门指南】详解文件IO以及文件描述符的使用
  • 山西建设执业注册中心网站查网站跳出率
  • 成都美誉网站设计网站建设英语词汇
  • spark组件-spark core(批处理)-rdd行动算子(action)
  • MOTR: End-to-End Multiple-Object Tracking with TRansformer推理学习
  • RedHat自动化Ansible的部署
  • 服务器iis做网站网站开发与设计实训心得一千字
  • 买域名后怎么做网站做门户网站需要多少钱
  • 海南网站建设网站开发小程序app网站建设培训相关资料
  • 供热设施网站搭建教程支付招聘网站套餐费用怎么做帐