一Eureka1.1Eureka简介Eureka是基于RESTRepresentational State Transfer服务主要以AWS云服务为支撑提供服务发现并实现负载均衡和故障转移。我们称此服务为Eureka服务。Eureka提供了Java客户端组件Eureka Client方便与服务端的交互。客户端内置了基于round-robin实现的简单负载均衡。在Netflix为Eureka提供更为复杂的负载均衡方案进行封装以实现高可用它包括基于流量、资源利用率以及请求返回状态的加权负载均衡。Eureka包含两个组件Eureka Server和Eureka Client1.1.1Eureka Server提供服务注册服务各个微服务节点通过配置启动后会在EurekaServer中进行注册这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息服务节点的信息可以在界面中直观看到。1.1.2EurekaClient通过注册中心进行访问是一个Java客户端用于简化Eureka Server的交互客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳EurekaServer将会从服务注册表中把这个服务节点移除默认90秒1.2Eureka架构1.3Eureka中的一些概念1.3.1Register :服务注册Eureka客户端向Eureka Server注册时它提供自身的元数据比如IP地址、端口等1.3.2Renew服务续约Eureka客户端会每隔30秒发送一次心跳来续约。通过续约来告知Eureka Server该客户端仍然存在。1.3.3Fetch Registries获取注册列表信息Eureka客户端从服务器获取注册表信息将其缓存到本地。客户端会使用该信息查找其他服务从而进行远程调用。该注册列表信息定期每30秒更新一次。1.3.4Cancel服务下线Eureka客户端在程序关闭时向Eureka服务器发送取消请求。1.3.5Eviction服务剔除在默认情况下当Eureka客户端90秒没有向Eureka服务器发送续约Eureka服务器就会将该服务实例从服务注册列表删除。1.3.6自我保护Eureka会定时剔除没有续约的服务那就可能会出现一种极端情况网络发生了异常所有的服务都没有续约那么EurekaServer就会剔除掉所有的服务这显然不合理于是就有了自我保护机制当短时间内15min如果续约失败的比例达到了设置的阈值85%就会触发自我保护机制该机制下EurekaServer不会剔除服务等到正常后再退出自我保护机制。自我保护开关eureka.server.enable-self-preservationtrue。除了以上的特性外Eureka的缓存机制也非常经典下面详细介绍一下。1.3.7Eureka Server 数据存储Eureka Server 的数据存储分了两层数据存储层和缓存层。数据存储层记录注册到 Eureka Server 上的服务信息缓存层是经过包装后的数据可以直接在 Eureka Client 调用时返回。Eureka Server 的数据存储层是双层的 ConcurrentHashMap第一层的 ConcurrentHashMap 的 keyspring.application.name 也就是客户端实例注册的应用名value 为嵌套的 ConcurrentHashMap。第二层嵌套的 ConcurrentHashMap 的 keyinstanceId 也就是服务的唯一实例 IDvalue 为 Lease 对象Lease 对象存储着这个实例的所有注册信息包括 ip 、端口、属性等。Eureka Server 缓存机制Eureka Server 为了提供响应效率提供了两层的缓存结构将 Eureka Client 所需要的注册信息直接存储在缓存结构中。第一层缓存readOnlyCacheMap本质上是 ConcurrentHashMap依赖定时从readWriteCacheMap 同步数据默认时间为 30 秒。readOnlyCacheMap 是一个 CurrentHashMap 只读缓存这个主要是为了供客户端获取注册信息时使用其缓存更新依赖于定时器的更新通过和 readWriteCacheMap 的值做对比如果数据不一致则以 readWriteCacheMap 的数据为准。第二层缓存readWriteCacheMap本质上是 Guava 缓存。readWriteCacheMap 的数据主要同步于存储层。当获取缓存时判断缓存中是否没有数据如果不存在此数据则通过 CacheLoader 的 load 方法去加载加载成功之后将数据放入缓存同时返回数据。readWriteCacheMap 缓存过期时间默认为 180 秒当服务下线、过期、注册、状态变更都会来清除此缓存中的数据。Eureka Client 获取全量或者增量的数据时会先从一级缓存中获取如果一级缓存中不存在再从二级缓存中获取如果二级缓存也不存在这时候先将存储层的数据同步到缓存中再从缓存中获取。1.4Eureka Server常用配置#服务端开启自我保护模式 eureka.server.enable-self-preservationtrue #扫描失效服务的间隔时间单位毫秒默认是60*1000即60秒 eureka.server.eviction-interval-timer-in-ms 60000 #间隔多长时间清除过期的 delta 数据 eureka.server.delta-retention-timer-interval-in-ms0 #请求频率限制器 eureka.server.rate-limiter-burst-size10 #是否开启请求频率限制器 eureka.server.rate-limiter-enabledfalse #请求频率的平均值 eureka.server.rate-limiter-full-fetch-average-rate100 #是否对标准的client进行频率请求限制。如果是false则只对非标准client进行限制 eureka.server.rate-limiter-throttle-standard-clientsfalse #注册服务、拉去服务列表数据的请求频率的平均值 eureka.server.rate-limiter-registry-fetch-average-rate500 #设置信任的client list eureka.server.rate-limiter-privileged-clients #在设置的时间范围类期望与client续约的百分比。 eureka.server.renewal-percent-threshold0.85 #多长时间更新续约的阈值 eureka.server.renewal-threshold-update-interval-ms0 #对于缓存的注册数据多长时间过期 eureka.server.response-cache-auto-expiration-in-seconds180 #多长时间更新一次缓存中的服务注册数据 eureka.server.response-cache-update-interval-ms0 #缓存增量数据的时间以便在检索的时候不丢失信息 eureka.server.retention-time-in-m-s-in-delta-queue0 #当时间戳不一致的时候是否进行同步 eureka.server.sync-when-timestamp-differstrue #是否采用只读缓存策略只读策略对于缓存的数据不会过期。 eureka.server.use-read-only-response-cachetrue ################server node 与 node 之间关联的配置#####################33 #发送复制数据是否在request中总是压缩 eureka.server.enable-replicated-request-compressionfalse #指示群集节点之间的复制是否应批处理以提高网络效率。 eureka.server.batch-replicationfalse #允许备份到备份池的最大复制事件数量。而这个备份池负责除状态更新的其他事件。可以根据内存大小超时和复制流量来设置此值得大小 eureka.server.max-elements-in-peer-replication-pool10000 #允许备份到状态备份池的最大复制事件数量 eureka.server.max-elements-in-status-replication-pool10000 #多个服务中心相互同步信息线程的最大空闲时间 eureka.server.max-idle-thread-age-in-minutes-for-peer-replication15 #状态同步线程的最大空闲时间 eureka.server.max-idle-thread-in-minutes-age-for-status-replication15 #服务注册中心各个instance相互复制数据的最大线程数量 eureka.server.max-threads-for-peer-replication20 #服务注册中心各个instance相互复制状态数据的最大线程数量 eureka.server.max-threads-for-status-replication1 #instance之间复制数据的通信时长 eureka.server.max-time-for-replication30000 #正常的对等服务instance最小数量。-1表示服务中心为单节点。 eureka.server.min-available-instances-for-peer-replication-1 #instance之间相互复制开启的最小线程数量 eureka.server.min-threads-for-peer-replication5 #instance之间用于状态复制开启的最小线程数量 eureka.server.min-threads-for-status-replication1 #instance之间复制数据时可以重试的次数 eureka.server.number-of-replication-retries5 #eureka节点间间隔多长时间更新一次数据。默认10分钟。 eureka.server.peer-eureka-nodes-update-interval-ms600000 #eureka服务状态的相互更新的时间间隔。 eureka.server.peer-eureka-status-refresh-time-interval-ms0 #eureka对等节点间连接超时时间 eureka.server.peer-node-connect-timeout-ms200 #eureka对等节点连接后的空闲时间 eureka.server.peer-node-connection-idle-timeout-seconds30 #节点间的读数据连接超时时间 eureka.server.peer-node-read-timeout-ms200 #eureka server 节点间连接的总共最大数量 eureka.server.peer-node-total-connections1000 #eureka server 节点间连接的单机最大数量 eureka.server.peer-node-total-connections-per-host10 #在服务节点启动时eureka尝试获取注册信息的次数 eureka.server.registry-sync-retries #在服务节点启动时eureka多次尝试获取注册信息的间隔时间 eureka.server.registry-sync-retry-wait-ms #当eureka server启动的时候不能从对等节点获取instance注册信息的情况应等待多长时间。 eureka.server.wait-time-in-ms-when-sync-empty01.5Eureka Client 常用配置#该客户端是否可用 eureka.client.enabledtrue #实例是否在eureka服务器上注册自己的信息以供其他服务发现默认为true eureka.client.register-with-eurekafalse #此客户端是否获取eureka服务器注册表上的注册信息默认为true eureka.client.fetch-registryfalse #是否过滤掉非UP的实例。默认为true eureka.client.filter-only-up-instancestrue #与Eureka注册服务中心的通信zone和url地址 eureka.client.serviceUrl.defaultZonehttp://${eureka.instance.hostname}:${server.port}/eureka/ #client连接Eureka服务端后的空闲等待时间默认为30 秒 eureka.client.eureka-connection-idle-timeout-seconds30 #client连接eureka服务端的连接超时时间默认为5秒 eureka.client.eureka-server-connect-timeout-seconds5 #client对服务端的读超时时长 eureka.client.eureka-server-read-timeout-seconds8 #client连接all eureka服务端的总连接数默认200 eureka.client.eureka-server-total-connections200 #client连接eureka服务端的单机连接数量默认50 eureka.client.eureka-server-total-connections-per-host50 #执行程序指数回退刷新的相关属性是重试延迟的最大倍数值默认为10 eureka.client.cache-refresh-executor-exponential-back-off-bound10 #执行程序缓存刷新线程池的大小默认为5 eureka.client.cache-refresh-executor-thread-pool-size2 #心跳执行程序回退相关的属性是重试延迟的最大倍数值默认为10 eureka.client.heartbeat-executor-exponential-back-off-bound10 #心跳执行程序线程池的大小,默认为5 eureka.client.heartbeat-executor-thread-pool-size5 # 询问Eureka服务url信息变化的频率s默认为300秒 eureka.client.eureka-service-url-poll-interval-seconds300 #最初复制实例信息到eureka服务器所需的时间s默认为40秒 eureka.client.initial-instance-info-replication-interval-seconds40 #间隔多长时间再次复制实例信息到eureka服务器默认为30秒 eureka.client.instance-info-replication-interval-seconds30 #从eureka服务器注册表中获取注册信息的时间间隔s默认为30秒 eureka.client.registry-fetch-interval-seconds30 # 获取实例所在的地区。默认为us-east-1 eureka.client.regionus-east-1 #实例是否使用同一zone里的eureka服务器默认为true理想状态下eureka客户端与服务端是在同一zone下 eureka.client.prefer-same-zone-eurekatrue # 获取实例所在的地区下可用性的区域列表用逗号隔开。AWS eureka.client.availability-zones.chinadefaultZone,defaultZone1,defaultZone2 #eureka服务注册表信息里的以逗号隔开的地区名单如果不这样返回这些地区名单则客户端启动将会出错。默认为null eureka.client.fetch-remote-regions-registry #服务器是否能够重定向客户端请求到备份服务器。 如果设置为false服务器将直接处理请求如果设置为true它可能发送HTTP重定向到客户端。默认为false eureka.client.allow-redirectsfalse #客户端数据接收 eureka.client.client-data-accept #增量信息是否可以提供给客户端看默认为false eureka.client.disable-deltafalse #eureka服务器序列化/反序列化的信息中获取“_”符号的的替换字符串。默认为“__“ eureka.client.escape-char-replacement__ #eureka服务器序列化/反序列化的信息中获取“$”符号的替换字符串。默认为“_-” eureka.client.dollar-replacement_- #当服务端支持压缩的情况下是否支持从服务端获取的信息进行压缩。默认为true eureka.client.g-zip-contenttrue #是否记录eureka服务器和客户端之间在注册表的信息方面的差异默认为false eureka.client.log-delta-difffalse # 如果设置为true,客户端的状态更新将会点播更新到远程服务器上默认为true eureka.client.on-demand-update-status-changetrue #此客户端只对一个单一的VIP注册表的信息感兴趣。默认为null eureka.client.registry-refresh-single-vip-address #client是否在初始化阶段强行注册到服务中心默认为false eureka.client.should-enforce-registration-at-initfalse #client在shutdown的时候是否显示的注销服务从服务中心默认为true eureka.client.should-unregister-on-shutdowntrue1.6Eureka Instance 常用配置#服务注册中心实例的主机名 eureka.instance.hostnamelocalhost #注册在Eureka服务中的应用组名 eureka.instance.app-group-name #注册在的Eureka服务中的应用名称 eureka.instance.appname #该实例注册到服务中心的唯一ID eureka.instance.instance-id #该实例的IP地址 eureka.instance.ip-address #该实例相较于hostname是否优先使用IP eureka.instance.prefer-ip-addressfalse1.7Eureka注册中心组成Eureka属于AP架构高可用。1.8EurekaServer端源码–流程图1找到eurekaserver jar包spi机制找到其中的sping.factories文件找到server服务端自动装配的类EurekaServerAutoConfiguration2此配置类中会配置一些BeanEurekaServerConfig—初始化eureka相关配置EurekaController—初始化接口获取eurekaServer信息PeerAwareInstanceRegistry—初始化集群注册表PeerEurekaNodes—初始化集群节点集合EurekaServerContext—基于eurekaServer注册配置表集群节点集合及初始化eurekaServer上下文EurekaServerBootstrap—启动eureka服务FilterRegistrationBean—初始化Jersey过滤器3此配置类中Import注解的实现类EurekaServerInitializerConfiguration实现了SmartLifecycle接口并且定义了isAutoStartup返回trueSmartLifecycle继承自Lifecycle就会调用到Lifecycle的start方法。SmartLifecycle继承了LifecycleLifecycleProcessor继承了Lifecycle并且其中定义了onRefresh方法在spring启动时刷新上下文时候就会调用到此方法。调用链路如下spring启动刷新上下文------this.finishRefresh();LifecycleProcessor().onRefresh();this.startBeans(true);DefaultLifecycleProcessor.this.doStartstartstart方法中新起了一个线程启动eureka服务初始化上下文------this.eurekaServerBootstrap.contextInitialized(this.servletContext);发布eureka注册有效事件------this.publish(new EurekaRegistryAvailableEvent(this.getEurekaServerConfig()));发布eureka服务启动成功事件------this.publish(new EurekaServerStartedEvent(this.getEurekaServerConfig()));contextInitialized方法中初始化eureka环境------this.initEurekaEnvironment();初始化上下文------this.initEurekaServerContext();初始化上下文------this.initEurekaServerContext()中------服务同步replicate从相邻节点复制注册表------this.registry.syncUp();获取相邻节点注册的服务实例------Applications apps this.eurekaClient.getApplications();注册到本地------this.register(instance, instance.getLeaseInfo().getDurationInSecs(), true);初始化上下文------this.initEurekaServerContext()中------服务剔除evictregistry.openForTraffic(this.applicationInfoManager, registryCount);修改服务实例状态为UP------applicationInfoManager.setInstanceStatus(InstanceStatus.UP);开启一个定时任务每隔60s清理没有续约的任务------postInit();定时任务------this.evictionTimer.schedule((TimerTask)this.evictionTaskRef.get(), this.serverConfig.getEvictionIntervalTimerInMs(), this.serverConfig.getEvictionIntervalTimerInMs());定时任务run方法-----EvictionTask.run()------com.netflix.eureka.registry.AbstractInstanceRegistry.EvictionTask#run1.9EurekaClient端源码–流程图1.9.1如何开启服务注册进Eureka服务EurekaClient的开启可以使用EnableDiscoveryClient注解也可以不使用这个注解因为这个注解中的属性autoRegister默认为TRUE,如果我们不想某个服务注册进Eureka可以将这个属性设置为falseEnableDiscoveryClient(autoRegister false)1.9.2Client端流程EurekaClientAutoConfiguration中注解AutoConfigureAfter中含有一个类EurekaDiscoveryClientConfiguration这个类中会注入一个bean-Marker。此Marker正是EurekaClientAutoConfiguration所需要的只有这个bean存在springboot才会在启动的时候注册这个EurekaClientAutoConfiguration-Bean.EurekaClientAutoConfiguration中配置了很多的BeanEurekaClientConfigBean------初始化EurekaClient相关配置EurekaInstanceConfigBean------client实例相关配置信息EurekaServiceRegistry------服务注册EurekaHealthIndicator------心跳检测EurekaClient其中EurekaClient很重要EurekaClient是对DiscoveryClient包装。DiscoveryClient的构造函数中会初始化各种任务在initScheduledTasks中会初始化定时拉取服务注册信息更新本地注册表和服务续约任务初始化定时服务注册任务。后续具体流程请查看流程图。二Nacos2.1安装并运行Nacos本地Java8Maven环境先从官网下载Nacoshttps://github.com/alibaba/nacos/releases或者https://nacos.io/download/nacos-server/?spm5238cd80.2ef5001f.0.0.3f613b7cHmXbuy解压安装包直接运行bin目录下的打开cmd执行命令具体文档见https://nacos.io/docs/v2.4/quickstart/quick-start/启动命令(standalone代表着单机模式运行非集群模式):startup.cmd -m standalone命令运行成功后直接访问http://localhost:8848/nacos、默认账号密码都是nacos2.2服务注册中心对比2.1.1Nacos 支持AP和CP模式的切换C是所有节点在同一时间看到的数据是一致的而A的定义是所有的请求都会收到响应。C:Consistency强一致性A:Availability可用性P:Partition tolerance分区容错性CAP理论关注粒度是数据而不是整体系统设计的策略//-----------------------------------------------------------------------------------------------------最多只能同时较好的满足两个。CAP理论的核心是一个分布式系统不可能同时很好的满足一致性可用性和分区容错性这三个需求因此根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类CA - 单点集群满足一致性可用性的系统通常在可扩展性上不太强大。CP - 满足一致性分区容忍必的系统通常性能不是特别高。AP - 满足可用性分区容忍性的系统通常可能对一致性要求低一些。2.1.2何时选择使用何种模式一般来说如果不需要存储服务级别的信息且服务实例是通过nacos-client注册并能够保持心跳上报那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务都适用于AP模式AP模式为了服务的可能性而减弱了一致性因此AP模式下只支持注册临时实例。如果需要在服务级别编辑或者存储配置信息那么 CP 是必须K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例此时则是以 Raft 协议为集群运行模式该模式下注册实例之前必须先注册服务如果服务不存在则会返回错误。2.3Nacos服务注册与发现实战代码git地址--https://gitee.com/zhang_xiaosong/Spring-Cloud-Alibaba/tree/master/services2.3.1服务注册可以在idea通过启动不同的端口号来模拟集群2.3.2服务发现简单的代码测试DisCoveryTestSpringBootTest public class DisCoveryTest { Resource private DiscoveryClient discoveryClient; Resource private NacosServiceDiscovery nacosServiceDiscovery; Test public void disCoveryClientTest() throws Exception { for (String service : discoveryClient.getServices()) { System.out.println(service: service); for (ServiceInstance instance : discoveryClient.getInstances(service)) { System.out.println(ip: instance.getHost() -port: instance.getPort()); } } } Test public void nacosServiceDiscoveryTest() throws Exception { for (String service : nacosServiceDiscovery.getServices()) { System.out.println(service: service); for (ServiceInstance instance : nacosServiceDiscovery.getInstances(service)) { System.out.println(ip: instance.getHost() -port: instance.getPort()); } } } }输出结果2.3.3服务调用-手搓版product服务ProductController/** * description: * author: zhangguisong * date: 2026/3/25 19:21 */ RestController RequestMapping(/product) public class ProductController { Resource private ProductService productService; GetMapping(/detail/{id}) public Product getProduct(PathVariable(id) Integer id) { return productService.getDetailById(id); } }ProductServicepublic interface ProductService { Product getDetailById(Integer id); }ProductServiceImplService public class ProductServiceImpl implements ProductService { Override public Product getDetailById(Integer id) { Product product new Product(); product.setId(id); product.setName(product id); product.setPrice(new BigDecimal(10)); return product; } }order服务OrderControllerRestController RequestMapping(/order) public class OrderController { Resource private OrderService orderService; PostMapping(/add) public String add() { orderService.createOrder(); return ok; } }OrderServicepublic interface OrderService { void createOrder(); }OrderServiceImpl/** * description: * author: zhangguisong * date: 2026/3/25 19:34 */ Service Slf4j public class OrderServiceImpl implements OrderService { Resource private RestTemplate restTemplate; Resource private DiscoveryClient discoveryClient; Override public void createOrder() { Order order new Order(); order.setProductId(5); Product product getProduct(order.getProductId()); order.setOrderNo(orderNo); order.setNum(2); order.setTotalPrice(product.getPrice().multiply(new BigDecimal(order.getNum()))); order.setUserName(userName); order.setProductList(Collections.singletonList(product)); System.out.println(order); } private Product getProduct(Integer id) { //获取商品服务的所有的机器 ipport ListServiceInstance instances discoveryClient.getInstances(service-product); log.info(instances:{}, JSONUtil.toJsonStr(instances)); ServiceInstance serviceInstance instances.get(0); //远程URL String url http:// serviceInstance.getHost() : serviceInstance.getPort() /product/detail/ id; log.info(url:{}, url); //发送远程请求 return restTemplate.getForObject(url, Product.class); } //TODO zgs 2026/3/25 20:22 desc完成负载均衡的调用 private Product getProductBalance(Integer id) { return null; } }2.3.4服务调用-手搓负载均衡版OrderServiceImpl/** * description: * author: zhangguisong * date: 2026/3/25 19:34 */ Service Slf4j public class OrderServiceImpl implements OrderService { Resource private RestTemplate restTemplate; Resource private DiscoveryClient discoveryClient; Resource private LoadBalancerClient loadBalancerClient; Override public void createOrder() { Order order new Order(); order.setProductId(5); Product product getProductBalance(order.getProductId()); order.setOrderNo(orderNo); order.setNum(2); order.setTotalPrice(product.getPrice().multiply(new BigDecimal(order.getNum()))); order.setUserName(userName); order.setProductList(Collections.singletonList(product)); System.out.println(order); } private Product getProduct(Integer id) { //获取商品服务的所有的机器 ipport ListServiceInstance instances discoveryClient.getInstances(service-product); log.info(instances:{}, JSONUtil.toJsonStr(instances)); ServiceInstance serviceInstance instances.get(0); //远程URL String url http:// serviceInstance.getHost() : serviceInstance.getPort() /product/detail/ id; log.info(url:{}, url); //发送远程请求 return restTemplate.getForObject(url, Product.class); } //TODO zgs 2026/3/25 20:22 desc完成负载均衡的调用 private Product getProductBalance(Integer id) { ServiceInstance choose loadBalancerClient.choose(service-product); String url http:// choose.getHost() : choose.getPort() /product/detail/ id; log.info(loadbalancer-url:{}, url); return restTemplate.getForObject(url, Product.class); } }从控制台就可以看出现在是轮训调用的了2.3.5服务调用-基于注解的负载均衡OrderConfig增加LoadBalanced注解Configuration public class OrderConfig { LoadBalanced Bean public RestTemplate restTemplate() { return new RestTemplate(); } }OrderServiceImpl/** * description: * author: zhangguisong * date: 2026/3/25 19:34 */ Service Slf4j public class OrderServiceImpl implements OrderService { Resource private RestTemplate restTemplate; Resource private DiscoveryClient discoveryClient; Resource private LoadBalancerClient loadBalancerClient; Override public void createOrder() { Order order new Order(); order.setProductId(5); Product product getProductAnnotation(order.getProductId()); order.setOrderNo(orderNo); order.setNum(2); order.setTotalPrice(product.getPrice().multiply(new BigDecimal(order.getNum()))); order.setUserName(userName); order.setProductList(Collections.singletonList(product)); System.out.println(order); } private Product getProduct(Integer id) { //获取商品服务的所有的机器 ipport ListServiceInstance instances discoveryClient.getInstances(service-product); log.info(instances:{}, JSONUtil.toJsonStr(instances)); ServiceInstance serviceInstance instances.get(0); //远程URL String url http:// serviceInstance.getHost() : serviceInstance.getPort() /product/detail/ id; log.info(url:{}, url); //发送远程请求 return restTemplate.getForObject(url, Product.class); } //TODO zgs 2026/3/25 20:22 desc完成负载均衡的调用 private Product getProductBalance(Integer id) { ServiceInstance choose loadBalancerClient.choose(service-product); String url http:// choose.getHost() : choose.getPort() /product/detail/ id; log.info(loadbalancer-url:{}, url); return restTemplate.getForObject(url, Product.class); } //TODO zgs 2026/3/25 20:45 desc基于注解的负载均衡 private Product getProductAnnotation(Integer id) { //service-product会被动态替换 String url http://service-product/product/detail/ id; return restTemplate.getForObject(url, Product.class); } }2.3.6本地缓存注册中心什么是 Nacos 本地缓存Nacos 本地缓存是指客户端将从 Nacos 服务器获取的服务列表、配置信息等数据缓存在本地 JVM 内存中这样可以提高性能 - 减少网络请求快速读取缓存数据增强容错 - 即使 Nacos 服务器暂时不可用也能使用本地缓存继续工作降低负载 - 减少对 Nacos 服务器的压力本地缓存机制说明服务发现缓存Naming Cache位置{user.home}/nacos/naming/{namespace}/{serviceName}内容服务实例列表、健康状态、权重等更新方式定时拉取 UDP 推送配置缓存Config Cache位置{user.home}/nacos/config/{tenant}/{group}/{dataId}内容配置文件的 JSON/YAML 内容更新方式长轮询监听配置变化本地缓存的工作原理┌─────────────┐ ┌──────────────┐ ┌─────────────┐│ 应用启动 │ ────── │ 读取本地缓存 │ ────── │ 快速启动 │└─────────────┘ └──────────────┘ └─────────────┘↓ ↓┌─────────────┐ ┌──────────────┐ ┌─────────────┐│ 连接 Nacos │ ────── │ 同步最新数据 │ ────── │ 更新缓存 │└─────────────┘ └──────────────┘ └─────────────┘↓ ↓┌─────────────┐ ┌──────────────┐│ 监听变化 │ ────── │ 实时刷新缓存 │└─────────────┘ └──────────────┘2.4Nacos-配置中心实战2.4.1引入配置中心依赖dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependency2.4.2在配置文件中设置配置spring: application: name: service-order config: import: - nacos:service-order.yml - optional:nacos:service-order.yml cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # 助力这里不能写掉了 否则会报错找不到配置中心的地址 config: server-addr: 127.0.0.1:8848 file-extension: yaml group: DEFAULT_GROUP server: port: 80002.4.3在nacos控制台中去新建配置order: timeout: 30min auto-confirm: 7d2.4.4通过代码获取Nacos中的配置信息OrderControllerGetMapping(/config) public String config() { return orderService.getConfig(); }OrderServiceImplValue(${order.timeout}) private String timeout; Value(${order.auto-confirm}) private String autoConfirm; Override public String getConfig() { log.info(配置{}-{}, timeout, autoConfirm); return 配置 timeout - autoConfirm; }可以看出能成功获取然后在nacos控制台修改配置后重新获取注意这里想要实时刷新需要配合注解使用RefreshScopeService Slf4j RefreshScope public class OrderServiceImpl implements OrderService { Override public String getConfig() { log.info(配置{}-{}, timeout, autoConfirm); return 配置 timeout - autoConfirm; } }可以看出是获取到了的从控制台打印也能看到是监听到了config的更新的对于不需要使用nacos引入检查的可以加配置来跳过这个检查 否则启动会报错spring: application: name: service-product cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: import-check: enabled: false # 关闭nacos配置导入检查 server: port: 80012.4.5更方便的动态刷新原先使用ValueRefreshScope来实现动态刷新是不太方便的应为如果有很多的配置 那岂不是要写很多的Value所以就有了下面的这种方式来更快捷的实现动态刷新。OrderPropertiespackage com.zgs.order.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * description: 订单的配置类 * author: zgs * date: 2026/3/25 22:05 */ Component Data ConfigurationProperties(prefix order) public class OrderProperties { private String timeout; private String autoConfirm; }OrderServiceImplService Slf4j //RefreshScope public class OrderServiceImpl implements OrderService { Resource private OrderProperties orderProperties; // Value(${order.timeout}) // private String timeout; // // Value(${order.auto-confirm}) // private String autoConfirm; Override public String getConfig() { // log.info(配置{}-{}, timeout, autoConfirm); // return 配置 timeout - autoConfirm; log.info(配置{}-{}, orderProperties.getTimeout(), orderProperties.getAutoConfirm()); return 配置 orderProperties.getTimeout() - orderProperties.getAutoConfirm(); } }去nacos控制台更新配置后再次请求接口控制台输出结果2.4.6NacosConfigManager实现监听配置变化
01:服务注册与发现+配置中心-Nacos+Eureka
发布时间:2026/5/23 12:12:39
一Eureka1.1Eureka简介Eureka是基于RESTRepresentational State Transfer服务主要以AWS云服务为支撑提供服务发现并实现负载均衡和故障转移。我们称此服务为Eureka服务。Eureka提供了Java客户端组件Eureka Client方便与服务端的交互。客户端内置了基于round-robin实现的简单负载均衡。在Netflix为Eureka提供更为复杂的负载均衡方案进行封装以实现高可用它包括基于流量、资源利用率以及请求返回状态的加权负载均衡。Eureka包含两个组件Eureka Server和Eureka Client1.1.1Eureka Server提供服务注册服务各个微服务节点通过配置启动后会在EurekaServer中进行注册这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息服务节点的信息可以在界面中直观看到。1.1.2EurekaClient通过注册中心进行访问是一个Java客户端用于简化Eureka Server的交互客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳EurekaServer将会从服务注册表中把这个服务节点移除默认90秒1.2Eureka架构1.3Eureka中的一些概念1.3.1Register :服务注册Eureka客户端向Eureka Server注册时它提供自身的元数据比如IP地址、端口等1.3.2Renew服务续约Eureka客户端会每隔30秒发送一次心跳来续约。通过续约来告知Eureka Server该客户端仍然存在。1.3.3Fetch Registries获取注册列表信息Eureka客户端从服务器获取注册表信息将其缓存到本地。客户端会使用该信息查找其他服务从而进行远程调用。该注册列表信息定期每30秒更新一次。1.3.4Cancel服务下线Eureka客户端在程序关闭时向Eureka服务器发送取消请求。1.3.5Eviction服务剔除在默认情况下当Eureka客户端90秒没有向Eureka服务器发送续约Eureka服务器就会将该服务实例从服务注册列表删除。1.3.6自我保护Eureka会定时剔除没有续约的服务那就可能会出现一种极端情况网络发生了异常所有的服务都没有续约那么EurekaServer就会剔除掉所有的服务这显然不合理于是就有了自我保护机制当短时间内15min如果续约失败的比例达到了设置的阈值85%就会触发自我保护机制该机制下EurekaServer不会剔除服务等到正常后再退出自我保护机制。自我保护开关eureka.server.enable-self-preservationtrue。除了以上的特性外Eureka的缓存机制也非常经典下面详细介绍一下。1.3.7Eureka Server 数据存储Eureka Server 的数据存储分了两层数据存储层和缓存层。数据存储层记录注册到 Eureka Server 上的服务信息缓存层是经过包装后的数据可以直接在 Eureka Client 调用时返回。Eureka Server 的数据存储层是双层的 ConcurrentHashMap第一层的 ConcurrentHashMap 的 keyspring.application.name 也就是客户端实例注册的应用名value 为嵌套的 ConcurrentHashMap。第二层嵌套的 ConcurrentHashMap 的 keyinstanceId 也就是服务的唯一实例 IDvalue 为 Lease 对象Lease 对象存储着这个实例的所有注册信息包括 ip 、端口、属性等。Eureka Server 缓存机制Eureka Server 为了提供响应效率提供了两层的缓存结构将 Eureka Client 所需要的注册信息直接存储在缓存结构中。第一层缓存readOnlyCacheMap本质上是 ConcurrentHashMap依赖定时从readWriteCacheMap 同步数据默认时间为 30 秒。readOnlyCacheMap 是一个 CurrentHashMap 只读缓存这个主要是为了供客户端获取注册信息时使用其缓存更新依赖于定时器的更新通过和 readWriteCacheMap 的值做对比如果数据不一致则以 readWriteCacheMap 的数据为准。第二层缓存readWriteCacheMap本质上是 Guava 缓存。readWriteCacheMap 的数据主要同步于存储层。当获取缓存时判断缓存中是否没有数据如果不存在此数据则通过 CacheLoader 的 load 方法去加载加载成功之后将数据放入缓存同时返回数据。readWriteCacheMap 缓存过期时间默认为 180 秒当服务下线、过期、注册、状态变更都会来清除此缓存中的数据。Eureka Client 获取全量或者增量的数据时会先从一级缓存中获取如果一级缓存中不存在再从二级缓存中获取如果二级缓存也不存在这时候先将存储层的数据同步到缓存中再从缓存中获取。1.4Eureka Server常用配置#服务端开启自我保护模式 eureka.server.enable-self-preservationtrue #扫描失效服务的间隔时间单位毫秒默认是60*1000即60秒 eureka.server.eviction-interval-timer-in-ms 60000 #间隔多长时间清除过期的 delta 数据 eureka.server.delta-retention-timer-interval-in-ms0 #请求频率限制器 eureka.server.rate-limiter-burst-size10 #是否开启请求频率限制器 eureka.server.rate-limiter-enabledfalse #请求频率的平均值 eureka.server.rate-limiter-full-fetch-average-rate100 #是否对标准的client进行频率请求限制。如果是false则只对非标准client进行限制 eureka.server.rate-limiter-throttle-standard-clientsfalse #注册服务、拉去服务列表数据的请求频率的平均值 eureka.server.rate-limiter-registry-fetch-average-rate500 #设置信任的client list eureka.server.rate-limiter-privileged-clients #在设置的时间范围类期望与client续约的百分比。 eureka.server.renewal-percent-threshold0.85 #多长时间更新续约的阈值 eureka.server.renewal-threshold-update-interval-ms0 #对于缓存的注册数据多长时间过期 eureka.server.response-cache-auto-expiration-in-seconds180 #多长时间更新一次缓存中的服务注册数据 eureka.server.response-cache-update-interval-ms0 #缓存增量数据的时间以便在检索的时候不丢失信息 eureka.server.retention-time-in-m-s-in-delta-queue0 #当时间戳不一致的时候是否进行同步 eureka.server.sync-when-timestamp-differstrue #是否采用只读缓存策略只读策略对于缓存的数据不会过期。 eureka.server.use-read-only-response-cachetrue ################server node 与 node 之间关联的配置#####################33 #发送复制数据是否在request中总是压缩 eureka.server.enable-replicated-request-compressionfalse #指示群集节点之间的复制是否应批处理以提高网络效率。 eureka.server.batch-replicationfalse #允许备份到备份池的最大复制事件数量。而这个备份池负责除状态更新的其他事件。可以根据内存大小超时和复制流量来设置此值得大小 eureka.server.max-elements-in-peer-replication-pool10000 #允许备份到状态备份池的最大复制事件数量 eureka.server.max-elements-in-status-replication-pool10000 #多个服务中心相互同步信息线程的最大空闲时间 eureka.server.max-idle-thread-age-in-minutes-for-peer-replication15 #状态同步线程的最大空闲时间 eureka.server.max-idle-thread-in-minutes-age-for-status-replication15 #服务注册中心各个instance相互复制数据的最大线程数量 eureka.server.max-threads-for-peer-replication20 #服务注册中心各个instance相互复制状态数据的最大线程数量 eureka.server.max-threads-for-status-replication1 #instance之间复制数据的通信时长 eureka.server.max-time-for-replication30000 #正常的对等服务instance最小数量。-1表示服务中心为单节点。 eureka.server.min-available-instances-for-peer-replication-1 #instance之间相互复制开启的最小线程数量 eureka.server.min-threads-for-peer-replication5 #instance之间用于状态复制开启的最小线程数量 eureka.server.min-threads-for-status-replication1 #instance之间复制数据时可以重试的次数 eureka.server.number-of-replication-retries5 #eureka节点间间隔多长时间更新一次数据。默认10分钟。 eureka.server.peer-eureka-nodes-update-interval-ms600000 #eureka服务状态的相互更新的时间间隔。 eureka.server.peer-eureka-status-refresh-time-interval-ms0 #eureka对等节点间连接超时时间 eureka.server.peer-node-connect-timeout-ms200 #eureka对等节点连接后的空闲时间 eureka.server.peer-node-connection-idle-timeout-seconds30 #节点间的读数据连接超时时间 eureka.server.peer-node-read-timeout-ms200 #eureka server 节点间连接的总共最大数量 eureka.server.peer-node-total-connections1000 #eureka server 节点间连接的单机最大数量 eureka.server.peer-node-total-connections-per-host10 #在服务节点启动时eureka尝试获取注册信息的次数 eureka.server.registry-sync-retries #在服务节点启动时eureka多次尝试获取注册信息的间隔时间 eureka.server.registry-sync-retry-wait-ms #当eureka server启动的时候不能从对等节点获取instance注册信息的情况应等待多长时间。 eureka.server.wait-time-in-ms-when-sync-empty01.5Eureka Client 常用配置#该客户端是否可用 eureka.client.enabledtrue #实例是否在eureka服务器上注册自己的信息以供其他服务发现默认为true eureka.client.register-with-eurekafalse #此客户端是否获取eureka服务器注册表上的注册信息默认为true eureka.client.fetch-registryfalse #是否过滤掉非UP的实例。默认为true eureka.client.filter-only-up-instancestrue #与Eureka注册服务中心的通信zone和url地址 eureka.client.serviceUrl.defaultZonehttp://${eureka.instance.hostname}:${server.port}/eureka/ #client连接Eureka服务端后的空闲等待时间默认为30 秒 eureka.client.eureka-connection-idle-timeout-seconds30 #client连接eureka服务端的连接超时时间默认为5秒 eureka.client.eureka-server-connect-timeout-seconds5 #client对服务端的读超时时长 eureka.client.eureka-server-read-timeout-seconds8 #client连接all eureka服务端的总连接数默认200 eureka.client.eureka-server-total-connections200 #client连接eureka服务端的单机连接数量默认50 eureka.client.eureka-server-total-connections-per-host50 #执行程序指数回退刷新的相关属性是重试延迟的最大倍数值默认为10 eureka.client.cache-refresh-executor-exponential-back-off-bound10 #执行程序缓存刷新线程池的大小默认为5 eureka.client.cache-refresh-executor-thread-pool-size2 #心跳执行程序回退相关的属性是重试延迟的最大倍数值默认为10 eureka.client.heartbeat-executor-exponential-back-off-bound10 #心跳执行程序线程池的大小,默认为5 eureka.client.heartbeat-executor-thread-pool-size5 # 询问Eureka服务url信息变化的频率s默认为300秒 eureka.client.eureka-service-url-poll-interval-seconds300 #最初复制实例信息到eureka服务器所需的时间s默认为40秒 eureka.client.initial-instance-info-replication-interval-seconds40 #间隔多长时间再次复制实例信息到eureka服务器默认为30秒 eureka.client.instance-info-replication-interval-seconds30 #从eureka服务器注册表中获取注册信息的时间间隔s默认为30秒 eureka.client.registry-fetch-interval-seconds30 # 获取实例所在的地区。默认为us-east-1 eureka.client.regionus-east-1 #实例是否使用同一zone里的eureka服务器默认为true理想状态下eureka客户端与服务端是在同一zone下 eureka.client.prefer-same-zone-eurekatrue # 获取实例所在的地区下可用性的区域列表用逗号隔开。AWS eureka.client.availability-zones.chinadefaultZone,defaultZone1,defaultZone2 #eureka服务注册表信息里的以逗号隔开的地区名单如果不这样返回这些地区名单则客户端启动将会出错。默认为null eureka.client.fetch-remote-regions-registry #服务器是否能够重定向客户端请求到备份服务器。 如果设置为false服务器将直接处理请求如果设置为true它可能发送HTTP重定向到客户端。默认为false eureka.client.allow-redirectsfalse #客户端数据接收 eureka.client.client-data-accept #增量信息是否可以提供给客户端看默认为false eureka.client.disable-deltafalse #eureka服务器序列化/反序列化的信息中获取“_”符号的的替换字符串。默认为“__“ eureka.client.escape-char-replacement__ #eureka服务器序列化/反序列化的信息中获取“$”符号的替换字符串。默认为“_-” eureka.client.dollar-replacement_- #当服务端支持压缩的情况下是否支持从服务端获取的信息进行压缩。默认为true eureka.client.g-zip-contenttrue #是否记录eureka服务器和客户端之间在注册表的信息方面的差异默认为false eureka.client.log-delta-difffalse # 如果设置为true,客户端的状态更新将会点播更新到远程服务器上默认为true eureka.client.on-demand-update-status-changetrue #此客户端只对一个单一的VIP注册表的信息感兴趣。默认为null eureka.client.registry-refresh-single-vip-address #client是否在初始化阶段强行注册到服务中心默认为false eureka.client.should-enforce-registration-at-initfalse #client在shutdown的时候是否显示的注销服务从服务中心默认为true eureka.client.should-unregister-on-shutdowntrue1.6Eureka Instance 常用配置#服务注册中心实例的主机名 eureka.instance.hostnamelocalhost #注册在Eureka服务中的应用组名 eureka.instance.app-group-name #注册在的Eureka服务中的应用名称 eureka.instance.appname #该实例注册到服务中心的唯一ID eureka.instance.instance-id #该实例的IP地址 eureka.instance.ip-address #该实例相较于hostname是否优先使用IP eureka.instance.prefer-ip-addressfalse1.7Eureka注册中心组成Eureka属于AP架构高可用。1.8EurekaServer端源码–流程图1找到eurekaserver jar包spi机制找到其中的sping.factories文件找到server服务端自动装配的类EurekaServerAutoConfiguration2此配置类中会配置一些BeanEurekaServerConfig—初始化eureka相关配置EurekaController—初始化接口获取eurekaServer信息PeerAwareInstanceRegistry—初始化集群注册表PeerEurekaNodes—初始化集群节点集合EurekaServerContext—基于eurekaServer注册配置表集群节点集合及初始化eurekaServer上下文EurekaServerBootstrap—启动eureka服务FilterRegistrationBean—初始化Jersey过滤器3此配置类中Import注解的实现类EurekaServerInitializerConfiguration实现了SmartLifecycle接口并且定义了isAutoStartup返回trueSmartLifecycle继承自Lifecycle就会调用到Lifecycle的start方法。SmartLifecycle继承了LifecycleLifecycleProcessor继承了Lifecycle并且其中定义了onRefresh方法在spring启动时刷新上下文时候就会调用到此方法。调用链路如下spring启动刷新上下文------this.finishRefresh();LifecycleProcessor().onRefresh();this.startBeans(true);DefaultLifecycleProcessor.this.doStartstartstart方法中新起了一个线程启动eureka服务初始化上下文------this.eurekaServerBootstrap.contextInitialized(this.servletContext);发布eureka注册有效事件------this.publish(new EurekaRegistryAvailableEvent(this.getEurekaServerConfig()));发布eureka服务启动成功事件------this.publish(new EurekaServerStartedEvent(this.getEurekaServerConfig()));contextInitialized方法中初始化eureka环境------this.initEurekaEnvironment();初始化上下文------this.initEurekaServerContext();初始化上下文------this.initEurekaServerContext()中------服务同步replicate从相邻节点复制注册表------this.registry.syncUp();获取相邻节点注册的服务实例------Applications apps this.eurekaClient.getApplications();注册到本地------this.register(instance, instance.getLeaseInfo().getDurationInSecs(), true);初始化上下文------this.initEurekaServerContext()中------服务剔除evictregistry.openForTraffic(this.applicationInfoManager, registryCount);修改服务实例状态为UP------applicationInfoManager.setInstanceStatus(InstanceStatus.UP);开启一个定时任务每隔60s清理没有续约的任务------postInit();定时任务------this.evictionTimer.schedule((TimerTask)this.evictionTaskRef.get(), this.serverConfig.getEvictionIntervalTimerInMs(), this.serverConfig.getEvictionIntervalTimerInMs());定时任务run方法-----EvictionTask.run()------com.netflix.eureka.registry.AbstractInstanceRegistry.EvictionTask#run1.9EurekaClient端源码–流程图1.9.1如何开启服务注册进Eureka服务EurekaClient的开启可以使用EnableDiscoveryClient注解也可以不使用这个注解因为这个注解中的属性autoRegister默认为TRUE,如果我们不想某个服务注册进Eureka可以将这个属性设置为falseEnableDiscoveryClient(autoRegister false)1.9.2Client端流程EurekaClientAutoConfiguration中注解AutoConfigureAfter中含有一个类EurekaDiscoveryClientConfiguration这个类中会注入一个bean-Marker。此Marker正是EurekaClientAutoConfiguration所需要的只有这个bean存在springboot才会在启动的时候注册这个EurekaClientAutoConfiguration-Bean.EurekaClientAutoConfiguration中配置了很多的BeanEurekaClientConfigBean------初始化EurekaClient相关配置EurekaInstanceConfigBean------client实例相关配置信息EurekaServiceRegistry------服务注册EurekaHealthIndicator------心跳检测EurekaClient其中EurekaClient很重要EurekaClient是对DiscoveryClient包装。DiscoveryClient的构造函数中会初始化各种任务在initScheduledTasks中会初始化定时拉取服务注册信息更新本地注册表和服务续约任务初始化定时服务注册任务。后续具体流程请查看流程图。二Nacos2.1安装并运行Nacos本地Java8Maven环境先从官网下载Nacoshttps://github.com/alibaba/nacos/releases或者https://nacos.io/download/nacos-server/?spm5238cd80.2ef5001f.0.0.3f613b7cHmXbuy解压安装包直接运行bin目录下的打开cmd执行命令具体文档见https://nacos.io/docs/v2.4/quickstart/quick-start/启动命令(standalone代表着单机模式运行非集群模式):startup.cmd -m standalone命令运行成功后直接访问http://localhost:8848/nacos、默认账号密码都是nacos2.2服务注册中心对比2.1.1Nacos 支持AP和CP模式的切换C是所有节点在同一时间看到的数据是一致的而A的定义是所有的请求都会收到响应。C:Consistency强一致性A:Availability可用性P:Partition tolerance分区容错性CAP理论关注粒度是数据而不是整体系统设计的策略//-----------------------------------------------------------------------------------------------------最多只能同时较好的满足两个。CAP理论的核心是一个分布式系统不可能同时很好的满足一致性可用性和分区容错性这三个需求因此根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类CA - 单点集群满足一致性可用性的系统通常在可扩展性上不太强大。CP - 满足一致性分区容忍必的系统通常性能不是特别高。AP - 满足可用性分区容忍性的系统通常可能对一致性要求低一些。2.1.2何时选择使用何种模式一般来说如果不需要存储服务级别的信息且服务实例是通过nacos-client注册并能够保持心跳上报那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务都适用于AP模式AP模式为了服务的可能性而减弱了一致性因此AP模式下只支持注册临时实例。如果需要在服务级别编辑或者存储配置信息那么 CP 是必须K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例此时则是以 Raft 协议为集群运行模式该模式下注册实例之前必须先注册服务如果服务不存在则会返回错误。2.3Nacos服务注册与发现实战代码git地址--https://gitee.com/zhang_xiaosong/Spring-Cloud-Alibaba/tree/master/services2.3.1服务注册可以在idea通过启动不同的端口号来模拟集群2.3.2服务发现简单的代码测试DisCoveryTestSpringBootTest public class DisCoveryTest { Resource private DiscoveryClient discoveryClient; Resource private NacosServiceDiscovery nacosServiceDiscovery; Test public void disCoveryClientTest() throws Exception { for (String service : discoveryClient.getServices()) { System.out.println(service: service); for (ServiceInstance instance : discoveryClient.getInstances(service)) { System.out.println(ip: instance.getHost() -port: instance.getPort()); } } } Test public void nacosServiceDiscoveryTest() throws Exception { for (String service : nacosServiceDiscovery.getServices()) { System.out.println(service: service); for (ServiceInstance instance : nacosServiceDiscovery.getInstances(service)) { System.out.println(ip: instance.getHost() -port: instance.getPort()); } } } }输出结果2.3.3服务调用-手搓版product服务ProductController/** * description: * author: zhangguisong * date: 2026/3/25 19:21 */ RestController RequestMapping(/product) public class ProductController { Resource private ProductService productService; GetMapping(/detail/{id}) public Product getProduct(PathVariable(id) Integer id) { return productService.getDetailById(id); } }ProductServicepublic interface ProductService { Product getDetailById(Integer id); }ProductServiceImplService public class ProductServiceImpl implements ProductService { Override public Product getDetailById(Integer id) { Product product new Product(); product.setId(id); product.setName(product id); product.setPrice(new BigDecimal(10)); return product; } }order服务OrderControllerRestController RequestMapping(/order) public class OrderController { Resource private OrderService orderService; PostMapping(/add) public String add() { orderService.createOrder(); return ok; } }OrderServicepublic interface OrderService { void createOrder(); }OrderServiceImpl/** * description: * author: zhangguisong * date: 2026/3/25 19:34 */ Service Slf4j public class OrderServiceImpl implements OrderService { Resource private RestTemplate restTemplate; Resource private DiscoveryClient discoveryClient; Override public void createOrder() { Order order new Order(); order.setProductId(5); Product product getProduct(order.getProductId()); order.setOrderNo(orderNo); order.setNum(2); order.setTotalPrice(product.getPrice().multiply(new BigDecimal(order.getNum()))); order.setUserName(userName); order.setProductList(Collections.singletonList(product)); System.out.println(order); } private Product getProduct(Integer id) { //获取商品服务的所有的机器 ipport ListServiceInstance instances discoveryClient.getInstances(service-product); log.info(instances:{}, JSONUtil.toJsonStr(instances)); ServiceInstance serviceInstance instances.get(0); //远程URL String url http:// serviceInstance.getHost() : serviceInstance.getPort() /product/detail/ id; log.info(url:{}, url); //发送远程请求 return restTemplate.getForObject(url, Product.class); } //TODO zgs 2026/3/25 20:22 desc完成负载均衡的调用 private Product getProductBalance(Integer id) { return null; } }2.3.4服务调用-手搓负载均衡版OrderServiceImpl/** * description: * author: zhangguisong * date: 2026/3/25 19:34 */ Service Slf4j public class OrderServiceImpl implements OrderService { Resource private RestTemplate restTemplate; Resource private DiscoveryClient discoveryClient; Resource private LoadBalancerClient loadBalancerClient; Override public void createOrder() { Order order new Order(); order.setProductId(5); Product product getProductBalance(order.getProductId()); order.setOrderNo(orderNo); order.setNum(2); order.setTotalPrice(product.getPrice().multiply(new BigDecimal(order.getNum()))); order.setUserName(userName); order.setProductList(Collections.singletonList(product)); System.out.println(order); } private Product getProduct(Integer id) { //获取商品服务的所有的机器 ipport ListServiceInstance instances discoveryClient.getInstances(service-product); log.info(instances:{}, JSONUtil.toJsonStr(instances)); ServiceInstance serviceInstance instances.get(0); //远程URL String url http:// serviceInstance.getHost() : serviceInstance.getPort() /product/detail/ id; log.info(url:{}, url); //发送远程请求 return restTemplate.getForObject(url, Product.class); } //TODO zgs 2026/3/25 20:22 desc完成负载均衡的调用 private Product getProductBalance(Integer id) { ServiceInstance choose loadBalancerClient.choose(service-product); String url http:// choose.getHost() : choose.getPort() /product/detail/ id; log.info(loadbalancer-url:{}, url); return restTemplate.getForObject(url, Product.class); } }从控制台就可以看出现在是轮训调用的了2.3.5服务调用-基于注解的负载均衡OrderConfig增加LoadBalanced注解Configuration public class OrderConfig { LoadBalanced Bean public RestTemplate restTemplate() { return new RestTemplate(); } }OrderServiceImpl/** * description: * author: zhangguisong * date: 2026/3/25 19:34 */ Service Slf4j public class OrderServiceImpl implements OrderService { Resource private RestTemplate restTemplate; Resource private DiscoveryClient discoveryClient; Resource private LoadBalancerClient loadBalancerClient; Override public void createOrder() { Order order new Order(); order.setProductId(5); Product product getProductAnnotation(order.getProductId()); order.setOrderNo(orderNo); order.setNum(2); order.setTotalPrice(product.getPrice().multiply(new BigDecimal(order.getNum()))); order.setUserName(userName); order.setProductList(Collections.singletonList(product)); System.out.println(order); } private Product getProduct(Integer id) { //获取商品服务的所有的机器 ipport ListServiceInstance instances discoveryClient.getInstances(service-product); log.info(instances:{}, JSONUtil.toJsonStr(instances)); ServiceInstance serviceInstance instances.get(0); //远程URL String url http:// serviceInstance.getHost() : serviceInstance.getPort() /product/detail/ id; log.info(url:{}, url); //发送远程请求 return restTemplate.getForObject(url, Product.class); } //TODO zgs 2026/3/25 20:22 desc完成负载均衡的调用 private Product getProductBalance(Integer id) { ServiceInstance choose loadBalancerClient.choose(service-product); String url http:// choose.getHost() : choose.getPort() /product/detail/ id; log.info(loadbalancer-url:{}, url); return restTemplate.getForObject(url, Product.class); } //TODO zgs 2026/3/25 20:45 desc基于注解的负载均衡 private Product getProductAnnotation(Integer id) { //service-product会被动态替换 String url http://service-product/product/detail/ id; return restTemplate.getForObject(url, Product.class); } }2.3.6本地缓存注册中心什么是 Nacos 本地缓存Nacos 本地缓存是指客户端将从 Nacos 服务器获取的服务列表、配置信息等数据缓存在本地 JVM 内存中这样可以提高性能 - 减少网络请求快速读取缓存数据增强容错 - 即使 Nacos 服务器暂时不可用也能使用本地缓存继续工作降低负载 - 减少对 Nacos 服务器的压力本地缓存机制说明服务发现缓存Naming Cache位置{user.home}/nacos/naming/{namespace}/{serviceName}内容服务实例列表、健康状态、权重等更新方式定时拉取 UDP 推送配置缓存Config Cache位置{user.home}/nacos/config/{tenant}/{group}/{dataId}内容配置文件的 JSON/YAML 内容更新方式长轮询监听配置变化本地缓存的工作原理┌─────────────┐ ┌──────────────┐ ┌─────────────┐│ 应用启动 │ ────── │ 读取本地缓存 │ ────── │ 快速启动 │└─────────────┘ └──────────────┘ └─────────────┘↓ ↓┌─────────────┐ ┌──────────────┐ ┌─────────────┐│ 连接 Nacos │ ────── │ 同步最新数据 │ ────── │ 更新缓存 │└─────────────┘ └──────────────┘ └─────────────┘↓ ↓┌─────────────┐ ┌──────────────┐│ 监听变化 │ ────── │ 实时刷新缓存 │└─────────────┘ └──────────────┘2.4Nacos-配置中心实战2.4.1引入配置中心依赖dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependency2.4.2在配置文件中设置配置spring: application: name: service-order config: import: - nacos:service-order.yml - optional:nacos:service-order.yml cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # 助力这里不能写掉了 否则会报错找不到配置中心的地址 config: server-addr: 127.0.0.1:8848 file-extension: yaml group: DEFAULT_GROUP server: port: 80002.4.3在nacos控制台中去新建配置order: timeout: 30min auto-confirm: 7d2.4.4通过代码获取Nacos中的配置信息OrderControllerGetMapping(/config) public String config() { return orderService.getConfig(); }OrderServiceImplValue(${order.timeout}) private String timeout; Value(${order.auto-confirm}) private String autoConfirm; Override public String getConfig() { log.info(配置{}-{}, timeout, autoConfirm); return 配置 timeout - autoConfirm; }可以看出能成功获取然后在nacos控制台修改配置后重新获取注意这里想要实时刷新需要配合注解使用RefreshScopeService Slf4j RefreshScope public class OrderServiceImpl implements OrderService { Override public String getConfig() { log.info(配置{}-{}, timeout, autoConfirm); return 配置 timeout - autoConfirm; } }可以看出是获取到了的从控制台打印也能看到是监听到了config的更新的对于不需要使用nacos引入检查的可以加配置来跳过这个检查 否则启动会报错spring: application: name: service-product cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: import-check: enabled: false # 关闭nacos配置导入检查 server: port: 80012.4.5更方便的动态刷新原先使用ValueRefreshScope来实现动态刷新是不太方便的应为如果有很多的配置 那岂不是要写很多的Value所以就有了下面的这种方式来更快捷的实现动态刷新。OrderPropertiespackage com.zgs.order.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * description: 订单的配置类 * author: zgs * date: 2026/3/25 22:05 */ Component Data ConfigurationProperties(prefix order) public class OrderProperties { private String timeout; private String autoConfirm; }OrderServiceImplService Slf4j //RefreshScope public class OrderServiceImpl implements OrderService { Resource private OrderProperties orderProperties; // Value(${order.timeout}) // private String timeout; // // Value(${order.auto-confirm}) // private String autoConfirm; Override public String getConfig() { // log.info(配置{}-{}, timeout, autoConfirm); // return 配置 timeout - autoConfirm; log.info(配置{}-{}, orderProperties.getTimeout(), orderProperties.getAutoConfirm()); return 配置 orderProperties.getTimeout() - orderProperties.getAutoConfirm(); } }去nacos控制台更新配置后再次请求接口控制台输出结果2.4.6NacosConfigManager实现监听配置变化