二、Nacos-注册中心(服务注册与发现)
文章目录
nacos是什么
Nacos 注册中心用于统一保存和管理微服务实例信息,让服务可以通过服务名找到其他服务,并结合健康检查和负载均衡完成可靠的服务调用。
nacos的工作原理

以上面单机版 Nacos 为例,假设系统中存在一个服务提供者 (Provider)有一个订单服务 order-service,以及一个服务消费者(Consumer)有一个用户服务 user-service。用户服务需要通过远程调用访问订单服务。
-
服务提供者 Provider 启动并读取注册配置
当订单服务 Provider 启动时,Spring Boot 会先完成应用初始化,同时读取项目中的 Nacos 注册中心配置。
这些配置通常包括 Nacos Server 地址、服务名称、命名空间、分组、集群名称等信息。例如,订单服务的服务名称为 order-service,Nacos 地址为 127.0.0.1:8848,命名空间为 prod,分组为 DEFAULT_GROUP。
除了项目中配置的信息,Nacos 客户端还会获取当前服务实例自身的运行信息,例如当前服务器 IP 地址为 192.168.1.10,应用端口为 8081,实例权重为 1.0,实例是否启用以及实例所属集群等。
此时,Nacos 客户端还可以携带一些自定义元数据。元数据就是对服务实例的补充说明,例如:
version=v1,表示当前实例属于 v1 版本; environment=prod,表示当前实例属于生产环境; region=tokyo,表示当前实例部署在东京区域; protocol=http,表示当前实例使用 HTTP 协议提供服务。因此,订单服务启动后准备注册的信息大致包括:服务名称 order-service、IP 地址 192.168.1.10、端口 8081、权重 1.0、集群名称、命名空间、分组以及版本、环境、区域等元数据。
-
Provider 向 Nacos 服务注册中心发送注册请求
订单服务收集完自身信息后,内置的 Nacos 客户端会向 Nacos Server 发送服务注册请求。这个注册请求的核心含义就是告诉 Nacos:
“我是一台名为 order-service 的服务实例,我的 IP 地址是 192.168.1.10,端口是 8081,我当前可以对外提供服务,请把我的地址记录下来。”
Provider 不需要由开发人员手动调用注册接口。只要项目正确引入 Nacos 服务发现依赖,并配置好 Nacos Server 地址和服务名称,应用启动时,Nacos 客户端通常就会自动完成服务注册。
-
Nacos 服务注册中心接收并校验注册信息
Nacos Server 收到注册请求后,会先校验注册信息是否完整,例如检查服务名称是否为空、IP 地址是否合法、端口是否合法、命名空间是否存在以及实例参数是否正确。
校验通过后, Nacos 会根据命名空间、分组和服务名称确定这个实例属于哪个服务。
服务通常不是只通过服务名称进行区分,而是可以理解为由以下几个信息共同确定:
命名空间 + 分组 + 服务名称。
例如,同样都是 order-service,开发环境和生产环境可以放在不同的命名空间中。这样开发环境的消费者就不会错误地发现生产环境中的订单服务。
-
Nacos 创建服务并将实例加入服务注册表
如果 Nacos 中还不存在 order-service,Nacos 会先创建对应的服务记录,然后将 192.168.1.10:8081 这个实例加入该服务的实例列表。
如果 order-service 已经存在,Nacos 就直接在这个服务下面新增一个实例。
例如,订单服务部署了三台服务器,那么 Nacos 中可以形成这样的实例关系:
order-service 服务下面存在 192.168.1.10:8081、192.168.1.11:8081 和 192.168.1.12:8081 三个实例。
它们的服务名称完全相同,但是 IP 地址不同。对于消费者来说,这三台服务器都能够提供订单服务。
Nacos 会维护每个实例的 IP、端口、权重、健康状态、启用状态、集群名称和元数据等信息。
在运行过程中,服务注册表通常会保存在 Nacos Server 的内存结构中,以便快速完成服务查询。部分需要长期保存的数据还会根据 Nacos 的部署方式和实例类型写入持久化存储。
-
Nacos 返回服务注册结果
Nacos 成功保存实例后,会向订单服务返回注册成功结果。订单服务收到成功响应后,就表示当前服务实例已经进入 Nacos 服务注册表,其他服务可以通过 order-service 这个服务名称发现它。
如果注册失败,例如 Nacos Server 无法连接、配置错误或者网络异常,Nacos 客户端通常会记录错误日志,并根据客户端机制尝试重新连接和注册。
-
Provider 注册成功后开始心跳续约或连接保活
服务注册成功并不代表注册过程永久结束。
因为 Nacos 需要知道订单服务是否还在正常运行,所以 Provider 还要持续向 Nacos 证明自己处于存活状态。
在常见的临时实例模式下,Nacos 客户端会定期发送心跳,或者通过长连接保活机制维持客户端与 Nacos Server 之间的连接。
心跳中通常会携带服务名称、实例 IP、端口、集群、命名空间等信息。
每次 Nacos 收到心跳后,都会更新该实例最近一次存活时间,并将该实例保持为健康状态。
心跳续约可以理解为订单服务定期对 Nacos 说:“我是 192.168.1.10:8081,我还在正常运行,请继续把我保留在可用实例列表中。”
-
Nacos 根据心跳和健康检查维护实例状态
如果 Nacos 能够持续收到订单服务的心跳,或者客户端长连接一直正常,Nacos 就认为该实例仍然健康。
如果订单服务突然宕机、服务器断电、网络中断或者应用进程被终止,Nacos 将无法继续收到该实例的心跳。
当实例超过一定时间没有心跳后,Nacos 会认为该实例可能已经出现故障,并将实例状态由健康修改为不健康。
实例被标记为不健康后,一般不会再被作为正常实例提供给消费者,防止消费者继续调用已经故障的服务器。
如果实例在更长时间内仍然没有恢复,临时实例可能会被 Nacos 从服务注册表中删除。
整个过程可以理解为:正常接收心跳时,实例保持健康;心跳超时时,实例被标记为不健康;长时间没有恢复时,实例可能被清理。
除了由客户端主动发送心跳,Nacos 在部分模式下也可以通过主动探测等方式检查实例是否正常。
-
服务消费者 Consumer 启动并订阅目标服务
用户服务 user-service 启动时,也会连接 Nacos Server。
但是用户服务在调用订单服务时,并不需要提前知道订单服务部署在哪一台服务器,也不需要在配置文件中写死 192.168.1.10:8081。
用户服务只需要知道目标服务的名称是 order-service。
用户服务中的 Nacos 客户端会向 Nacos 服务发现模块查询或订阅 order-service,表示:
“我需要调用 order-service,请告诉我这个服务目前有哪些可以使用的实例,并在实例发生变化时通知我。”
-
Consumer 发起远程调用时,根据服务名称查找实例
当用户服务准备调用订单服务时,程序中使用的是订单服务的服务名称,而不是固定 IP 地址。
例如,消费者要调用的逻辑目标是:http://order-service/order/query
这里的 order-service 并不是一个真实的域名,而是注册到 Nacos 中的服务名称。
消费者侧的服务发现组件会根据这个服务名称查找对应的实例列表。
如果消费者本地还没有 order-service 的实例信息,或者本地缓存需要更新,Nacos 客户端就会向 Nacos Server 查询这个服务。
需要注意的是,消费者通常不是每发送一次业务请求都访问一次 Nacos。第一次查询成功后,实例列表会被保存到消费者本地缓存中,后续调用通常直接使用本地实例列表。
-
Nacos 服务发现模块查询服务注册表
Nacos 收到 order-service 的查询请求后,会从服务注册表中找到这个服务下面的全部实例。
假设此时存在以下三个实例:
192.168.1.10:8081,健康状态为正常; 192.168.1.11:8081,健康状态为正常; 192.168.1.12:8081,由于心跳超时,健康状态为异常。Nacos 会根据消费者所在的命名空间、分组、集群以及查询条件进行筛选。
通常只有命名空间和分组匹配、实例已经启用并且健康状态正常的实例,才适合返回给消费者。
因此,Nacos 最终可能只返回 192.168.1.10:8081 和 192.168.1.11:8081 两个健康实例,而不会把已经异常的 192.168.1.12:8081 作为正常实例提供给消费者。
-
Nacos 将可用实例列表返回给 Consumer
Nacos 会将查询到的服务实例列表返回给用户服务。
返回的信息不仅包括 IP 地址和端口,还可能包括实例权重、健康状态、集群名称、启用状态以及实例元数据。
例如,Nacos 返回两个订单服务实例:
第一个实例的地址为 192.168.1.10:8081,版本为 v1,权重为 1.0; 第二个实例的地址为 192.168.1.11:8081,版本为 v1,权重为 2.0。这些信息可以帮助消费者进行实例选择。例如,消费者可以优先选择相同集群的实例,也可以根据权重让性能更好的服务器承担更多请求。
-
Consumer 将实例列表保存到本地缓存
用户服务收到实例列表后,会将这些实例保存到本地缓存中。
本地缓存非常重要,因为消费者调用订单服务的频率可能很高。如果每一次调用都先访问 Nacos 查询实例,会产生大量额外网络请求,也会给 Nacos Server 带来较大压力。
保存本地缓存后,用户服务后续调用订单服务时,可以直接从内存中获取 order-service 的实例列表。
本地缓存还有一个作用:如果 Nacos Server 短时间内出现故障,消费者仍然可以暂时使用之前缓存的实例地址继续调用服务。
但是本地缓存中的实例可能不是永远有效的,因此消费者还需要订阅服务变化,并在实例发生变化时及时更新缓存。
-
Consumer 使用负载均衡选择一个 Provider 实例
用户服务得到两个订单服务实例后,不能同时把一个请求发送给所有实例,而是需要通过负载均衡算法选择其中一个。
常见的负载均衡方式包括轮询、随机、加权随机、加权轮询以及优先选择同集群实例等。
例如,当前存在:
192.168.1.10:8081,权重为 1.0; 192.168.1.11:8081,权重为 2.0。那么负载均衡组件可能让第二个实例承担更多请求。
假设本次请求最终选择了 192.168.1.10:8081,消费者就会把原来的逻辑地址:http://order-service/order/query转换为真实地址:http://192.168.1.10:8081/order/query
-
Consumer 直接向 Provider 发送业务请求
选出具体实例后,用户服务会直接向订单服务发送 HTTP、RPC 或其他协议的远程调用请求。此时实际的业务调用链路是:Consumer 直接调用 Provider。Nacos 不负责转发业务请求,也不会成为每一次业务请求的中转站。
Nacos 只负责保存服务地址、发现服务实例、维护健康状态以及通知实例变化。
因此,Nacos 参与的是“调用之前如何找到目标服务”, 而不是“替消费者转发业务请求”。
如果每个请求都必须经过 Nacos 转发,Nacos 就会成为整个系统的流量瓶颈。实际上,消费者拿到地址后会直接访问服务提供者。
-
Provider 实例上线、下线或故障时,Nacos 更新注册表
在系统运行过程中,订单服务的实例数量可能发生变化。例如,系统访问量增加时,新启动了一台订单服务 192.168.1.13:8081。这个新实例启动后会按照前面的注册流程,将自身信息注册到 Nacos。Nacos 会把它加入 order-service 的实例列表。
如果某台订单服务正常关闭,Nacos 客户端可以在应用关闭前主动向 Nacos 发送注销请求。Nacos 收到注销请求后,会将该实例从服务注册表中移除。
如果某台订单服务突然宕机,来不及主动注销,Nacos 就会通过心跳超时或连接断开检测到异常,然后将其标记为不健康,并在必要时删除。
因此,服务注册表不是固定不变的,而是会随着服务实例的启动、停止和故障不断更新。
-
Nacos 将服务实例变化通知给 Consumer
用户服务在查询 order-service 后,通常还会订阅这个服务的变化。
当订单服务增加新实例、实例主动下线、实例心跳超时、健康状态变化、权重变化或者元数据变化时,Nacos 会更新内部服务注册表。
注册表变化后,Nacos 会通过服务变更通知机制告诉已经订阅 order-service 的消费者。
消费者收到通知后,会重新获取最新实例列表,或者使用通知中携带的新实例信息更新本地缓存。
例如,原来本地缓存中存在: 192.168.1.10:8081 和 192.168.1.11:8081。
当 192.168.1.10:8081 宕机后,Nacos 将其标记为不健康,并通知用户服务。
用户服务更新本地缓存后,只保留 192.168.1.11:8081,后续请求就不会继续发送给已经宕机的实例。
通过这种机制,即使微服务实例不断扩容、缩容或发生故障,消费者也能及时获得最新的可用服务地址。
-
管理员通过 Nacos 控制台管理服务
除了 Provider 和 Consumer,管理员还可以通过 Nacos 控制台查看和管理服务。
管理员可以在控制台中查看当前注册了哪些服务、每个服务有多少实例、实例的 IP 和端口、实例是否健康、实例权重以及实例元数据等内容。
管理员也可以手动修改实例权重、将实例设置为不启用,或者查看服务所在的命名空间和分组。
控制台本身并不是独立保存这些数据,而是调用 Nacos Server 的管理功能,对服务注册中心中的数据进行查询和修改。
-
管理员通过 Nacos 配置中心发布配置
除了服务注册和服务发现,Nacos 还可以作为配置中心使用。
管理员可以通过 Nacos 控制台创建配置。一份配置通常由 Namespace、Group 和 Data ID 共同确定。例如:
Namespace 为 prod; Group 为 DEFAULT_GROUP; Data ID 为 order-service.yaml。配置内容可能为:
order.timeout=3000,表示订单接口调用超时时间为 3000 毫秒; order.maxRetry=3,表示订单调用失败后最多重试 3 次; order.discountEnabled=true,表示是否开启优惠功能。管理员点击发布后,控制台会将配置提交给 Nacos Server。
-
Nacos 配置管理模块保存配置
Nacos 配置管理模块收到配置后,会校验 Data ID、Group、Namespace 和配置内容。
校验通过后,Nacos 会保存配置内容,并记录配置的修改时间、版本信息和内容摘要等数据。
配置数据通常会写入嵌入式数据库或 MySQL 等持久化存储中,防止 Nacos Server 重启后配置丢失。
同时,Nacos 还可以在内存或本地文件中保存部分配置缓存,提高配置查询速度。
Nacos 也会记录配置变更日志,方便管理员查看配置历史或者在配置错误时进行回滚。
-
微服务启动时从 Nacos 获取配置
订单服务启动时,除了向 Nacos 注册服务,还可以从 Nacos 配置中心获取远程配置。
订单服务会根据 Namespace、Group 和 Data ID 查找自己的配置,例如获取 prod 命名空间、DEFAULT_GROUP 分组下的 order-service.yaml。
Nacos Server 查询到配置后,将配置内容返回给订单服务。
订单服务收到配置后,会把这些配置加载到 Spring 环境中,供项目中的 Bean 和业务代码使用。
这样,数据库连接参数、接口超时时间、业务开关等内容就不需要全部写死在应用本地配置文件中,而可以由 Nacos 统一管理。
-
微服务监听 Nacos 配置变化
订单服务获取配置后,还会继续监听这份配置是否发生变化。
假设原来的超时时间为:order.timeout=3000。
管理员在 Nacos 控制台中将其修改为:order.timeout=5000。
配置发布后,Nacos 会发现配置内容发生变化,并通知正在监听这份配置的订单服务。
订单服务收到配置变化通知后,会重新从 Nacos 拉取最新配置,然后更新本地配置内容。
对于支持动态刷新的属性,新配置可以在应用不重启的情况下生效。对于不支持动态刷新的 Bean 或配置,可能仍然需要重新创建 Bean 或重启应用,具体取决于项目中的实现方式。
-
Nacos 对配置变化进行版本和日志管理
每次管理员发布配置时,Nacos 都可以记录本次变更。
记录内容可能包括修改前的配置、修改后的配置、修改时间、操作人员和配置版本等信息。
如果新发布的配置存在问题,例如将接口超时时间错误地设置为几十毫秒,管理员可以通过配置历史找到之前的正确版本并重新发布。
因此,Nacos 配置中心不仅负责保存配置,还负责配置发布、配置获取、配置监听、配置变更通知以及配置历史管理。
-
Nacos 使用缓存、数据库和日志存储数据
为了同时保证查询性能和数据可靠性,Nacos 会使用不同的存储方式管理数据。
服务实例列表、服务订阅关系以及一些频繁访问的数据,通常会放在内存或本地缓存中,这样消费者查询服务时能够快速获得结果。
配置、命名空间、用户、权限以及需要长期保存的数据,会写入嵌入式数据库或 MySQL 等持久化存储中,保证 Nacos 重启后仍然能够恢复。
Nacos 还会保存运行日志、错误日志、访问日志、配置变更日志和服务变更日志,方便管理员排查问题。
例如,某个服务注册失败时,可以通过日志查看是网络连接失败、服务参数错误,还是 Nacos Server 本身发生异常。
-
单机版 Nacos 由一个 Nacos Server 完成全部功能
在单机模式下,服务注册、服务发现、健康检查、配置管理、元数据管理和控制台管理都由同一个 Nacos Server 完成。
所有 Provider 和 Consumer 都连接这个 Nacos Server。
这种模式部署简单,适合本地学习、开发环境、测试环境和小型项目。
但是单机模式存在单点故障。如果唯一的 Nacos Server 宕机,新的 Provider 可能无法完成注册,Consumer 无法及时获取最新的服务变化,应用也无法从 Nacos 获取最新配置。
已经获取过实例列表的 Consumer,还能暂时使用本地缓存继续调用 Provider,但是无法及时感知新的实例上线、下线和健康状态变化。
Nacos 工作原理简述
-
当服务提供者 Provider 启动时,会将服务名称、IP 地址、端口、权重、集群以及版本、环境、区域等元数据注册到 Nacos 服务注册中心。
Nacos 收到注册请求后,会校验注册信息,根据命名空间、分组和服务名称找到对应服务,并将实例保存到服务注册表中。
注册成功后,Provider 会通过心跳或长连接保活机制持续向 Nacos 证明自己仍然存活。
Nacos 根据心跳和健康检查结果维护实例状态。能够正常续约的实例保持健康;长时间没有心跳的实例会被标记为不健康;继续长时间没有恢复的临时实例可能被删除。
-
当服务消费者 Consumer 需要远程调用某个服务时,会根据服务名称从 Nacos 服务发现模块获取健康实例列表。
Nacos 会从服务注册表中查询目标服务,过滤掉不健康、未启用或者不符合命名空间、分组和集群要求的实例,然后将可用实例列表返回给 Consumer。
Consumer 将实例列表保存到本地缓存中,再通过负载均衡算法选择一个具体实例,最后直接向 Provider 发起业务请求。Nacos 不负责转发业务流量,只负责提供服务地址。
-
当 Provider 新增、下线、故障或者恢复时,Nacos 会更新服务注册表,并把服务变化通知给已经订阅该服务的 Consumer。Consumer 收到通知后更新本地实例列表,从而避免继续调用失效实例。
-
在配置管理方面,管理员可以通过 Nacos 控制台发布配置。Nacos 将配置保存到持久化存储中,应用启动时从 Nacos 获取配置并持续监听配置变化。配置被修改后,Nacos 通知应用重新获取最新配置,从而实现统一配置管理和部分配置的动态更新。
这就是 Nacos 在单机模式下完成服务注册、心跳续约、健康检查、服务发现、负载均衡配合、服务变更通知以及配置管理的完整工作原理。
服务注册
服务注册就是:
微服务启动以后,把自己的服务名称、IP 地址、端口号等信息,主动告诉注册中心。 在 Nacos 中,服务注册可以理解为:
服务启动
↓
向 Nacos 报到
↓
Nacos 记录这个服务实例
就像一个新员工入职后,需要到公司人事系统登记自己的姓名、部门和联系方式。
为什么要注册服务?
假设商品服务部署在:
192.168.1.10:8081
订单服务需要调用商品服务,就必须知道这个地址。但是在微服务系统中,服务地址可能经常发生变化:
服务重启,端口变了
服务部署到另一台服务器,IP 变了
服务扩容,增加了新实例
某个服务实例宕机了
如果把服务地址直接写死:http://192.168.1.10:8081/product/1,地址一变,调用就会失败。因此,服务启动后需要把自己的地址注册到 Nacos,由 Nacos 统一管理。
服务注册时注册什么信息?
一个服务实例注册到 Nacos 时,通常会携带以下信息:
服务名称
IP 地址
端口号
分组
命名空间
集群名称
实例权重
健康状态
是否为临时实例
例如:
服务名称:service-product
IP 地址:192.168.1.10
端口号:8081
健康状态:健康
Nacos 中就会形成一条记录:
service-product
└── 192.168.1.10:8081
同一个服务可以注册多个实例,假设商品服务部署了三份:
商品服务实例1:192.168.1.10:8081
商品服务实例2:192.168.1.11:8081
商品服务实例3:192.168.1.12:8081
它们的服务名称都叫:service-product。注册到 Nacos 后:
service-product
├── 192.168.1.10:8081
├── 192.168.1.11:8081
└── 192.168.1.12:8081
这里要区分两个概念:
- 服务:
service-product - 服务实例:
192.168.1.10:8081
192.168.1.11:8081
192.168.1.12:8081
服务注册原理

