跳到主要内容

服务注册与发现

服务注册与发现是微服务架构中最基础也是最重要的组件之一。它解决了服务消费者如何找到服务提供者的问题,是构建微服务系统的基石。

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

在传统的单体应用中,服务之间通过固定的IP地址和端口进行通信。但在微服务架构中:

  1. 服务实例动态变化:服务会动态部署、扩容、缩容,IP地址不固定
  2. 负载均衡需求:同一个服务有多个实例,需要动态选择
  3. 故障处理:某个实例故障时,需要自动切换到健康实例
传统的服务调用(无服务发现):
┌─────────────┐ ┌─────────────┐
│ 服务A │ ──────→ │ 192.168.1.10:8080 │
│ │ 硬编码 │ (服务B) │
└─────────────┘ └─────────────┘

微服务架构(服务注册与发现):
┌─────────────┐ ┌─────────────┐
│ 服务注册中心 │ ←──────│ 服务B实例1 │
│ (Eureka) │ │ 192.168.1.10:8080 │
│ │ ←──────│ 服务B实例2 │
│ │ │ 192.168.1.11:8080 │
│ │ ──────→│ 服务A │
└─────────────┘ └─────────────┘

服务注册与发现的工作原理

核心概念

  1. 服务注册中心(Service Registry)

    • 充当服务目录的角色
    • 存储所有可用服务的信息
    • 常用组件:Eureka、Nacos、Consul
  2. 服务提供者(Service Provider)

    • 启动时向注册中心注册自己的服务
    • 定时发送心跳,证明自己还活着
    • 关闭时向注册中心注销
  3. 服务消费者(Service Consumer)

    • 从注册中心获取服务列表
    • 本地缓存服务列表
    • 根据负载均衡策略选择具体实例

服务注册流程

// 服务提供者启动时注册
@SpringBootApplication
@EnableEurekaClient // 启用Eureka客户端
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}

服务启动时会自动进行以下操作:

  1. 读取配置文件中的服务名称和应用端口
  2. 连接到 Eureka Server
  3. 发送 HTTP 注册请求,包含服务名、IP、端口等信息
  4. 定时(默认30秒)发送心跳续约

服务发现流程

// 服务消费者发现服务
@RestController
public class ConsumerController {

@Autowired
private DiscoveryClient discoveryClient;

@GetMapping("/call")
public String callService() {
// 获取服务列表
List<ServiceInstance> instances = discoveryClient
.getInstances("user-service");

// 负载均衡选择一个实例
ServiceInstance instance = instances.get(0);

// 调用服务
String url = instance.getUri() + "/hello";
return restTemplate.getForObject(url, String.class);
}
}

Eureka 服务注册中心

Eureka 是 Netflix 开发的服务注册与发现组件,现在已经成为 Spring Cloud 的核心组件之一。

Eureka 架构

┌─────────────────────────────────────────────────────────────────┐
│ Eureka 集群 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│
│ │ Eureka │ │ Eureka │ │ Eureka ││
│ │ Server 1 │◄─────►│ Server 2 │◄─────►│ Server 3 ││
│ │ (主节点) │ 同步 │ (从节点) │ 同步 │ (从节点) ││
│ └─────────────┘ └─────────────┘ └─────────────┘│
│ │ │ │ │
└──────────┼─────────────────────┼─────────────────────┼──────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 服务实例 │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ User │ │ Order │ │ Product │ │
│ │ Service │ │ Service │ │ Service │ │
│ │ 实例1,实例2 │ │ 实例1,2,3 │ │ 实例1 │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└──────────────────────────────────────────────────────────┘

搭建 Eureka Server

<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
# application.yml
server:
port: 8761

eureka:
instance:
hostname: localhost
client:
# 不向自己注册
register-with-eureka: false
# 不从自己获取服务列表
fetch-registry: false
server:
# 关闭自我保护模式
enable-self-preservation: false
// 启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

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

服务提供者注册到 Eureka

