跳到主要内容

服务注册与发现

服务注册与发现是微服务架构的核心基础设施。本章详细介绍 Nacos 服务发现的工作原理和使用方法。

服务发现概述

在微服务架构中,服务实例的数量和位置是动态变化的。服务发现机制解决了以下问题:

  • 服务注册:服务启动时向注册中心注册自己的地址
  • 服务发现:服务消费者从注册中心获取服务提供者的地址列表
  • 健康检查:检测服务实例是否健康,剔除不健康的实例
  • 负载均衡:在多个服务实例之间分配请求

Nacos 服务发现模式

Nacos 支持两种服务发现模式:

模式说明适用场景
DNS 模式通过 DNS 解析获取服务地址传统应用、Kubernetes 集成
RPC 模式通过 HTTP/gRPC 接口获取服务地址Spring Cloud、Dubbo 应用

服务注册

临时实例 vs 持久实例

Nacos 区分两种类型的服务实例:

特性临时实例持久实例
健康检查方式客户端心跳服务端主动探测
实例剔除心跳超时自动剔除不会自动剔除
存储方式内存优先持久化存储
适用场景微服务实例数据库、中间件等基础设施

临时实例适合微服务场景,实例会随着服务启停动态变化。持久实例适合需要长期稳定存在的服务。

Spring Cloud 服务注册

1. 添加依赖

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

2. 配置文件

spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: dev
group: DEFAULT_GROUP
# 注册为临时实例(默认 true)
ephemeral: true
# 服务权重(0-100)
weight: 1
# 服务元数据
metadata:
version: 1.0.0
region: cn-east

3. 启动服务

添加 @EnableDiscoveryClient 注解启用服务发现:

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

服务启动后会自动向 Nacos 注册。

手动注册服务

使用 Nacos Open API 手动注册服务:

curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance' \
-d 'serviceName=user-service' \
-d 'ip=192.168.1.100' \
-d 'port=8080' \
-d 'weight=1.0' \
-d 'ephemeral=true' \
-d 'metadata={"version":"1.0.0"}'

注册参数说明

参数说明默认值
serviceName服务名必填
ip实例 IP必填
port实例端口必填
weight权重(0.01-10000)1.0
enabled是否启用true
healthy是否健康true
ephemeral是否临时实例true
metadata元数据
clusterName集群名DEFAULT
groupName分组名DEFAULT_GROUP

服务发现

Spring Cloud 服务发现

1. 使用 RestTemplate

@Service
public class OrderService {

@Autowired
private RestTemplate restTemplate;

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

public User getUser(Long userId) {
// 使用服务名代替具体地址
String url = "http://user-service/users/" + userId;
return restTemplate.getForObject(url, User.class);
}
}

@LoadBalanced 注解使 RestTemplate 具备负载均衡能力,会自动从 Nacos 获取服务实例列表。

2. 使用 OpenFeign

// 定义 Feign 客户端
@FeignClient(name = "user-service")
public interface UserClient {

@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}

// 使用 Feign 客户端
@Service
public class OrderService {

@Autowired
private UserClient userClient;

public User getUser(Long userId) {
return userClient.getUser(userId);
}
}

手动发现服务

使用 Nacos Open API 查询服务实例:

# 查询服务实例列表
curl 'http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=user-service'

# 查询指定实例
curl 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=user-service&ip=192.168.1.100&port=8080'

使用 NamingService API

@Service
public class NacosService {

@Autowired
private NamingService namingService;

// 注册服务
public void registerInstance() throws NacosException {
namingService.registerInstance("user-service", "192.168.1.100", 8080);
}

// 获取服务实例列表
public List<Instance> getInstances() throws NacosException {
return namingService.selectInstances("user-service", true);
}

// 获取一个健康实例
public Instance selectOneHealthyInstance() throws NacosException {
return namingService.selectOneHealthyInstance("user-service");
}

// 订阅服务变化
public void subscribe() throws NacosException {
namingService.subscribe("user-service", new EventListener() {
@Override
public void onEvent(Event event) {
if (event instanceof NamingEvent) {
NamingEvent namingEvent = (NamingEvent) event;
System.out.println("服务实例变化: " + namingEvent.getInstances());
}
}
});
}
}

健康检查

临时实例健康检查

临时实例采用客户端心跳模式:

┌──────────────┐                    ┌──────────────┐
│ 服务实例 │ │ Nacos │
│ (客户端) │ │ Server │
└──────────────┘ └──────────────┘
│ │
│ 心跳请求 (每 5 秒) │
│ ─────────────────────────────────>│
│ │
│ 心跳响应 │
│<───────────────────────────────── │
│ │
│ │ 15 秒无心跳
│ │ → 标记为不健康
│ │
│ │ 30 秒无心跳
│ │ → 剔除实例

心跳时间配置:

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

持久实例健康检查

持久实例由 Nacos Server 主动探测,支持多种探测方式:

TCP 探测

# 注册持久实例时指定健康检查类型
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance' \
-d 'serviceName=mysql-service' \
-d 'ip=192.168.1.200' \
-d 'port=3306' \
-d 'ephemeral=false'