-
当服务提供者 Provider 启动时,应用中的 Nacos Client 会随之完成初始化,并读取 Nacos 的相关配置,例如 Nacos Server 地址、服务名称、命名空间、分组、集群名称以及实例类型等信息。
-
Nacos Client 会收集并封装当前服务实例的信息,主要包括服务名称、IP 地址、端口、权重、命名空间、分组、集群以及元数据等。例如,订单服务可以注册为:服务名称
order-service、IP 地址192.168.1.10、端口8081、权重1.0,元数据为version=v1、environment=prod。其中,元数据可以用于区分服务版本、部署环境、机房区域或灰度实例。 -
服务实例信息封装完成后,Nacos Client 会向 Nacos Server 的 Open API 或客户端通信接口发送注册请求。这个请求的含义可以理解为:当前有一个名为
order-service的服务实例,运行在192.168.1.10:8081,现在可以正常对外提供服务,请将它加入服务注册表。 -
Nacos Server 的接入层收到注册请求后,会对请求参数进行校验,例如检查服务名称是否为空、IP 和端口是否合法、命名空间是否存在以及实例参数是否完整。如果参数不合法,Nacos 会返回注册失败信息;如果参数合法,则继续执行注册流程。
-
参数校验通过后,Nacos 会根据“命名空间、分组、服务名称、集群名称”等信息,确定当前实例应该属于哪个服务。例如,
prod命名空间、DEFAULT_GROUP分组下的order-service,与dev命名空间中的同名服务属于不同的服务范围,从而实现环境隔离。 -
如果 Nacos 中还不存在对应的服务,服务注册中心会先创建该服务;如果服务已经存在,则直接将当前实例加入该服务的实例列表。例如,一个
order-service服务下面可以同时保存192.168.1.10:8081、192.168.1.11:8081和192.168.1.12:8081等多个实例。 -
实例管理模块会保存该实例的详细信息,包括 IP、端口、权重、健康状态、启用状态、集群、分组和元数据等。注册成功时,实例通常会被标记为可用状态,后续消费者便可以通过服务名称发现这个实例。
-
Nacos 完成服务实例写入后,会向 Provider 返回注册成功响应。Provider 收到响应后,就表示当前实例已经成功加入 Nacos 服务注册表,其他消费者可以通过
order-service这个服务名称查询到它。 -
服务注册成功后,Provider 并不是只注册一次就结束。为了让 Nacos 知道该实例仍然存活,Nacos Client 会通过心跳续约或长连接保活机制,持续向 Nacos Server 上报实例状态。心跳的含义就是告诉 Nacos:当前实例仍然在线,可以继续对外提供服务。
-
Nacos 的心跳检测模块收到心跳后,会更新该实例最近一次存活时间,并继续将实例标记为健康状态。只要心跳能够正常到达,实例就会一直保留在可用实例列表中。
-
如果 Provider 因应用崩溃、服务器宕机、网络中断等原因无法继续发送心跳,Nacos 在一段时间内收不到该实例的存活信息,就会认为该实例可能出现异常,并将其标记为不健康。
-
实例被标记为不健康后,Nacos 会更新服务注册表中的实例状态。服务消费者再次查询实例时,通常不会再选择这个异常实例,从而避免把业务请求发送到已经不可用的服务器。
-
如果实例长时间没有恢复,临时实例可能会被 Nacos 从服务注册表中移除。如果 Provider 恢复运行并重新连接 Nacos,则会再次执行服务注册流程,重新加入服务实例列表。
-
如果 Provider 是正常关闭,例如 Spring Boot 应用被正常停止,Nacos Client 可以在应用销毁前主动向 Nacos 发送注销请求。Nacos 收到注销请求后,会立即将该实例从服务注册表中移除,而不必等待心跳超时。
-
当实例注册、下线、被标记为不健康或者恢复健康时,Nacos 会更新对应服务的实例列表,并将变化通知给订阅该服务的消费者。消费者收到通知后,会更新本地缓存中的实例信息,保证后续调用使用最新的可用实例。
综上,Nacos 服务注册的完整过程就是:Provider 启动后收集服务名称、IP、端口和元数据等实例信息,通过 Nacos Client 发送注册请求;Nacos Server 校验并保存实例,将其加入服务注册表;注册成功后,Provider 通过心跳或长连接持续维持实例健康状态;当实例心跳超时、主动下线或发生故障时,Nacos 会更新或移除该实例,并把实例变化通知给服务消费者。
服务发现
服务发现是什么?
Nacos 服务发现,就是帮助服务消费者找到可用服务提供者的过程。
在微服务系统中,服务提供者的 IP、端口可能会动态变化,消费者不能把调用地址写死。因此,消费者会从 Nacos 中获取目标服务当前可用的实例列表,再选择其中一个实例发起远程调用。
服务发现原理

