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

源码-Eureka

CAP

保证可用性和分区容错性,不保证一致性。
可用性表现在:eureka-server集群中的节点没有主从之分,只要有一个eureka-server集群存活,就可以保证服务可用。
分区容错性表现在:当网络发生故障,导致短时间内大量eureka-client没有发送心跳,eureka-server不会清理这些过期的eureka-client,而是继续使用。

AutoConfiguration

EurekaServerAutoConfiguration

1、通过在启动类上添加@EnableEurekaServer,通过@Import注入Marker的bean;
2、通过 spring.factories 中 EnableAutoConfiguration=xxx 的方式注入EurekaServerAutoConfiguration;
3、EurekaServerAutoConfiguration含有@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),所以启动类上必须添加@EnableEurekaServer才会生效。
4、EurekaServerAutoConfiguration 中通过 @Import 注入实现SmartLifecycle的配置类,所以在finishRefresh()中会调用该配置类的start()方法,start()方法中单独起一个线程完成EurekaServer的所有初始化操作。

EurekaClientAutoConfiguration

1、通过 spring.factories 中 EnableAutoConfiguration=xxx 的方式注入EurekaClientAutoConfiguration。
2、EurekaClientAutoConfiguration通过@Bean注入EurekaClient的bean,在EurekaClient的构造函数中完成 EurekaClient 的初始化操作。主要方法在initScheduledTasks()中。

服务注册

client

initScheduledTasks()

if(clientConfig.shouldRegisterWithEureka()){instanceInfoReplicator.start();}

start()

scheduledExecutorService.schedule(this,40,TimeUnit.SECONDS);

run()

