跳到主要内容

服务注册与发现

服务注册与发现是微服务架构的基石。它解决了服务消费者如何找到服务提供者的问题,让服务实例可以动态增减而不影响系统运行。

为什么需要服务注册与发现

理解服务注册与发现的价值,需要回顾没有它时的困境。

硬编码的问题

在传统架构中,服务调用方需要知道服务提供方的地址。通常的做法是在配置文件中写死服务地址。这种方式在服务实例固定且数量少时可行,但在微服务场景下会暴露严重问题。

服务实例的 IP 和端口可能随时变化。容器化部署时,每次重启可能获得新的 IP。弹性伸缩时,实例数量动态增减。某个实例故障时,需要快速切换到其他实例。硬编码的地址无法应对这些变化。

服务注册与发现的解决方案

服务注册与发现机制引入了一个中间层:服务注册中心。所有服务启动时向注册中心注册自己的信息(IP、端口、元数据等),服务消费者从注册中心获取可用的服务实例列表。

这种方式带来了几个好处。服务消费者不再需要知道具体的服务地址,只需要知道服务名。服务实例可以动态增减,注册中心自动维护最新的实例列表。某个实例故障时,注册中心会将其从列表中移除,消费者无感知。

核心概念

理解服务注册与发现,需要掌握几个核心概念。

服务注册中心

服务注册中心是整个机制的核心。它维护着所有服务实例的信息,包括服务名、IP 地址、端口、健康状态、元数据等。常见的服务注册中心有 Nacos、Eureka、Consul、Zookeeper 等。

服务提供者

服务提供者是提供业务服务的应用。它启动时向注册中心注册自己的信息,运行期间定期发送心跳证明自己还活着,关闭时向注册中心注销。

服务消费者

服务消费者是调用其他服务的应用。它从注册中心获取服务实例列表,根据负载均衡策略选择一个实例发起调用。消费者通常会缓存实例列表,避免每次调用都查询注册中心。

服务实例

服务实例是服务的具体运行单元,由 IP 地址和端口标识。一个服务可以有多个实例,这些实例可能部署在不同的机器上,具有不同的处理能力。

Nacos 服务注册与发现

Nacos 是阿里巴巴开源的服务发现和配置管理平台,是 Spring Cloud Alibaba 的核心组件。相比 Eureka,Nacos 功能更全面、性能更好、社区更活跃,是目前国内微服务架构的首选。

Nacos 简介

Nacos 的名字来自 Naming and Configuration Service 的首字母。它提供了两大核心功能:服务发现和配置管理。这里先介绍服务发现功能。

Nacos 的服务发现具有以下特点:支持 DNS 和 RPC 两种服务发现模式,提供多种健康检查方式(TCP、HTTP、MySQL 等),支持临时实例和持久实例,提供可视化的管理控制台,支持集群部署保证高可用。

搭建 Nacos Server

使用 Nacos 前需要先部署 Nacos Server。

下载 Nacos:

# 从 GitHub 下载
wget https://github.com/alibaba/nacos/releases/download/2.4.0/nacos-server-2.4.0.tar.gz
tar -xzf nacos-server-2.4.0.tar.gz
cd nacos/bin

单机模式启动:

# Linux/Mac
sh startup.sh -m standalone

# Windows
startup.cmd -m standalone

启动后访问 http://localhost:8848/nacos,默认用户名和密码都是 nacos

生产环境建议使用集群模式。集群部署需要配置数据库(Nacos 2.x 推荐使用 MySQL)和集群节点配置。

服务注册到 Nacos

在 Spring Boot 项目中添加 Nacos 服务发现依赖:

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置服务信息:

spring:
application:
name: user-service # 服务名,服务发现的唯一标识
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos Server 地址
namespace: dev # 命名空间,用于环境隔离
group: DEFAULT_GROUP # 分组,用于业务隔离
cluster-name: SHANGHAI # 集群名称,用于就近访问

启动类添加 @EnableDiscoveryClient 注解:

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}

服务启动后会自动向 Nacos 注册。可以在 Nacos 控制台的服务列表中看到注册的服务。

服务发现与调用

服务消费者从 Nacos 获取服务实例列表并调用。

使用 DiscoveryClient 获取实例:

@Service
public class OrderService {

@Autowired
private DiscoveryClient discoveryClient;

@Autowired
private RestTemplate restTemplate;

public User getUser(Long userId) {
// 获取服务实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");

if (instances.isEmpty()) {
throw new RuntimeException("没有可用的用户服务实例");
}

// 选择一个实例(简单的负载均衡)
ServiceInstance instance = instances.get(0);
String url = instance.getUri() + "/user/" + userId;

return restTemplate.getForObject(url, User.class);
}
}

更推荐的方式是配合 LoadBalancer 使用:

@Configuration
public class RestTemplateConfig {

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
public class OrderService {

@Autowired
private RestTemplate restTemplate;

public User getUser(Long userId) {
// 直接使用服务名,LoadBalancer 自动负载均衡
String url = "http://user-service/user/" + userId;
return restTemplate.getForObject(url, User.class);
}
}

Nacos 配置项详解

Nacos 服务发现支持丰富的配置项:

spring:
cloud:
nacos:
discovery:
# 基础配置
server-addr: localhost:8848 # Nacos Server 地址
namespace: dev # 命名空间 ID
group: DEFAULT_GROUP # 分组名
service: ${spring.application.name} # 服务名,默认为应用名

# 实例配置
ip: 192.168.1.100 # 注册的 IP,默认自动获取
port: 8080 # 注册的端口,默认为服务端口
weight: 1 # 权重,用于负载均衡
cluster-name: DEFAULT # 集群名
ephemeral: true # 是否临时实例

# 元数据
metadata:
version: v1
region: shanghai

# 注册相关
enabled: true # 是否启用服务注册
register-enabled: true # 是否自动注册

# 心跳配置
heart-beat-interval: 5000 # 心跳间隔(毫秒)
heart-beat-timeout: 15000 # 心跳超时(毫秒)
ip-delete-timeout: 30000 # IP 删除超时(毫秒)

命名空间与分组

命名空间用于环境隔离,比如开发、测试、生产环境分别使用不同的命名空间。分组用于业务隔离,同一环境中不同项目可以使用不同分组。

在 Nacos 控制台创建命名空间:

  1. 进入命名空间管理页面
  2. 点击新建命名空间
  3. 填写命名空间 ID(如 dev)和名称(如 开发环境

在配置中使用:

spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: dev # 命名空间 ID
group: USER_GROUP # 分组

临时实例与持久实例

Nacos 区分临时实例和持久实例:

临时实例(Ephemeral = true)是常见的微服务实例类型。实例主动向 Nacos 注册,通过心跳维持注册状态。心跳停止后,Nacos 会自动删除实例。适合无状态服务,服务重启后 IP 可能变化。

持久实例(Ephemeral = false)由 Nacos 主动探测健康状态。即使服务下线,实例信息仍保留。适合需要固定 IP 的服务,如数据库、遗留系统等。

spring:
cloud:
nacos:
discovery:
ephemeral: false # 持久实例

元数据

元数据是附加在服务实例上的标签信息,可用于路由、权重、灰度发布等场景。

注册时设置元数据:

spring:
cloud:
nacos:
discovery:
metadata:
version: v2 # 版本号,用于灰度发布
weight: 80 # 权重
zone: shanghai # 可用区
env: gray # 环境标识

在负载均衡时使用元数据:

@Service
public class OrderService {

@Autowired
private DiscoveryClient discoveryClient;

public User getUser(Long userId, String version) {
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");

// 根据元数据过滤实例
ServiceInstance instance = instances.stream()
.filter(i -> version.equals(i.getMetadata().get("version")))
.findFirst()
.orElse(instances.get(0));

return restTemplate.getForObject(instance.getUri() + "/user/" + userId, User.class);
}
}

健康检查

Nacos 提供两种健康检查模式:

临时实例使用客户端心跳模式。服务实例定期向 Nacos 发送心跳,Nacos 超时未收到心跳则将实例标记为不健康或删除。

持久实例使用服务端探测模式。Nacos 主动探测实例的健康状态,支持 TCP、HTTP、MySQL 等多种探测方式。

集群部署

生产环境需要部署 Nacos 集群以保证高可用。Nacos 集群推荐至少 3 个节点。

配置集群节点(cluster.conf):

# cluster.conf
192.168.1.1:8848
192.168.1.2:8848
192.168.1.3:8848

配置数据库(application.properties):

spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=nacos
db.password.0=nacos

客户端配置集群地址:

spring:
cloud:
nacos:
discovery:
server-addr: 192.168.1.1:8848,192.168.1.2:8848,192.168.1.3:8848

Eureka 服务注册与发现

Eureka 是 Netflix 开源的服务注册中心,是 Spring Cloud Netflix 的核心组件。虽然 Eureka 2.x 已停止开发,但 Eureka 1.x 仍在维护,Spring Cloud 也继续支持。对于简单场景或已有项目,Eureka 仍是可选方案。

Eureka 架构

Eureka 采用 AP 设计,优先保证可用性。它包含两个角色:Eureka Server 作为服务注册中心,Eureka Client 作为服务提供者或消费者。

Eureka Server 之间会相互复制注册信息,实现注册中心的高可用。Eureka Client 启动时向 Server 注册,运行期间发送心跳续约,关闭时注销。Client 还会从 Server 拉取注册表并缓存到本地。

搭建 Eureka Server

添加依赖:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

配置文件:

server:
port: 8761

eureka:
instance:
hostname: localhost
client:
register-with-eureka: false # 不向自己注册
fetch-registry: false # 不从自己获取注册表
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动类:

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

访问 http://localhost:8761 可以看到 Eureka 控制台。

服务注册到 Eureka

添加依赖:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置:

spring:
application:
name: user-service

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
registry-fetch-interval-seconds: 30 # 拉取注册表间隔
instance:
prefer-ip-address: true # 使用 IP 注册
lease-renewal-interval-in-seconds: 10 # 心跳间隔
lease-expiration-duration-in-seconds: 30 # 租约过期时间

启动类添加 @EnableEurekaClient@EnableDiscoveryClient

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}

自我保护模式

Eureka 的自我保护模式是一个重要特性。当 Eureka Server 在短时间内丢失大量客户端心跳时(可能是网络问题),会进入自我保护模式,停止剔除实例,防止因网络问题导致服务误删。

生产环境建议开启自我保护:

eureka:
server:
enable-self-preservation: true
renewal-percent-threshold: 0.85 # 续约百分比阈值

Eureka 集群

Eureka Server 集群通过相互注册实现:

# server1
eureka:
client:
service-url:
defaultZone: http://server2:8761/eureka/,http://server3:8761/eureka/

# server2
eureka:
client:
service-url:
defaultZone: http://server1:8761/eureka/,http://server3:8761/eureka/

Nacos 与 Eureka 对比

特性NacosEureka
CAP支持 AP 和 CP 切换AP
健康检查TCP、HTTP、MySQL 等心跳
配置管理内置需要额外组件
控制台完善基础
社区活跃度活跃维护模式
企业级特性命名空间、分组、权重

对于新项目,推荐使用 Nacos。功能更全面,社区更活跃,且经过阿里巴巴大规模生产验证。

最佳实践

命名规范

服务名使用小写字母和连字符,如 user-serviceorder-service。避免使用下划线和特殊字符。

心跳配置

心跳间隔根据网络环境调整。内网环境可以设置为 5-10 秒,公网环境建议 10-30 秒。租约过期时间通常是心跳间隔的 3 倍。

健康检查

结合 Spring Boot Actuator 实现健康检查:

management:
endpoints:
web:
exposure:
include: health

可以自定义健康检查逻辑:

@Component
public class MyHealthIndicator implements HealthIndicator {

@Override
public Health health() {
// 检查数据库连接、缓存等
if (checkDatabaseConnection() && checkCacheConnection()) {
return Health.up().build();
}
return Health.down().withDetail("error", "Service unhealthy").build();
}
}

优雅下线

服务下线时,应该先从注册中心注销,再停止接收新请求,等待处理中的请求完成。

Spring Cloud 支持优雅下线:

server:
shutdown: graceful

spring:
lifecycle:
timeout-per-shutdown-phase: 30s

多环境管理

使用命名空间隔离不同环境。开发、测试、生产环境使用不同的命名空间,避免互相影响。

监控告警

监控注册中心的服务数量、实例数量、健康实例比例等指标。当服务实例数异常减少或健康实例比例下降时,及时告警。

小结

本章详细介绍了服务注册与发现:

服务注册与发现解决了动态环境下服务寻址的问题,服务提供者向注册中心注册,消费者从注册中心获取实例列表。Nacos 是推荐的服务注册中心,功能全面、性能好、社区活跃。Eureka 是备选方案,适合简单场景。合理配置心跳、健康检查、命名空间,确保服务发现的可靠性。