-
当服务消费者 Consumer 启动时,应用中的 Nacos Client 会同步初始化,并读取 Nacos 服务发现相关配置,例如 Nacos Server 地址、当前应用名称、命名空间、分组、集群名称等信息。
-
假设用户服务
user-service需要远程调用订单服务order-service。在没有注册中心时,用户服务必须在配置文件中写死订单服务的 IP 和端口,例如192.168.1.10:8081。使用 Nacos 后,消费者只需要知道目标服务名称order-service,不需要关心订单服务具体部署在哪台服务器上。 -
当 Consumer 第一次准备调用
order-service时,Nacos Client 会根据目标服务名称查找本地缓存,判断本地是否已经保存了该服务的实例列表。 -
如果本地缓存中不存在
order-service的实例信息,或者缓存中的实例信息需要刷新,Nacos Client 就会向 Nacos Server 发起服务查询或服务订阅请求。请求中通常会携带服务名称、命名空间、分组、集群名称等条件。 -
Nacos Server 的接入层收到查询请求后,会根据“命名空间、分组和服务名称”定位目标服务。例如,消费者查询的可能是
prod命名空间、DEFAULT_GROUP分组下的order-service。 -
Nacos 的服务发现模块会从服务注册表中查询
order-service当前注册的全部实例。假设订单服务中存在以下三个实例:192.168.1.10:8081、192.168.1.11:8081和192.168.1.12:8081。 -
Nacos 查询到实例后,会根据实例状态进行筛选。例如,检查实例是否已经启用、是否健康、是否属于指定集群、是否符合命名空间和分组要求。
-
假设
192.168.1.12:8081因为长时间没有发送心跳,已经被标记为不健康,那么 Nacos 不会把它作为正常可用实例提供给消费者。最终返回的可用实例可能只有192.168.1.10:8081和192.168.1.11:8081。 -
Nacos 返回的内容不仅包含实例的 IP 和端口,还可能包含实例权重、健康状态、启用状态、集群名称以及元数据等信息。例如,一个实例的元数据可能包含
version=v2,表示该实例属于 v2 版本;也可能包含region=tokyo,表示该实例部署在东京区域。 -
Consumer 收到 Nacos 返回的实例列表后,会将其保存到本地缓存中。保存本地缓存后,Consumer 后续调用
order-service时,通常直接从内存中获取实例列表,而不是每次发送业务请求都重新访问 Nacos。 -
本地缓存可以提高服务调用效率,同时减少 Nacos Server 的查询压力。即使 Nacos Server 短时间无法访问,Consumer 仍然可以暂时使用已经缓存的服务实例继续发送请求。
-
当 Consumer 从本地缓存中获得多个可用实例后,会通过客户端负载均衡组件选择其中一个实例。常见的选择方式包括轮询、随机、权重以及优先选择同集群实例等。
-
例如,当前存在两个实例:
192.168.1.10:8081的权重为1.0,192.168.1.11:8081的权重为2.0。负载均衡组件可能让权重较高的第二个实例承担更多请求。 -
假设本次调用最终选择了
192.168.1.10:8081,Consumer 就会把原来的逻辑服务地址http://order-service/order/query转换为真实地址http://192.168.1.10:8081/order/query。 -
获取真实地址后,Consumer 会直接向选中的 Provider 发送 HTTP、RPC 或其他协议的业务请求。Nacos 不参与业务请求的转发,它只负责帮助 Consumer 找到可用的 Provider 地址。
-
因此,服务发现阶段的交互是 Consumer 向 Nacos 查询服务地址,而真正的业务调用是 Consumer 直接访问 Provider,不是先把业务请求发送给 Nacos,再由 Nacos 转发。
-
Consumer 第一次查询
order-service时,通常还会订阅这个服务。订阅的含义是告诉 Nacos:“我正在使用order-service,如果这个服务的实例列表发生变化,请及时通知我。” -
Nacos 会维护 Consumer 对服务的订阅关系。当
order-service有新实例上线、旧实例下线、实例发生故障、实例恢复健康、权重被修改或者元数据发生变化时,Nacos 会更新服务注册表。 -
服务注册表发生变化后,Nacos 会通过客户端长连接或服务变更通知机制,将最新的实例变化推送给已经订阅该服务的 Consumer。
-
Consumer 收到变更通知后,会更新本地缓存中的实例列表。例如,原来缓存中存在
192.168.1.10:8081和192.168.1.11:8081两个实例。 -
如果
192.168.1.10:8081因为服务宕机而被 Nacos 标记为不健康,Nacos 会通知 Consumer。Consumer 更新本地缓存后,只保留192.168.1.11:8081,后续请求就不会继续发送给已经故障的实例。 -
如果新启动了一个订单服务实例
192.168.1.13:8081,该实例注册成功后,Nacos 同样会通知 Consumer。Consumer 更新本地缓存后,就可以在后续请求中将新实例加入负载均衡范围。 -
如果 Consumer 调用某个 Provider 时发生连接失败或请求异常,客户端可能会根据负载均衡和重试机制选择其他可用实例重新调用。具体是否重试以及重试次数,取决于项目使用的负载均衡组件和远程调用组件配置。
-
当 Nacos Server 暂时发生故障时,已经获取过实例列表的 Consumer 通常还可以使用本地缓存继续调用 Provider。但是在这段时间内,Consumer 可能无法及时感知新实例上线、旧实例下线以及实例健康状态变化。
-
当 Nacos Server 恢复后,Nacos Client 会重新建立连接、恢复服务订阅,并重新获取最新的实例列表,使本地缓存逐渐恢复到最新状态。
-
当 Provider 正常关闭时,会主动向 Nacos 注销服务实例;当 Provider 异常宕机时,Nacos 会根据心跳超时或连接断开判断实例异常。无论是哪种情况,Nacos 都会更新服务注册表,并把变化通知给 Consumer。
-
Consumer 更新实例列表后,负载均衡组件只会从当前可用实例中选择目标地址,从而避免继续调用已经下线或不健康的 Provider。
综上,Nacos 服务发现的完整过程就是:
- Consumer 根据服务名称向 Nacos 查询目标服务,Nacos 从服务注册表中查找并筛选健康实例,然后将可用实例列表返回给 Consumer;Consumer 将实例列表保存到本地缓存,通过负载均衡选择一个具体实例,最后直接向 Provider 发起远程调用;当服务实例发生上线、下线或健康状态变化时,Nacos 会通知 Consumer 更新本地缓存,保证后续请求尽量发送到最新的可用实例。
nacos注册中心使用案例
项目准备
安装启动nacos:官网下载地址
下载好后进入程序包的bin目录下,在文件资源路径输入cmd回车进入黑窗口,执行:startup.cmd -m standalone 命令启动nacos。
创建一个聚合工程,项目结构如下:
cloud-demo/
├── cloudOrder/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/order/
│ │ │ │ └── OrderMainApplication.java # 订单服务启动类
│ │ │ │
│ │ │ └── resources/
│ │ │ └── application.yml # 订单服务配置文件
│ │ │
│ │ └── test/
│ │ └── java/ # 订单服务测试代码
│ │
│ └── pom.xml # 订单服务 Maven 配置
│
├── cloudProduct/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/product/
│ │ │ │ └── ProductMainApplication.java # 商品服务启动类
│ │ │ │
│ │ │ └── resources/
│ │ │ └── application.yml # 商品服务配置文件
│ │ │
│ │ └── test/
│ │ └── java/ # 商品服务测试代码
│ │
│ └── pom.xml # 商品服务 Maven 配置
│
└── pom.xml # 父工程 Maven 配置