publicvoidrun(){try{discoveryClient.register();// 服务注册,对应 EurekaServer 服务注册接口}finally{scheduledExecutorService.schedule(this,30,TimeUnit.SECONDS);}}

register()

booleanregister()throwsThrowable{EurekaHttpResponse<Void>httpResponse=eurekaTransport.registrationClient.register(instanceInfo);returnhttpResponse.getStatusCode()==Status.NO_CONTENT.getStatusCode();}

Server

ApplicationResource.addInstance()

@POST@Consumes({"application/json","application/xml"})publicResponseaddInstance(InstanceInfoinfo){registry.register(info,"true".equals(isReplication));}

register()

publicvoidregister(InstanceInforegistrant,intleaseDuration,booleanisReplication){// 判断注册表是否已有该实例Map<String,Lease<InstanceInfo>>gMap=registry.get(registrant.getAppName());if(gMap==null){// 如果没有,注册表添加该实例registry.putIfAbsent(registrant.getAppName(),gNewMap);}// 清空"读写缓存"数据readWriteCacheMap.invalidate(key);}

服务发现

Client

initScheduledTasks()

if(clientConfig.shouldFetchRegistry()){// 开始执行一次性任务scheduledExecutorService.schedule(runnable,30,TimeUnit.SECONDS)}

一次性任务的逻辑

publicvoidrun(){Future<?>future=null;try{// threadPoolExecutor核心线程数1,最大线程数2。task中执行服务注册逻辑future=threadPoolExecutor.submit(task);// 30s后获取结果,如果获取失败走到catch里future.get(30,TimeUnit.SECONDS);// 获取结果成功,延迟时间重新设置成30sdelay.set(30);}catch(TimeoutExceptione){// 当前延迟时间longcurrentDelay=delay.get();// 取当前延迟时间*2 与 最大延迟时间(300s)的最小值longnewDelay=Math.min(300,currentDelay*2);delay.compareAndSet(currentDelay,newDelay);}finally{if(!scheduler.isShutdown()){// 再次开始执行一次性任务(延迟时间延长两倍)scheduler.schedule(this,delay.get(),TimeUnit.MILLISECONDS);}}}

服务拉取业务逻辑:
CacheRefreshThread

privateclassCacheRefreshThreadimplementsRunnable{publicvoidrun(){booleansuccess=fetchRegistry(remoteRegionsModified);if(success){lastSuccessfulRegistryFetchTimestamp=System.currentTimeMillis();}}}

fetchRegistry()
第一次拉取走全量拉取,否则走增量拉取。
如果增量拉取失败,也会走全量拉取。
全量拉取:为eurekaClient对象的Applications属性赋值。服务端位置:ApplicationsResource.getContainers()
增量拉取:更新eurekaClient对象的Applications属性。服务端位置:ApplicationsResource.getContainerDifferential()

Server

服务端查询注册表并返回给客户端:

ValuegetValue(finalKeykey){// 先查只读缓存finalValuecurrentPayload=readOnlyCacheMap.get(key);if(currentPayload!=null){// 只读缓存中有,直接返回Valuepayload=currentPayload;}else{// 只读缓存中没有,再查读写缓存(读写缓存中没有时会查注册表,并保存到读写缓存)payload=readWriteCacheMap.get(key);// 将读写缓存的结果同步到只读缓存并返回readOnlyCacheMap.put(key,payload);}returnpayload;}

多级缓存的目的:避免频繁对注册表registry读写,因为读写都要上锁,影响性能。

三级缓存

instanceRegistry

服务注册时,会将服务信息添加到注册表。

readWriteCacheMap

服务注册时,会清理读写缓存中的数据,这样可以保证服务发现时,如果只读缓存没有再查询读写缓存时,可以读取到最新的注册表信息。
写入到读写缓存的数据,180s后会自动失效。

readOnlyCacheMap

服务发现时,会先查询只读缓存。
定时任务:同步只读缓存中的值为读写缓存最新值

timer.schedule(TimerTasktask,DatefirstTime,longperiod)

task

for(Keykey:readOnlyCacheMap.keySet()){ValuecacheValue=readWriteCacheMap.get(key);ValuecurrentCacheValue=readOnlyCacheMap.get(key);if(cacheValue!=currentCacheValue){readOnlyCacheMap.put(key,cacheValue);}}

服务续约

Client

initScheduledTasks()

if(clientConfig.shouldRegisterWithEureka()){// 开始执行一次性任务scheduledExecutorService.schedule(runnable,30,TimeUnit.SECONDS)}

一次性任务的逻辑

publicvoidrun(){Future<?>future=null;try{// threadPoolExecutor核心线程数1,最大线程数2。task中执行服务注册逻辑future=threadPoolExecutor.submit(task);// 30s后获取结果,如果获取失败走到catch里future.get(30,TimeUnit.SECONDS);// 获取结果成功,延迟时间重新设置成30sdelay.set(30);}catch(TimeoutExceptione){// 当前延迟时间longcurrentDelay=delay.get();// 取当前延迟时间*2 与 最大延迟时间(300s)的最小值longnewDelay=Math.min(300,currentDelay*2);delay.compareAndSet(currentDelay,newDelay);}finally{if(!scheduler.isShutdown()){// 再次开始执行一次性任务(延迟时间延长两倍)scheduler.schedule(this,delay.get(),TimeUnit.MILLISECONDS);}}}

心跳检测业务逻辑:
HeartbeatThread

privateclassHeartbeatThreadimplementsRunnable{publicvoidrun(){if(renew()){lastSuccessfulHeartbeatTimestamp=System.currentTimeMillis();}}}

renew()

booleanrenew(){EurekaHttpResponse<InstanceInfo>httpResponse=eurekaServer.sendHeartBeat(instanceInfo);if(httpResponse.getStatusCode()==404){// 服务续约失败后,走服务注册register();}}

server

InstanceResource.renewLease()

// 更新上一次更新时间 = 当前时间 + 90slastUpdateTimestamp=System.currentTimeMillis()+duration;

服务剔除

server

postInit()

protectedvoidpostInit(){evictionTimer.schedule(EvictionTask,60,60);}

EvictionTask

List<Lease<InstanceInfo>>expiredLeases=newArrayList<>();for(Entry<String,Map<String,Lease<InstanceInfo>>>groupEntry:registry.entrySet()){Map<String,Lease<InstanceInfo>>leaseMap=groupEntry.getValue();if(leaseMap!=null){for(Entry<String,Lease<InstanceInfo>>leaseEntry:leaseMap.entrySet()){Lease<InstanceInfo>lease=leaseEntry.getValue();if(lease.isExpired(additionalLeaseMs)&&lease.getHolder()!=null){expiredLeases.add(lease);}}}}

isExpired()

publicbooleanisExpired(longadditionalLeaseMs){return(evictionTimestamp>0||System.currentTimeMillis()>(lastUpdateTimestamp+duration+additionalLeaseMs));}
http://www.jsqmd.com/news/706540/

相关文章:

  • 正则表达式终极指南:10个文本处理匹配技巧
  • 【MCP 2026漏洞猎人内部手册】:3类隐蔽型RCE触发路径+2套自动化检测脚本(限免领取至2026.04.30)
  • langsmith-fetch技能:调试LangChain和LangGraph代理的必备工具
  • nw.js调试工具:10个高级调试技巧解决复杂开发问题
  • ADB Idea多设备支持完全指南:智能设备选择与记忆功能
  • AndroidTagGroup布局优化指南:掌握15个自定义属性提升UI体验
  • 开源代码生成工具MassGen:模板驱动,解放重复编码生产力
  • 智能体技能开发实战:从工具调用到系统架构的完整指南
  • Cloudflare HTML 解析器的十年演化史(二)
  • 如何快速掌握Preact:从零开始的现代前端框架完整指南
  • NW.js质量保证终极指南:从代码审查到自动化测试的完整流程
  • ARM NEON与VFP指令集:高性能嵌入式开发实战
  • DevDocs知识管理系统:团队经验的积累与分享终极指南
  • 第二十二篇技术笔记:郭大侠学DoIP - OBD口的“隐藏技能”
  • 2026年3月有名的避雷塔代加工加工厂,钢管塔避雷塔/箱变基础平台/三项变压器/角钢塔避雷针,避雷塔加工联系方式 - 品牌推荐师
  • 掌握Noto Emoji:构建跨平台表情符号的终极视觉工具箱
  • 10个高效Docker部署策略:容器化应用最佳实践指南
  • owl4ce/dotfiles桌面环境核心组件深度解析
  • 强化学习智能体记忆系统设计:从经验回放到语义检索的架构演进
  • 9Router:本地AI模型路由代理,智能调度Claude/Codex/免费模型实现低成本不间断编程
  • 如何掌握Yew Future:Rust Web应用的异步操作与并发处理终极指南
  • owl4ce/dotfiles双主题切换:从机械风到艺术风的完美转换
  • PHPCI配置文件详解:phpci.yml编写技巧与最佳实践
  • Homarr开发者工具链详解:Turbo、TypeScript与Monorepo架构
  • 终极PHP导航菜单指南:从KnpMenu到Spatie Menu的完整实现方案
  • 2026年可靠卫生检测报告收费指南及行业标杆名录:卫生检测公司、卫生检测公司、卫生检测报告在哪里办、卫生检测报告在哪里办选择指南 - 优质品牌商家
  • 如何快速掌握Vim:零基础到熟练的完整指南
  • 乐山临江鳝丝店排行:临江鳝丝店哪家靠谱/临江鳝丝店排名前十/乐山临江鳝丝店哪个专业/乐山临江鳝丝店哪个值得选/乐山临江鳝丝店哪些更专业/选择指南 - 优质品牌商家
  • Copilot Next 工作流配置不再玄学:12个可复制的settings.json片段,附真实项目性能对比数据(+47.2%编码速度)
  • 365 Data Science免费课程:数据科学学习路径与实战资源解析