<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
# application.yml
spring:
application:
name: user-service # 服务名称

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
# 使用IP而不是主机名注册
prefer-ip-address: true
# 心跳间隔
lease-renewal-interval-in-seconds: 10
# 服务过期时间
lease-expiration-duration-in-seconds: 30
// 启动类
@SpringBootApplication
@EnableEurekaClient // 或者 @EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}

服务消费者从 Eureka 获取服务

# 服务消费者配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 每隔30秒拉取一次服务列表
registry-fetch-interval-seconds: 30
// 使用 Ribbon + RestTemplate
@Configuration
public class RestTemplateConfig {

@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

@RestController
public class OrderController {

@Autowired
private RestTemplate restTemplate;

@GetMapping("/order/{id}")
public Order getOrder(@PathVariable Long id) {
// 直接使用服务名调用,Ribbon会自动进行负载均衡
String url = "http://user-service/user/" + id;
User user = restTemplate.getForObject(url, User.class);

return new Order(id, user);
}
}

Nacos 服务注册与发现

Nacos 是阿里巴巴开源的服务注册与发现组件,提供了更强大的功能,包括配置管理和服务管理。

Nacos 特点

  1. 服务注册与发现:支持临时和非临时实例
  2. 配置管理:支持配置的热更新
  3. 动态服务监听:支持服务上下线的实时通知
  4. 更友好的控制台:提供可视化的管理界面

搭建 Nacos Server

Nacos 可以通过多种方式运行:

# 下载并解压
wget https://github.com/alibaba/nacos/releases/download/2.2.0/nacos-server-2.2.0.tar.gz
tar -xvf nacos-server-2.2.0.tar.gz

# 启动(单机模式)
cd nacos/bin
sh startup.sh -m standalone

访问 http://localhost:8848/nacos(默认用户名/密码:nacos/nacos)

使用 Nacos 进行服务注册

<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
# 命名空间(用于环境隔离)
namespace: dev
# 分组
group: DEFAULT_GROUP
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}

Nacos 与 Eureka 对比

特性EurekaNacos
CAP 理论AP(最终一致)CP/AP 可切换
配置管理需要额外组件内置配置管理
控制台基础完善
活跃度较低(维护状态)活跃
社区Netflix阿里

服务健康检查

Eureka 健康检查

eureka:
instance:
health-check-url-path: /actuator/health
// 自定义健康检查
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// 检查业务逻辑
if (isServiceHealthy()) {
return Health.up().build();
}
return Health.down().withDetail("error", "Service unavailable").build();
}

private boolean isServiceHealthy() {
// 实际业务检查逻辑
return true;
}
}

Nacos 健康检查

Nacos 支持三种健康检查方式:

  1. 临时实例:基于心跳
  2. 永久实例:由注册方主动上报状态
  3. 权重:支持流量分配

服务发现的高级特性

1. 元数据

eureka:
instance:
metadata-map:
version: v1
region: shanghai
// 获取元数据
ServiceInstance instance = ...;
String version = instance.getMetadata().get("version");

2. 区域感知

eureka:
instance:
metadata-map:
zone: shanghai-zone
client:
prefer-same-zone-eureka: true

3. 自我保护

Eureka 的自我保护机制可以防止网络分区导致的服务误删:

eureka:
server:
# 关闭自我保护(生产环境建议开启)
enable-self-preservation: true
# 续约百分比阈值
renewal-percent-threshold: 0.85

最佳实践

  1. 高可用:生产环境使用集群部署注册中心
  2. 心跳配置:根据业务需求调整心跳间隔
  3. 元数据:合理使用元数据进行服务分组和路由
  4. 监控:监控服务注册中心和实例的健康状态
  5. 清理:及时清理过期实例

小结

服务注册与发现是微服务架构的基础设施,本章主要介绍了:

  1. 为什么需要服务注册与发现:解决服务动态性和负载均衡问题
  2. Eureka:Spring Cloud 官方推荐的服务注册组件
  3. Nacos:功能更强大的服务注册与配置管理组件
  4. 服务健康检查:确保只调用健康的服务实例

下一章我们将学习配置管理,了解如何集中管理微服务的配置。