父pom文件、cloudOrder模块pom、cloudProduct模块pom内容:
=================================================父pom内容=================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.study</groupId>
<artifactId>cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloudProduct</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--SpringBoot启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--注册中心启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
=================================================cloudOrder模块pom内容=================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.study</groupId>
<artifactId>cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloudOrder</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--SpringBoot启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
=================================================cloudProduct模块pom内容=================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.study</groupId>
<artifactId>cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloudProduct</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--SpringBoot启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
两个子模块配置文件就定义了端口和名称,重点标记: 使用nacos必须要给项目定义name属性,因为:Nacos 默认使用 ${spring.application.name} 作为注册到服务中心的服务名;没有配置应用名称,Nacos 就不知道该把当前实例注册成什么服务。 否则就会:Nacos 客户端已经启动,但没有找到要注册的“服务名称”,所以跳过了注册。 报 No service to register for nacos client… 这个日志。
ok,现在配置文件内容如下:
==============order配置==============
server:
port: 8000
spring:
application:
name: cloud-order
==============product配置==============
server:
port: 9000
spring:
application:
name: cloud-product
添加好两个模块的启动类,保证项目正常启动。order占用8000端口,product占用9000端口
添加服务注册功能
把order和product两个模块都注册到nacos中,所以先给两个子模块添加服务注册的功能,给两个模块都添加以下maven依赖:
<!--注册中心启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
怎么使用呢?我们只需要在启动类添加@EnableDiscoveryClient注解,然后再配置文件添加好nacos服务端的地址就可以开启服务注册功能了。
@EnableDiscoveryClient
@SpringBootApplication
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class,args);
}
}
@EnableDiscoveryClient
@SpringBootApplication
public class ProductMainApplication {
public static void main(String[] args) {
SpringApplication.run(ProductMainApplication.class, args);
}
}
server:
port: 8000
spring:
application:
name: cloud-order
# 添加nacos地址
cloud:
nacos:
discovery:
server-addr: localhost:8848
=============================================================================
server:
port: 9000
spring:
application:
name: cloud-product
# 添加nacos地址
cloud:
nacos:
discovery:
server-addr=127.0.0.1:8848
OK,启动项目就可以在idea的控制台和nacos的管理界面看到order、product模块注册成功了:


使用idea复制功能注册多个实例
我们测试把order、product模块都复制一份出来,再nacos都注册两个实例,看看怎么做?
1、利用idea的复制功能,把模块复制一份

2、修改参数,点击Modify options按钮,点击Program arguments添加参数。


注意重点: 只要是配置文件能写的配置,都可以在这里写。
OK,复制完毕,启动多份看看结果:

٩(◕‿◕。)۶ 服务注册搞定 ٩(◕‿◕。)۶
添加服务发现功能
DiscoveryClient、NacosServiceDiscovery
这两个类都是去nacos拉取服务列表的,功能上和用法上基本上一模一样,只是DiscoveryClient属于spring加的NacosServiceDiscovery是nacos原生的。下面是使用案例。
直接在order或者product模块引入测试依赖且创建一个测试类,此时项目结构如下:
只是在cloudOrder模块的加了一个测试类OrderTest.java
cloud-demo/
├── cloudOrder/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/order/
│ │ │ │ └── OrderMainApplication.java # 订单服务启动类
│ │ │ │
│ │ │ └── resources/
│ │ │ └── application.yml # 订单服务配置文件
│ │ │
│ │ └── test/
│ │ └── java/
│ │ └── OrderTest.java # 订单服务测试类
│ │
│ └── pom.xml # 订单服务 Maven 配置
│
├── cloudProduct/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/product/
│ │ │ │ └── ProductMainApplication.java # 商品服务启动类
│ │ │ │
│ │ │ └── resources/
│ │ │ └── application.yml # 商品服务配置文件
│ │ │
│ │ └── test/
│ │ └── java/ # 商品服务测试代码目录
│ │
│ └── pom.xml # 商品服务 Maven 配置
│
└── pom.xml # 父工程 Maven 配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.exception.NacosException;
import com.order.OrderMainApplication;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import java.util.List;
@SpringBootTest(classes = OrderMainApplication.class)
public class OrderTest {
@Resource
private DiscoveryClient discoveryClient;
@Resource
private NacosServiceDiscovery nacosServiceDiscovery;
@Test
public void discoveryClientTest() {
for (String service : discoveryClient.getServices()) {
List<ServiceInstance> instances = discoveryClient.getInstances(service);
for (ServiceInstance serviceInstance : instances) {
System.out.println("服务名称:" + service + "。ip:" + serviceInstance.getHost() + "。port:"
+ serviceInstance.getPort() + "。uri:" + serviceInstance.getUri());
}
}
}
@Test
public void nacosServiceDiscoveryTest() throws NacosException {
for (String service : nacosServiceDiscovery.getServices()) {
System.out.println("service = " + service);
List<ServiceInstance> instances = nacosServiceDiscovery.getInstances(service);
for (ServiceInstance instance : instances) {
System.out.println("ip:" + instance.getHost() + "。" + "port = " +
instance.getPort() + "。uri=" + instance.getUri());
}
}
}
}
控制台输出结果:


现在,我们成功的nacos拿到了服务列表了。可有有什么用呢?然后呢?怎么调用?
远程调用
我们现在模拟一个调用场景,用户在浏览器购买商品,Order模块调用Product模块查询商品库存,商品库存查完库返回订单数量是否足够,订单根据数量足够告诉用户是否购买成功。流程:
用户下单
↓
订单模块(Order)
↓
商品模块(Prduct)
为了测试远程调用,我们去order、product两个模块中添加一个controller接口。然后使用RestTemplate调用远程接口,RestTemplate是什么:RestTemplate 是 Spring Framework 提供的一个同步 HTTP 客户端工具类,用于简化 RESTful Web Service 的调用。
项目结构如下:
新增了两个控制器:OrderController.java、ProductController.java,一个配置类:OrderConfig.java
cloud-demo/
├── cloudOrder/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/order/
│ │ │ │ ├── config/
│ │ │ │ │ └── OrderConfig.java # 订单服务配置类
│ │ │ │ │
│ │ │ │ ├── controller/
│ │ │ │ │ └── OrderController.java # 订单服务控制层
│ │ │ │ │
│ │ │ │ └── OrderMainApplication.java # 订单服务启动类
│ │ │ │
│ │ │ └── resources/
│ │ │ └── application.yml # 订单服务配置文件
│ │ │
│ │ └── test/
│ │ └── java/
│ │ └── OrderTest.java # 订单服务测试类
│ │
│ └── pom.xml # 订单服务 Maven 配置
│
├── cloudProduct/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/product/
│ │ │ │ ├── controller/
│ │ │ │ │ └── ProductController.java # 商品服务控制层
│ │ │ │ │
│ │ │ │ └── ProductMainApplication.java # 商品服务启动类
│ │ │ │
│ │ │ └── resources/
│ │ │ └── application.yml # 商品服务配置文件
│ │ │
│ │ └── test/
│ │ └── java/ # 商品服务测试代码目录
│ │
│ └── pom.xml # 商品服务 Maven 配置
│
└── pom.xml # 父工程 Maven 配置
OrderConfig.java代码:
package com.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class OrderConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
OrderController代码:
package com.order.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient; // 服务发现
@Autowired
private RestTemplate restTemplate; // 远程调用
@GetMapping
public String order(){
// 随机生成10以内整数,模拟用户下单数量
int orderNum = new Random().nextInt(10);
System.out.println("用户下单,数量:"+ orderNum +"。准备远程调用Product服务查询商品库存");
// 获取商品服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-product");
// 选择实例准备远程调用
ServiceInstance instance = instances.get(0);
//远程URL
String url = "http://"+instance.getHost() +":" +instance.getPort();
// 给远程发送请求
int productNum = restTemplate.getForObject(url, Integer.class);
return orderNum >= productNum ? "下单成功" : "下单失败,商品库存不足。";
}
}
ProductController代码:
package com.product.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
@RestController
public class ProductController {
@GetMapping
public int getProduct(){
// 生成10以内的整数,模拟从数据库查回来的商品数量
int num = new Random().nextInt(10);
System.out.println("商品服务,商品数量:" + num);
return num;
}
}
结果演示:

٩(◕‿◕。)۶ 远程调用搞定 ٩(◕‿◕。)۶ 。。。。了吗? (・_・?)
restTemplate远程调用的问题
看代码:
@GetMapping
public String order(){
......
......
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-product");
ServiceInstance instance = instances.get(0);
String url = "http://"+instance.getHost() +":" +instance.getPort();
// 给远程发送请求
int productNum = restTemplate.getForObject(url, Integer.class);
......
......
}
重点在这两句:
List instances = discoveryClient.getInstances(“cloud-product”);
ServiceInstance instance = instances.get(0);
逻辑是:回去nacos的注册中心拿到cloud-product的服务列表,这里没有问题。
问题是ServiceInstance instance = instances.get(0);
get(0),就算地球爆炸了也永远只会拿cloud-product服务的实例列表中的第一个实例进行远程调用,也就是说你哪怕部署10台product商品服务,但是只会有一台能够被使用 。
这怎么解决呢?难道要我自己对instances 这个列表写个算法来保证每个实例都能被公平的调用吗? 安啦。Spring Cloud官方早就给你准备好了。
loadBalancerClient + restTemplate
LoadBalancerClient:是Spring Cloud官方提供的负载均衡。
直接在order模块中添加LoadBalancerClient的依赖:
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
在order模块的控制器添加新的远程调用 + 负载均衡的方法,OrderController代码:
package com.order.controller;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient; // 服务发现
@Autowired
private RestTemplate restTemplate; // 远程调用
@Autowired
private LoadBalancerClient loadBalancerClient;
/*第二阶段:LoadBalancer负载均衡获取实例 + restTemplate远程调用*/
@GetMapping("loadBalancerOrder")
public String loadBalancerOrder(){
// 随机生成10以内整数,模拟用户下单数量
int orderNum = new Random().nextInt(10);
System.out.println("用户下单,数量:"+ orderNum +"。准备远程调用Product服务查询商品库存");
// 负载均衡的获取服务实例
ServiceInstance choose = loadBalancerClient.choose("cloud-product");
System.out.println("负载均衡获取cloud-product服务实例:" + choose.getHost() + ":" + choose.getPort());
//远程URL
String url = "http://"+choose.getHost() +":" +choose.getPort();
// 给远程发送请求
int productNum = restTemplate.getForObject(url, Integer.class);
String uri = choose.getUri().toString();
return orderNum >= productNum ? "下单成功,调用" + uri +
"服务查询商品库存成功" : "下单失败,调用" + uri + "服务查询商品库存不足";
}
/*第一阶段:远程调用*/
@GetMapping
public String order(){
// 随机生成10以内整数,模拟用户下单数量
int orderNum = new Random().nextInt(10);
System.out.println("用户下单,数量:"+ orderNum +"。准备远程调用Product服务查询商品库存");
// 获取商品服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-product");
// 选择实例准备远程调用
ServiceInstance instance = instances.get(0);
//远程URL
String url = "http://"+instance.getHost() +":" +instance.getPort();
// 给远程发送请求
int productNum = restTemplate.getForObject(url, Integer.class);
return orderNum >= productNum ? "下单成功" : "下单失败,商品库存不足。";
}
}
测试结果展示,复制三个商品模块(product)注册的nacos,然后再浏览器调用三次order订单接口,结果如下:


