一、核心概念
1.1 什么是服务注册与发现?
在微服务架构中,服务实例(如订单服务、用户服务)是动态的(可能因扩缩容、故障重启而变化)。服务注册与发现是解决服务间如何相互定位和通信的核心机制。
- 服务注册 (Service Registration):服务启动时,将自己的网络地址(IP、端口)、服务名等信息注册到一个注册中心 (Registry)。
- 服务发现 (Service Discovery):服务需要调用其他服务时,向注册中心查询目标服务的可用实例列表,然后通过负载均衡选择一个实例进行调用。
1.2 核心组件
| 组件 | 说明 |
|---|---|
| 服务提供者 (Service Provider) | 暴露服务的微服务实例(如 order-service)。启动时向注册中心注册自身信息。 |
| 服务消费者 (Service Consumer) | 需要调用其他服务的微服务实例(如 user-service 调用 order-service)。从注册中心获取服务提供者列表。 |
| 注册中心 (Registry) | 存储服务注册信息的中心化服务。Eureka 和 Nacos 都是注册中心的实现。 |
| 心跳机制 (Heartbeat) | 服务提供者定期(如 Eureka 默认30秒)向注册中心发送心跳,证明自己“存活”。 |
| 服务下线 (Deregistration) | 服务正常关闭时,主动通知注册中心注销自己。 |
| 服务剔除 (Eviction) | 注册中心在一段时间内(如 Eureka 默认90秒)未收到服务提供者的心跳,则将其从注册列表中移除。 |
1.3 Eureka vs Nacos
| 特性 | Eureka (Netflix) | Nacos (Alibaba) |
|---|---|---|
| 开发团队 | Netflix | Alibaba |
| 功能定位 | 纯服务注册与发现 | 服务注册发现 + 配置管理 (一体化平台) |
| 一致性协议 | AP (高可用、分区容忍) | 支持 AP (服务发现) 和 CP (配置管理) |
| 健康检查 | 客户端心跳 | 支持心跳、TCP、HTTP、MySQL 等多种方式 |
| 集群模式 | Peer-to-Peer (对等) | 支持单机、集群(AP/CP) |
| 配置中心 | ❌ 需要配合 Spring Cloud Config | ✅ 内置强大配置中心 |
| 控制台 | 简单 | 功能丰富(服务管理、配置管理、命名空间、权限等) |
| 社区活跃度 | Eureka 2.x 已停止开发,1.x 维护中 | 非常活跃,持续更新 |
| 推荐场景 | 现有 Eureka 项目 | 新项目,尤其需要配置中心 |
选择建议:新项目强烈推荐 Nacos,功能更全面,社区更活跃。Eureka 可用于维护现有项目。
二、详细操作步骤
Part 1: 使用 Eureka 实现服务注册与发现
步骤 1.1:搭建 Eureka Server (注册中心)
创建项目: 使用 Spring Initializr 创建一个新项目,添加依赖:
Spring WebEureka Server(spring-cloud-starter-netflix-eureka-server)
配置
pom.xml:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>启用 Eureka Server:
// EurekaServerApplication.java @SpringBootApplication @EnableEurekaServer // 启用 Eureka Server public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }配置
application.yml:server: port: 8761 # Eureka Server 端口 spring: application: name: eureka-server # 服务名 eureka: client: register-with-eureka: false # 作为 Server,不向自己注册 fetch-registry: false # 作为 Server,不从自己拉取注册信息 server: enable-self-preservation: true # 开启自我保护模式(网络分区时保护实例) eviction-interval-timer-in-ms: 30000 # 清理无效实例的间隔(默认60s) instance: hostname: localhost启动 Eureka Server: 启动应用,访问
http://localhost:8761,应能看到 Eureka 控制台界面(此时无服务注册)。
步骤 1.2:服务提供者注册到 Eureka
创建服务提供者项目 (如
order-service): 添加依赖:Spring WebEureka Client(spring-cloud-starter-netflix-eureka-client)
配置
pom.xml:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>配置
application.yml:server: port: 8081 # 服务端口 spring: application: name: order-service # 服务名,消费者通过此名发现 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ # 指向 Eureka Server 地址 instance: prefer-ip-address: true # 注册时使用 IP 地址而不是主机名 instance-id: ${spring.application.name}:${server.port} # 实例 ID 显示更清晰编写一个简单接口:
@RestController @RequestMapping("/orders") public class OrderController { @GetMapping("/{id}") public String getOrder(@PathVariable String id) { return "Order: " + id + " from port " + server.port; } }启动服务: 启动
order-service,刷新 Eureka 控制台,应能看到ORDER-SERVICE实例已注册。
步骤 1.3:服务消费者发现并调用服务
创建服务消费者项目 (如
user-service): 同样添加Eureka Client依赖。配置
application.yml:server: port: 8082 spring: application: name: user-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true instance-id: ${spring.application.name}:${server.port}集成 Ribbon (负载均衡): Spring Cloud Netflix Ribbon 是客户端负载均衡器,通常与 Eureka 配合使用。
- 注意:Ribbon 已进入维护模式,但仍在广泛使用。新项目可考虑 Spring Cloud LoadBalancer。
- 在
@Bean配置RestTemplate时添加@LoadBalanced注解。
配置
RestTemplate:// UserApplication.java 或配置类 @Bean @LoadBalanced // 启用客户端负载均衡 public RestTemplate restTemplate() { return new RestTemplate(); }编写调用逻辑:
@RestController @RequestMapping("/users") public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping("/{userId}/orders/{orderId}") public String getUserOrder(@PathVariable String userId, @PathVariable String orderId) { // 使用服务名 "order-service" 进行调用,Ribbon 会自动解析并负载均衡 String url = "http://order-service/orders/" + orderId; String response = restTemplate.getForObject(url, String.class); return "User: " + userId + ", " + response; } }启动并测试: 启动
user-service,访问http://localhost:8082/users/123/orders/456,应能成功调用order-service并返回结果。
Part 2: 使用 Nacos 实现服务注册与发现
步骤 2.1:部署 Nacos Server
下载 Nacos: 访问 Nacos GitHub Releases,下载
nacos-server-$version.zip。启动 Nacos Server:
- 单机模式 (开发测试):
# Linux/Mac sh startup.sh -m standalone # Windows startup.cmd -m standalone - 集群模式 (生产):需配置
cluster.conf和数据库。
- 单机模式 (开发测试):
访问 Nacos 控制台: 启动后,访问
http://localhost:8848/nacos,默认用户名/密码:nacos/nacos。
步骤 2.2:服务提供者注册到 Nacos
创建服务提供者项目 (如
product-service): 添加依赖:Spring WebNacos Discovery(spring-cloud-starter-alibaba-nacos-discovery)
配置
pom.xml:<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>配置
application.yml:server: port: 8083 spring: application: name: product-service cloud: nacos: discovery: server-addr: localhost:8848 # Nacos Server 地址 # namespace: your-namespace-id # 指定命名空间 (生产环境隔离) # group: DEFAULT_GROUP # 指定分组 # weight: 1 # 实例权重,用于负载均衡 # metadata: # 自定义元数据 # version: 1.0.0编写接口并启动:
@RestController @RequestMapping("/products") public class ProductController { @GetMapping("/{id}") public String getProduct(@PathVariable String id) { return "Product: " + id + " from port " + server.port; } }启动
product-service,登录 Nacos 控制台,在“服务管理” -> “服务列表”中应能看到product-service。
步骤 2.3:服务消费者发现并调用服务
创建服务消费者项目 (如
shopping-cart-service): 添加Nacos Discovery依赖。配置
application.yml:server: port: 8084 spring: application: name: shopping-cart-service cloud: nacos: discovery: server-addr: localhost:8848集成负载均衡:
- 推荐使用 Spring Cloud LoadBalancer (取代 Ribbon)。
- 添加依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> - 配置
RestTemplate:@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
编写调用逻辑:
@RestController @RequestMapping("/cart") public class CartController { @Autowired private RestTemplate restTemplate; @GetMapping("/items/{productId}") public String getCartItem(@PathVariable String productId) { String url = "http://product-service/products/" + productId; String response = restTemplate.getForObject(url, String.class); return "Cart Item: " + response; } }启动并测试: 启动
shopping-cart-service,访问http://localhost:8084/cart/items/789,应能成功调用product-service。
三、常见错误与解决方案
| 错误现象 | 原因分析 | 解决方案 |
|---|---|---|
| 服务未注册到注册中心 | 1. 依赖缺失 2. 配置错误 (如 server-addr)3. 网络不通 4. 启动类缺少 @EnableDiscoveryClient (通常不需要,自动配置) |
1. 检查 pom.xml2. 核对配置文件 3. ping 或 telnet 测试网络4. 检查日志,看是否有注册相关错误 |
| 消费者调用失败 (404/UnknownHostException) | 1. 服务名拼写错误 2. 服务未启动或未注册成功 3. 负载均衡配置问题 ( @LoadBalanced) |
1. 检查 RestTemplate URL 中的服务名2. 确认提供者已注册 3. 确保 RestTemplate Bean 有 @LoadBalanced |
| Eureka Server 频繁进入自我保护模式 | 网络不稳定或心跳超时 | 1. 检查网络 2. 调整 eureka.instance.lease-renewal-interval-in-seconds (默认30s) 和 eureka.instance.lease-expiration-duration-in-seconds (默认90s)3. 生产环境合理配置 |
| Nacos 启动失败 (端口占用) | 8848 或 9848 端口被占用 | 1. netstat -anp | grep 88482. 杀死占用进程或修改 application.properties 中的 server.port |
| Nacos 集群启动失败 | cluster.conf 配置错误或数据库未初始化 |
1. 检查 cluster.conf 格式 (IP:PORT)2. 执行 nacos/conf/nacos-mysql.sql 初始化数据库3. 配置 application.properties 中的数据库连接 |
四、注意事项
- 服务命名:
- 使用小写字母、数字和连字符
-。 - 避免使用下划线
_。 - 名称应具有业务含义。
- 使用小写字母、数字和连字符
- 健康检查:
- Eureka:依赖客户端心跳。
- Nacos:支持更丰富的健康检查方式,可在控制台配置。
- 元数据 (Metadata):
- 可在注册时附加元数据(如版本号
version: 1.0),用于灰度发布、路由等高级场景。
- 可在注册时附加元数据(如版本号
- 命名空间 (Namespace):
- Nacos 支持命名空间,用于环境隔离(如
dev,test,prod)。
- Nacos 支持命名空间,用于环境隔离(如
- 分组 (Group):
- Nacos 支持分组,默认
DEFAULT_GROUP,可用于逻辑分组。
- Nacos 支持分组,默认
- 版本兼容性:
- 确保 Spring Boot、Spring Cloud、Nacos/Eureka Client 版本兼容。参考官方文档的版本对应关系。
- 安全性:
- 生产环境务必开启 Nacos/Eureka 的认证(如 Nacos 的
nacos.core.auth.enabled=true)。
- 生产环境务必开启 Nacos/Eureka 的认证(如 Nacos 的
五、使用技巧
5.1 Nacos 配置中心集成 (Bonus)
Nacos 一大优势是同时作为配置中心。
# bootstrap.yml (优先级高于 application.yml)
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml # 配置文件格式
# namespace: your-namespace-id
# group: DEFAULT_GROUP
在 Nacos 控制台创建配置 Data ID = ${spring.application.name}.${file-extension} (如 product-service.yaml),即可动态管理配置。
5.2 服务健康检查自定义 (Nacos)
在 application.yml 中配置:
spring:
cloud:
nacos:
discovery:
health-check-type: http # 可选: tcp, http, mysql
health-check-path: /actuator/health # HTTP 健康检查路径
health-check-interval: 5s # 检查间隔
5.3 利用 Nacos 控制台
- 服务详情:查看实例列表、健康状态、元数据。
- 服务编辑:动态修改权重、元数据。
- 订阅者:查看哪些服务订阅了本服务。
- 配置管理:管理配置文件。
5.4 优雅下线
- 正常关闭:服务收到
SIGTERM信号时,会先向注册中心发送注销请求,再停止。 - 强制关闭 (
kill -9):依赖注册中心的剔除机制。
六、最佳实践
- 选择 Nacos:新项目首选 Nacos,功能更全面。
- 环境隔离:使用 Nacos 命名空间或 Eureka 不同的
defaultZone隔离环境。 - 配置合理的超时:
- 服务调用超时 (
feign.client.config.default.connect-timeout,read-timeout)。 - 注册中心连接超时。
- 服务调用超时 (
- 启用认证:生产环境必须开启注册中心的访问控制。
- 监控:
- 监控注册中心状态(CPU、内存、连接数)。
- 监控服务实例的健康状态和数量。
- 服务治理:
- 结合 Sentinel (流量控制) 或 Hystrix (熔断) 提高系统韧性。
- 利用 Nacos 的权重进行灰度发布。
- 文档化:明确服务名、接口、依赖关系。
七、性能优化
- 注册中心集群:
- 生产环境必须部署集群,避免单点故障。
- Eureka:多节点 Peer-to-Peer 复制。
- Nacos:集群模式,推荐持久化到数据库。
- 调整心跳与剔除间隔:
- 平衡:缩短间隔能更快发现故障,但增加网络和注册中心压力。
- 建议:根据业务容忍度调整。例如,Eureka 可设
lease-renewal-interval-in-seconds=10,lease-expiration-duration-in-seconds=30。
- 客户端缓存:
- Eureka/Nacos Client 会缓存服务列表,减少对注册中心的直接查询。
- 避免在代码中频繁调用
DiscoveryClient.getInstances(),可结合缓存。
- 负载均衡优化:
- 使用更高效的负载均衡策略(如 Nacos 权重、一致性哈希)。
- Spring Cloud LoadBalancer 性能优于旧版 Ribbon。
- 减少注册信息:
- 避免在元数据中存储大量数据。
- 网络优化:
- 确保服务与注册中心网络延迟低。
总结
服务注册与发现是微服务架构的基石。
- Eureka:经典的 AP 模型注册中心,简单易用,但功能单一,社区趋于稳定。
- Nacos:功能强大的国产平台,集服务发现与配置管理于一体,AP/CP 可切换,新项目首选。
核心步骤:
- 部署:启动 Eureka Server 或 Nacos Server。
- 注册:服务提供者添加 Client 依赖并配置 Server 地址。
- 发现:服务消费者添加 Client 依赖,配置
RestTemplate为@LoadBalanced。 - 调用:使用服务名(而非具体 IP:Port)通过
RestTemplate调用。
掌握服务注册与发现,是构建可扩展、高可用微服务系统的起点。