HTTP 探测

# 注册时指定健康检查 URL
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance' \
-d 'serviceName=web-service' \
-d 'ip=192.168.1.100' \
-d 'port=8080' \
-d 'ephemeral=false' \
-d 'metadata={"preserved.register.source":"SPRING_CLOUD","healthCheckUrl":"/health"}'

自定义健康检查

@RestController
public class HealthController {

@GetMapping("/health")
public Map<String, Object> health() {
Map<String, Object> result = new HashMap<>();
result.put("status", "UP");
// 可以添加更多健康检查逻辑
return result;
}
}

服务订阅

服务消费者可以订阅服务变化,当服务实例发生变化时收到通知:

使用 Spring Cloud

Spring Cloud 默认会订阅服务变化,自动更新本地服务列表缓存。

使用 NamingService

@Component
public class ServiceSubscriber implements ApplicationRunner {

@Autowired
private NamingService namingService;

@Override
public void run(ApplicationArguments args) throws Exception {
// 订阅服务变化
namingService.subscribe("user-service", new EventListener() {
@Override
public void onEvent(Event event) {
NamingEvent namingEvent = (NamingEvent) event;
List<Instance> instances = namingEvent.getInstances();
// 处理服务实例变化
instances.forEach(instance -> {
System.out.println("实例: " + instance.getIp() + ":" + instance.getPort());
});
}
});
}
}

负载均衡

权重配置

通过配置实例权重实现负载均衡:

spring:
cloud:
nacos:
discovery:
weight: 0.5 # 权重值,范围 0.01-10000

或者在控制台手动配置:

  1. 登录 Nacos 控制台
  2. 进入「服务管理」->「服务列表」
  3. 点击服务名进入详情
  4. 修改实例的权重值

权重越大,被选中的概率越高。可以用于灰度发布,将新版本的权重设置较低,逐步增加。

使用 Ribbon

Spring Cloud Alibaba 默认集成 Ribbon,支持多种负载均衡策略:

user-service:
ribbon:
# 负载均衡策略
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule

NacosRule 会优先选择同一集群的实例,并支持权重负载均衡。

使用 Spring Cloud LoadBalancer

Spring Cloud 2020+ 版本推荐使用 Spring Cloud LoadBalancer:

spring:
cloud:
loadbalancer:
ribbon:
enabled: false
nacos:
enabled: true

服务元数据

元数据用于存储服务的附加信息,如版本、区域、环境等:

配置元数据

spring:
cloud:
nacos:
discovery:
metadata:
version: 1.0.0
region: cn-east
env: prod

使用元数据

@Service
public class MetadataService {

@Autowired
private NamingService namingService;

public void processWithMetadata() throws NacosException {
List<Instance> instances = namingService.selectInstances("user-service", true);

for (Instance instance : instances) {
Map<String, String> metadata = instance.getMetadata();
String version = metadata.get("version");
String region = metadata.get("region");

// 根据元数据筛选实例
if ("1.0.0".equals(version) && "cn-east".equals(region)) {
// 处理请求
}
}
}
}

元数据路由

通过元数据实现服务路由,例如按版本路由:

@Configuration
public class LoadBalancerConfig {

@Bean
public ReactorLoadBalancer<ServiceInstance> metadataLoadBalancer(
Environment environment, LoadBalancerClientFactory factory) {
String serviceId = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new MetadataLoadBalancer(
factory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
serviceId);
}
}

服务分组与集群

服务分组

通过分组隔离不同业务的服务:

spring:
cloud:
nacos:
discovery:
group: ORDER_GROUP # 订单服务组

集群配置

通过集群实现同机房优先访问:

spring:
cloud:
nacos:
discovery:
cluster-name: SHANGHAI # 上海机房

当消费者和服务提供者在同一集群时,会优先访问同集群的实例。

保护阈值

保护阈值用于防止服务雪崩。当健康实例比例低于阈值时,Nacos 会返回所有实例(包括不健康的),让消费者自行判断。

spring:
cloud:
nacos:
discovery:
protect-threshold: 0.5 # 健康实例比例阈值

设置保护阈值后,即使大部分实例不健康,也能保证部分流量正常访问。

常见问题

1. 服务注册失败

检查以下几点:

  • Nacos Server 是否正常运行
  • 网络是否连通
  • 命名空间和分组配置是否正确
  • 查看应用日志是否有错误信息

2. 服务发现不到实例

检查以下几点:

  • 服务是否已注册成功
  • 命名空间和分组是否一致
  • 服务实例是否健康
  • 查看控制台服务列表

3. 心跳超时

如果出现心跳超时,可以调整心跳参数:

spring:
cloud:
nacos:
discovery:
heart-beat-interval: 10000 # 增加心跳间隔
heart-beat-timeout: 30000 # 增加超时时间

4. 服务实例频繁上下线

可能原因:

  • 网络不稳定
  • 服务频繁重启
  • JVM 内存不足导致频繁 GC
  • 心跳线程被阻塞

下一步