@LoadBalanced+ restTemplate
基于注解的负载均衡,修改order模块代码,在OrderConfig给restTemplate方法添加@LoadBalanced注解,直接在控制器使用。
OrderConfig代码:
package com.order.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class OrderConfig {
@LoadBalanced // 注解负载均衡
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
OrderController代码:
package com.order.controller;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient; // 服务发现
@Autowired
private RestTemplate restTemplate; // 远程调用
@Autowired
private LoadBalancerClient loadBalancerClient;
/*第三阶段阶段:基于注解LoadBalancer负载均衡获取实例 + restTemplate远程调用*/
@GetMapping("loadBalancerAnnotationOrder")
public String loadBalancerAnnotationOrder(){
// 随机生成10以内整数,模拟用户下单数量
int orderNum = new Random().nextInt(10);
System.out.println("用户下单,数量:"+ orderNum +"。准备远程调用Product服务查询商品库存");
String url = "http://cloud-product";
// 给远程发送请求,cloud-product服务名称会被动态的替换成实例名,比如cloud-product会被替换成192.168.1.1:9000、9001、9002
int productNum = restTemplate.getForObject(url, Integer.class);
return orderNum >= productNum ? "下单成功" : "下单失败,商品库存不足。";
}
/*第二阶段:LoadBalancer负载均衡获取实例 + restTemplate远程调用*/
@GetMapping("loadBalancerOrder")
public String loadBalancerOrder(){
// 随机生成10以内整数,模拟用户下单数量
int orderNum = new Random().nextInt(10);
System.out.println("用户下单,数量:"+ orderNum +"。准备远程调用Product服务查询商品库存");
// 负载均衡的获取服务实例
ServiceInstance choose = loadBalancerClient.choose("cloud-product");
System.out.println("负载均衡获取cloud-product服务实例:" + choose.getHost() + ":" + choose.getPort());
//远程URL
String url = "http://"+choose.getHost() +":" +choose.getPort();
// 给远程发送请求
int productNum = restTemplate.getForObject(url, Integer.class);
String uri = choose.getUri().toString();
return orderNum >= productNum ? "下单成功,调用" + uri +
"服务查询商品库存成功" : "下单失败,调用" + uri + "服务查询商品库存不足";
}
/*第一阶段:远程调用*/
@GetMapping
public String order(){
// 随机生成10以内整数,模拟用户下单数量
int orderNum = new Random().nextInt(10);
System.out.println("用户下单,数量:"+ orderNum +"。准备远程调用Product服务查询商品库存");
// 获取商品服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-product");
// 选择实例准备远程调用
ServiceInstance instance = instances.get(0);
//远程URL
String url = "http://"+instance.getHost() +":" +instance.getPort();
// 给远程发送请求
int productNum = restTemplate.getForObject(url, Integer.class);
return orderNum >= productNum ? "下单成功" : "下单失败,商品库存不足。";
}
}
测试结果:


(◕‿◕✿)服务注册 ✅(◕‿◕✿)服务发现 ✅(◕‿◕✿)远程调用 ✅(◕‿◕✿)负载均衡 ✅(◕‿◕✿)
更多推荐
所有评论(0)