{T}

服务发现与配置中心

微服务一旦拆开,系统面对的第一个现实问题通常不是"代码怎么写",而是:

  • 服务怎么找到彼此
  • 配置怎么统一管理
  • 实例变化后调用方怎么感知
  • 配置变更后如何安全生效

单体时代可以把地址和参数直接写进本地配置文件,但到了微服务场景:

  • 实例数量会动态变化
  • 服务会随时扩缩容
  • 容器和节点会重建
  • 环境差异会越来越多
  • 配置变更不能总靠人工改机器

所以服务发现和配置中心,本质上是在解决微服务运行过程中的"基本秩序"问题。

一、服务发现核心原理

1.1 为什么需要服务发现

单体时代的做法为什么不够用了

在单体或很早期的分布式系统里,调用方经常直接配置目标地址,例如:

  • http://10.0.0.8:8080
  • http://user-service-prod.example.com

这种方式在服务实例很少、变化不频繁时还能工作,但在微服务场景下会快速暴露问题:

  • 实例重建后 IP 会变化
  • 容器扩容后地址会增减
  • 某些实例故障后需要及时摘除
  • 调用方不可能手工维护一长串实例地址

所以调用方真正需要的不是"某个实例地址",而是:

  • 我要调用哪个服务

至于这个服务当前有哪些可用实例、该打到哪一个实例,应由注册发现机制和负载均衡机制来决定。

服务发现解决的核心问题

服务发现的目标,是让调用方只关心逻辑服务名,而不用手写实例地址。

核心价值:

  • 解耦服务名与实例地址 - 调用方只需知道服务名,不需要维护具体的实例地址
  • 动态感知实例变化 - 实例上下线时,调用方能够及时感知并更新
  • 支持负载均衡 - 提供可用实例列表,配合负载均衡策略进行流量分发
  • 提高系统弹性 - 实例故障时自动摘除,恢复时自动注册

1.2 服务发现的基本模型

一个典型的服务发现流程包含以下步骤:

code
服务提供者                    注册中心                    服务消费者
    |                           |                           |
    |--1. 服务注册------------->|                           |
    |   (服务名,IP,端口)        |                           |
    |                           |                           |
    |--2. 心跳续约------------->|                           |
    |   (定时发送)              |                           |
    |                           |                           |
    |                           |<--3. 订阅服务-------------|
    |                           |   (服务名)                |
    |                           |                           |
    |                           |--4. 返回实例列表--------->|
    |                           |   (IP列表)                |
    |                           |                           |
    |                           |--5. 实例变更推送--------->|
    |   (实例下线)              |   (推送新列表)            |
    |                           |                           |

核心流程:

  1. 服务注册 - 服务实例启动后向注册中心注册自己
  2. 心跳续约 - 服务实例定期向注册中心发送心跳
  3. 服务订阅 - 调用方订阅目标服务的实例列表
  4. 实例列表获取 - 调用方获取并缓存可用实例列表
  5. 变更通知 - 实例变化时,注册中心推送最新列表给订阅者

1.3 服务发现的两种架构模式

客户端发现模式 (Client-Side Discovery)

工作原理:

code
服务消费者                    注册中心                    服务提供者
    |                           |                           |
    |--查询实例列表------------>|                           |
    |<--返回所有实例-------------|                           |
    |                           |                           |
    |--选择实例(负载均衡)-------|                           |
    |                           |                           |
    |--------------------------直接调用--------------------->|
    |                           |                           |

特点:

  • 调用方直接查询注册中心获取实例列表
  • 调用方在本地做负载均衡
  • 调用方直接访问目标服务实例

优点:

  • 架构简单,没有额外代理层
  • 客户端可以灵活控制负载均衡策略
  • 减少一次网络跳转,性能更好

缺点:

  • 客户端需要实现服务发现逻辑
  • 与注册中心耦合,客户端需要集成特定 SDK
  • 不同语言需要不同实现

代表实现:

  • Netflix Eureka + Ribbon
  • Nacos + Spring Cloud LoadBalancer
  • Consul + Spring Cloud Consul

服务端发现模式 (Server-Side Discovery)

工作原理:

code
服务消费者                    负载均衡器/网关            注册中心        服务提供者
    |                           |                           |               |
    |--调用服务名-------------->|                           |               |
    |                           |--查询实例列表------------>|               |
    |                           |<--返回实例列表-------------|               |
    |                           |--负载均衡选择实例---------|               |
    |                           |--------------------------直接调用------->|
    |<--返回响应----------------|                           |               |

特点:

  • 调用方通过负载均衡器或网关访问服务
  • 负载均衡器负责查询注册中心和负载均衡
  • 调用方不需要感知具体的注册中心

优点:

  • 客户端逻辑简单,只需调用统一入口
  • 与注册中心解耦,客户端不需要集成 SDK
  • 便于统一管理和监控

缺点:

  • 多一次网络跳转,性能略有损耗
  • 负载均衡器成为单点故障和性能瓶颈
  • 架构更复杂,需要维护额外的组件

代表实现:

  • Kubernetes Service + kube-proxy
  • Nginx + Service Discovery Plugin
  • Spring Cloud Gateway + Nacos

混合模式

在实际生产环境中,经常会结合两种模式:

  • 内部服务调用 - 使用客户端发现模式,性能更好
  • 外部流量入口 - 使用服务端发现模式(API 网关),便于统一管控

1.4 注册中心的核心机制

服务注册 (Service Registration)

实例启动后会把自己的信息上报到注册中心,常见内容包括:

基础信息:

  • 服务名 (service name)
  • IP 地址
  • 端口号 (port)
  • 协议类型 (protocol: HTTP/Dubbo/gRPC)

元数据 (Metadata):

  • 机房/可用区 (zone/region)
  • 集群标识 (cluster)
  • 版本号 (version)
  • 权重 (weight)
  • 环境标识 (env: dev/test/prod)
  • 自定义标签 (tags)

注册方式:

  1. 主动注册 - 服务启动后主动调用注册中心 API
  2. 被动注册 - 通过健康检查或 Sidecar 代理注册
java
// Spring Cloud Nacos 服务注册示例
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
yaml
# application.yml
spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: public
        group: DEFAULT_GROUP
        metadata:
          version: 1.0.0
          zone: beijing

心跳续约 (Heartbeat Renewal)

"注册过一次"不代表"当前还活着"。因此实例通常需要定期上报心跳,告诉注册中心:

  • 我还在线
  • 我还能继续接流量

心跳机制设计:

参数说明典型值
心跳间隔实例发送心跳的时间间隔5-30秒
超时时间注册中心多久没收到心跳认为实例失效15-90秒
过期时间实例被标记为不健康后的保留时间90-180秒

心跳超时的影响:

  • 太短 - 网络抖动容易误判实例下线
  • 太长 - 故障实例不能及时摘除,影响可用性

典型配置:

yaml
spring:
  cloud:
    nacos:
      discovery:
        heart-beat-interval: 5000      # 心跳间隔 5秒
        heart-beat-timeout: 15000      # 心跳超时 15秒
        ip-delete-timeout: 30000       # IP删除超时 30秒

健康检查 (Health Check)

除了心跳,注册中心还可以主动对实例进行健康检查:

健康检查类型:

  1. 应用层健康检查

    • HTTP 检查 - 访问特定健康检查端点
    • TCP 检查 - 检查端口是否可连
    • 自定义检查 - 执行自定义脚本
  2. 平台层健康检查

    • K8s liveness/readiness probe
    • 容器状态检查

健康检查端点示例:

java
@RestController
public class HealthController {
    
    @GetMapping("/health")
    public Map<String, Object> health() {
        Map<String, Object> health = new HashMap<>();
        health.put("status", "UP");
        health.put("db", checkDatabase() ? "UP" : "DOWN");
        health.put("redis", checkRedis() ? "UP" : "DOWN");
        return health;
    }
}

健康检查配置 (Nacos):

yaml
spring:
  cloud:
    nacos:
      discovery:
        health-check-enabled: true
        health-check-interval: 10000

实例摘除与恢复 (Instance Eviction & Recovery)

实例摘除的触发条件:

  • 心跳超时
  • 健康检查失败
  • 主动注销 (graceful shutdown)

实例摘除流程:

  1. 注册中心标记实例为不健康
  2. 通知订阅该服务的消费者
  3. 消费者更新本地缓存,移除该实例
  4. 一段时间后仍未恢复,从注册列表彻底删除

实例恢复流程:

  1. 实例恢复后重新发送心跳
  2. 注册中心重新标记为健康
  3. 通知消费者更新实例列表
  4. 消费者重新将实例加入负载均衡池

服务订阅与变更感知 (Subscription & Change Notification)

调用方通常不会每次调用都去查注册中心,而是:

  • 订阅实例列表变化
  • 在本地维护一份可用实例缓存

订阅模式:

  1. 推送模式 (Push)

    • 注册中心主动推送变更
    • 实时性好,但实现复杂
    • 需要维护长连接
  2. 拉取模式 (Pull)

    • 定时轮询注册中心
    • 实现简单,但有延迟
    • 可能产生不必要的查询
  3. 长轮询模式 (Long Polling)

    • 结合推送和拉取的优点
    • Nacos 默认使用长轮询

本地缓存策略:

code
消费者启动
    ↓
订阅服务实例列表
    ↓
缓存到本地内存
    ↓
┌──────┐
│ 定时更新 ←─────┐
└──────┘         │
    ↓            │
变更通知 ─────────┘
    ↓
更新本地缓存
    ↓
触发负载均衡器刷新

缓存带来的问题:

  • 本地缓存是否及时更新
  • 异常节点是否还停留在旧缓存里

这也是很多"服务明明好了但还调不到""服务明明挂了却还在被调用"的根源。

1.5 服务发现与负载均衡的协作

服务发现通常和负载均衡一起工作。调用方按服务名发起请求后,实际还要解决两个问题:

  • 该服务当前有哪些可用实例
  • 这次具体打到哪一个实例

调用链路:

  1. 调用方只写服务名,例如 user-service
  2. 注册中心提供该服务的实例列表
  3. 客户端负载均衡或网关在实例列表中选择一个实例
  4. 请求最终发到真实节点

从架构职责上看:

  • 注册中心负责"有哪些实例可用"
  • 负载均衡负责"这次该打到谁"

负载均衡策略:

策略说明适用场景
轮询 (Round Robin)按顺序依次选择实例实例性能相近
随机 (Random)随机选择实例实例性能相近
加权轮询 (Weighted Round Robin)按权重分配流量实例性能差异大
最少连接 (Least Connections)选择当前连接最少的实例长连接场景
一致性哈希 (Consistent Hash)相同请求参数打到相同实例有状态服务
地域感知 (Zone Aware)优先选择同可用区实例多机房部署

Spring Cloud LoadBalancer 示例:

java
@Configuration
public class LoadBalancerConfig {
    
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment environment, 
            LoadBalancerClientFactory factory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
            factory.getLazyProvider(name, ServiceInstanceListSupplier.class), 
            name
        );
    }
}
yaml
spring:
  cloud:
    loadbalancer:
      ribbon:
        enabled: false  # 禁用 Ribbon,使用 Spring Cloud LoadBalancer
      configurations: random  # 使用随机负载均衡

二、主流注册中心对比

2.1 注册中心概览

目前主流的服务注册中心包括:

注册中心开发语言一致性协议健康检查配置中心社区活跃度
NacosJavaAP/CP 可选客户端心跳 + 服务端检查√ 支持
EurekaJavaAP客户端心跳× 不支持(维护模式)
ConsulGoCP服务端检查√ 支持
ZookeeperJavaCP客户端心跳× 不支持

2.2 CAP 理论与注册中心选择

在分布式系统中,CAP 理论指出:

  • C (Consistency) - 一致性: 所有节点在同一时间看到的数据是一致的
  • A (Availability) - 可用性: 每个请求都能在合理时间内得到响应
  • P (Partition Tolerance) - 分区容错性: 网络分区发生时系统仍能运行

P 是必须的,因此只能在 C 和 A 之间权衡:

AP 架构 (优先可用性)

代表: Eureka, Nacos (默认)

特点:

  • 注册中心节点之间数据可能不一致
  • 网络分区时,每个节点仍可独立提供服务
  • 优先保证服务可用,容忍短暂的数据不一致

优点:

  • 高可用,任何节点都能处理请求
  • 网络分区时不会影响服务注册和发现
  • 适合对可用性要求极高的场景

缺点:

  • 可能读到脏数据(已下线的实例仍在列表中)
  • 数据最终一致性,有延迟

适用场景:

  • 服务数量大,调用频繁
  • 对可用性要求高
  • 能容忍短暂的实例列表不一致

CP 架构 (优先一致性)

代表: Zookeeper, Consul, Nacos (可切换)

特点:

  • 所有节点数据强一致
  • 网络分区时,多数派节点才能提供服务
  • 优先保证数据一致性,牺牲部分可用性

优点:

  • 数据强一致,不会出现脏数据
  • 选举机制保证集群状态一致性

缺点:

  • 网络分区时部分节点不可用
  • 选举期间服务不可用(通常秒级)
  • 性能相对较低

适用场景:

  • 对数据一致性要求高
  • 服务数量相对较少
  • 能接受短暂的不可用

2.3 Nacos 详细介绍

Nacos 简介

Nacos (Dynamic Naming and Configuration Service) 是阿里巴巴开源的服务发现和配置管理平台。

核心特性:

  • 服务发现和服务健康检查
  • 动态配置管理
  • 动态 DNS 服务
  • 服务元数据管理

优势:

  • 同时支持 AP 和 CP 模式
  • 国内生态完善,中文文档齐全
  • 与 Spring Cloud、Dubbo 深度集成
  • 阿里云商业化产品,生产验证充分

Nacos 架构

code
┌─────────────────────────────────────────────────────────┐
│                      Nacos Console                       │
│                    (管理控制台)                           │
└─────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────┐
│                     Nacos Server                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │  Open API    │  │  Config      │  │  Naming      │  │
│  │  (HTTP API)  │  │  Service     │  │  Service     │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│  ┌────────────────────────────────────────────────────┐ │
│  │              Consistency Protocol                  │ │
│  │         (Raft / Distro)                            │ │
│  └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────┐
│                     Storage Layer                        │
│            (MySQL / Derby Embedded)                      │
└─────────────────────────────────────────────────────────┘

核心组件:

  1. Naming Service - 服务注册与发现
  2. Config Service - 配置管理
  3. Consistency Protocol - 一致性协议 (Raft/CP 或 Distro/AP)
  4. Storage - 数据持久化 (MySQL 或内嵌 Derby)

Nacos 数据模型

命名空间 (Namespace):

  • 用于实现多环境隔离 (dev/test/prod)
  • 不同命名空间的服务实例相互隔离

分组 (Group):

  • 同一命名空间下的进一步隔离
  • 可用于不同项目或业务线

服务 (Service):

  • 服务名是服务的唯一标识
  • 格式: ${group_name}@@${service_name}

集群 (Cluster):

  • 同一服务可以部署在不同集群
  • 用于实现同机房优先调用

实例 (Instance):

  • 具体的服务实例 (IP:Port)
  • 包含权重、元数据等信息

数据模型层级:

code
Namespace (命名空间)
    └── Group (分组)
        └── Service (服务)
            └── Cluster (集群)
                └── Instance (实例)

Nacos 集群部署

部署架构:

code
                    ┌─────────────┐
                    │   Nginx     │
                    │ (负载均衡)   │
                    └─────────────┘
                          ↓
        ┌─────────────────┼─────────────────┐
        ↓                 ↓                 ↓
┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│   Nacos     │   │   Nacos     │   │   Nacos     │
│   Node 1    │   │   Node 2    │   │   Node 3    │
│  (Leader)   │   │ (Follower)  │   │ (Follower)  │
└─────────────┘   └─────────────┘   └─────────────┘
        └─────────────────┼─────────────────┘
                          ↓
                    ┌─────────────┐
                    │   MySQL     │
                    │  (主从集群)  │
                    └─────────────┘

集群配置 (cluster.conf):

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

application.properties:

properties
# 数据库配置
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.1.100:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=nacos
db.password.0=nacos

# 集群配置
nacos.member.list=192.168.1.1:8848,192.168.1.2:8848,192.168.1.3:8848

Nacos 一致性模式切换

AP 模式 (默认):

properties
# 使用 Distro 协议
nacos.core.protocol.raft.data.enabled=false
nacos.core.protocol.distro.enabled=true

CP 模式:

properties
# 使用 Raft 协议
nacos.core.protocol.raft.data.enabled=true
nacos.core.protocol.distro.enabled=false

切换场景:

  • AP 模式 - 适合服务发现场景,优先可用性
  • CP 模式 - 适合配置管理场景,优先一致性

Spring Cloud 集成 Nacos

依赖:

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

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2022.0.0.0</version>
</dependency>

配置文件 (bootstrap.yml):

yaml
spring:
  application:
    name: user-service
  cloud:
    nacos:
      # 服务发现配置
      discovery:
        server-addr: 192.168.1.1:8848,192.168.1.2:8848,192.168.1.3:8848
        namespace: prod
        group: USER_GROUP
        cluster-name: BEIJING
        metadata:
          version: 1.0.0
          preserved.register.source: SPRING_CLOUD
      # 配置中心配置
      config:
        server-addr: 192.168.1.1:8848,192.168.1.2:8848,192.168.1.3:8848
        namespace: prod
        group: USER_GROUP
        file-extension: yaml
        shared-configs:
          - data-id: common.yaml
            group: COMMON_GROUP
            refresh: true

2.4 Eureka 详细介绍

Eureka 简介

Eureka 是 Netflix 开源的服务发现组件,Spring Cloud Netflix 的核心组件之一。

核心组件:

  • Eureka Server - 服务注册中心
  • Eureka Client - 服务提供者和消费者

架构特点:

  • 纯 AP 架构,优先可用性
  • 各节点平等,无主从之分
  • 自我保护机制,防止网络分区导致的误摘除

Eureka 架构

code
┌─────────────────────────────────────────────────────────┐
│                   Application Service                    │
│                     (服务提供者)                          │
└─────────────────────────────────────────────────────────┘
        ↓ Register/Renew/Cancel        ↑ Query
┌─────────────────────────────────────────────────────────┐
│                    Eureka Server                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │   Registry   │  │    Peer      │  │   Service    │  │
│  │   (注册表)    │  │  Replication │  │   Status     │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
└─────────────────────────────────────────────────────────┘
        ↑ Query                          ↓ Replicate
┌─────────────────────────────────────────────────────────┐
│                   Application Client                     │
│                     (服务消费者)                          │
└─────────────────────────────────────────────────────────┘

Eureka 自我保护机制

触发条件:

  • 分钟内续约比例 < 85%
  • 认为发生网络分区,进入自我保护模式

自我保护行为:

  • 不再剔除任何服务实例
  • 即使实例已下线,仍保留在注册表中
  • 保证注册中心的可用性

配置:

yaml
eureka:
  server:
    enable-self-preservation: true      # 开启自我保护
    renewal-percent-threshold: 0.85     # 续约比例阈值
    eviction-interval-timer-in-ms: 60000 # 剔除任务间隔

Eureka 的局限性

已停止维护:

  • Netflix 宣布 Eureka 2.x 停止开发
  • Spring Cloud 2020 版本后已移除 Eureka
  • 不建议新项目使用

架构局限:

  • 只支持 AP 模式,不支持 CP
  • 不支持配置管理
  • 性能相对 Nacos 较低
  • 社区活跃度低

2.5 Consul 详细介绍

Consul 简介

Consul 是 HashiCorp 开源的服务网格解决方案,提供服务发现、配置管理、健康检查等功能。

核心特性:

  • 服务发现
  • 健康检查
  • KV 存储
  • 多数据中心支持
  • 服务网格 (Service Mesh)

架构特点:

  • CP 架构,基于 Raft 协议
  • 强一致性保证
  • 支持多数据中心

Consul 架构

code
┌─────────────────────────────────────────────────────────┐
│                      Consul Server                       │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │   HTTP API   │  │    DNS       │  │    Raft      │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
└─────────────────────────────────────────────────────────┘
        ↓ Register           ↑ Query          ↓ RPC
┌─────────────────────────────────────────────────────────┐
│                      Consul Agent                        │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │   Service    │  │   Health     │  │   Proxy      │  │
│  │   (服务)      │  │   Check      │  │  (代理)       │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
└─────────────────────────────────────────────────────────┘

Consul 服务注册

服务定义文件:

json
{
  "service": {
    "name": "user-service",
    "tags": ["v1"],
    "address": "192.168.1.100",
    "port": 8080,
    "check": {
      "http": "http://192.168.1.100:8080/health",
      "interval": "10s",
      "timeout": "1s"
    }
  }
}

Spring Cloud 集成:

yaml
spring:
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: user-service
        health-check-path: /health
        health-check-interval: 10s

2.6 Zookeeper 详细介绍

Zookeeper 简介

Zookeeper 是 Apache 顶级项目,最初用于 Hadoop 生态,后来被广泛用于服务发现、配置管理、分布式锁等场景。

核心特性:

  • CP 架构,基于 ZAB 协议
  • 强一致性保证
  • 临时节点特性适合服务注册

架构特点:

  • 树形目录结构
  • 节点类型: 持久节点、临时节点、顺序节点
  • Watcher 机制实现变更通知

Zookeeper 服务发现实现

服务注册:

code
/services
    /user-service
        /instance-00000001 (临时节点)
            数据: {"host":"192.168.1.100","port":8080}
        /instance-00000002 (临时节点)
            数据: {"host":"192.168.1.101","port":8080}

服务发现:

  1. 客户端订阅 /services/user-service 目录
  2. 获取目录下所有子节点
  3. 注册 Watcher 监听节点变化
  4. 节点变化时收到通知,重新获取实例列表

Spring Cloud Zookeeper:

yaml
spring:
  cloud:
    zookeeper:
      connect-string: 192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181
      discovery:
        enabled: true
        register: true
        instance-host: 192.168.1.100
        instance-port: 8080

Zookeeper 的局限性

  • 不适合大规模服务发现 (性能问题)
  • 需要自行实现服务发现逻辑
  • 没有内置的健康检查机制
  • 临时节点会话超时会导致服务误下线

2.7 注册中心选型建议

选型维度

维度权重说明
社区活跃度社区活跃意味着更好的支持和生态
功能完整性是否同时支持服务发现和配置管理
性能高并发场景下的性能表现
易用性文档、API、控制台的易用程度
生态集成与 Spring Cloud、Dubbo 的集成程度
高可用集群部署和容灾能力
一致性模型AP 还是 CP,是否支持切换

场景化推荐

Spring Cloud 微服务架构:

  • 首选: Nacos - 功能完整,生态完善,国内生产验证充分
  • 备选: Consul - 如果公司已有 HashiCorp 技术栈

Dubbo 微服务架构:

  • 首选: Nacos - Dubbo 官方推荐,集成度高
  • 备选: Zookeeper - 老项目迁移成本较低

Kubernetes 环境:

  • 首选: Kubernetes Service + CoreDNS - 原生支持,无需额外组件
  • 备选: Nacos/Consul - 如果需要更复杂的服务治理

多语言异构系统:

  • 首选: Consul - 支持多语言 SDK,HTTP API 友好
  • 备选: Nacos - HTTP API 完善,便于非 Java 语言接入

对一致性要求极高的场景:

  • 首选: ConsulZookeeper - CP 架构,强一致性保证
  • 备选: Nacos (CP 模式)

三、配置中心核心原理

3.1 为什么需要配置中心

配置集中管理解决了什么问题

微服务拆分后,每个服务通常都有大量运行时配置,例如:

基础配置:

  • 数据库连接信息
  • Redis、MQ 中间件地址
  • 超时参数
  • 线程池大小

业务配置:

  • 限流阈值
  • 灰度开关
  • 特性开关 (Feature Toggle)
  • 业务规则参数

安全配置:

  • 第三方接口密钥
  • 加密密钥
  • 访问令牌

如果这些配置分散在各台机器、本地文件或多个仓库里,常见问题会很快出现:

  • 不知道线上到底生效的是哪份配置
  • 环境差异越来越混乱
  • 改一个配置要逐台修改
  • 配置无法审计、无法回滚

配置中心的核心价值

配置中心的目标,就是把配置从服务本地抽离出来,统一管理、统一变更、统一追踪。

核心价值:

  • 统一来源 - 配置有唯一可信来源,避免配置分散
  • 环境隔离 - 开发、测试、预发、生产环境配置隔离
  • 动态更新 - 配置变更无需重启服务即可生效
  • 版本管理 - 配置变更有版本记录,支持回滚
  • 权限控制 - 配置变更有权限控制,防止误操作
  • 审计追踪 - 配置变更有审计日志,便于追溯

3.2 配置中心的核心功能

配置存储与版本管理

配置存储结构:

code
Namespace (命名空间)
    └── Group (分组)
        └── Data ID (配置ID)
            ├── Content (配置内容)
            ├── Version (版本号)
            ├── Create Time (创建时间)
            ├── Modify Time (修改时间)
            └── History (历史版本)

版本管理能力:

  • 每次配置变更自动生成新版本
  • 支持查看历史版本
  • 支持一键回滚到指定版本
  • 支持版本对比 (Diff)

配置分发机制

推送模式 (Push):

code
配置中心 ──推送变更──> 服务实例
  • 配置变更后主动推送给订阅的服务实例
  • 实时性好,但实现复杂
  • 需要维护长连接

拉取模式 (Pull):

code
服务实例 ──定时拉取──> 配置中心
  • 服务实例定时从配置中心拉取配置
  • 实现简单,但有延迟
  • 可能产生不必要的查询

长轮询模式 (Long Polling):

code
服务实例 ──发起长轮询──> 配置中心
         ↓ (等待30秒)
配置变更 ──立即返回──> 服务实例
  • 结合推送和拉取的优点
  • 实时性好,减少无效查询
  • Nacos 默认使用长轮询

配置刷新机制

配置刷新流程:

code
配置变更
    ↓
推送到服务实例
    ↓
触发配置刷新事件
    ↓
Spring Cloud Context 刷新
    ↓
重新绑定配置到 Bean
    ↓
业务逻辑生效

@RefreshScope 注解:

java
@RestController
@RefreshScope  // 支持配置动态刷新
public class ConfigController {
    
    @Value("${app.config.threshold:100}")
    private int threshold;
    
    @GetMapping("/threshold")
    public int getThreshold() {
        return threshold;
    }
}

刷新范围控制:

java
@Configuration
public class RefreshConfig {
    
    @Bean
    @RefreshScope
    public SomeService someService() {
        return new SomeService();
    }
}

3.3 配置分类与管理策略

配置分类

配置类型变更频率是否支持热更新管理方式
启动配置× 不支持本地配置文件
环境配置× 不支持配置中心
业务参数√ 支持配置中心
开关配置√ 支持配置中心

配置优先级

Spring Cloud 配置优先级 (从高到低):

  1. 命令行参数
  2. Java 系统属性
  3. 操作系统环境变量
  4. 配置中心配置
  5. application.yml (本地)
  6. 默认值

优先级问题:

  • 配置来源多不可怕,可怕的是优先级不清
  • 需要明确文档说明各环境配置来源和优先级
  • 生产环境应限制本地配置覆盖远端配置

配置变更流程

标准流程:

code
变更申请 ─> 审批 ─> 测试环境验证 ─> 预发环境验证 ─> 灰度发布 ─> 全量发布 ─> 监控观察

配置变更规范:

  1. 变更申请 - 明确变更内容、原因、影响范围
  2. 审批 - 核心配置需要多人审批
  3. 测试验证 - 在测试环境验证配置正确性
  4. 灰度发布 - 先在部分实例生效,观察无异常后再全量
  5. 监控告警 - 配置变更后密切监控关键指标
  6. 回滚预案 - 准备好回滚方案,一旦异常立即回滚

3.4 配置中心的高可用设计

配置中心高可用架构

code
                    ┌─────────────┐
                    │   客户端     │
                    └─────────────┘
                          ↓
        ┌─────────────────┼─────────────────┐
        ↓                 ↓                 ↓
┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│  Config     │   │  Config     │   │  Config     │
│  Server 1   │   │  Server 2   │   │  Server 3   │
└─────────────┘   └─────────────┘   └─────────────┘
        └─────────────────┼─────────────────┘
                          ↓
                    ┌─────────────┐
                    │   MySQL     │
                    │  (主从集群)  │
                    └─────────────┘

客户端容错机制

本地缓存:

  • 服务启动时从配置中心拉取配置并缓存到本地
  • 配置中心不可用时,从本地缓存读取配置
  • 保证服务不会因配置中心故障而无法启动

配置快照:

java
// Nacos 配置快照
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        // 配置中心故障时,从本地快照加载配置
        System.setProperty("nacos.cache.enabled", "true");
        SpringApplication.run(Application.class, args);
    }
}

Failfast vs Failover:

  • Failfast - 配置中心不可用时直接启动失败 (适用于关键配置)
  • Failover - 配置中心不可用时使用本地缓存 (适用于非关键配置)

3.5 Nacos 配置管理详解

Nacos 配置管理架构

code
┌─────────────────────────────────────────────────────────┐
│                    Nacos Config                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │   Config     │  │   Config     │  │   Config     │  │
│  │   Open API   │  │   Service    │  │   History    │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
└─────────────────────────────────────────────────────────┘
        ↓ Pull/Push         ↑ Long Polling
┌─────────────────────────────────────────────────────────┐
│                  Spring Cloud App                        │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │  Nacos       │  │  Property    │  │  Refresh     │  │
│  │  Config      │  │  Source      │  │  Scope       │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
└─────────────────────────────────────────────────────────┘

Nacos 配置模型

命名空间 (Namespace):

  • 用于环境隔离
  • 例如: dev、test、prod

分组 (Group):

  • 用于项目或业务隔离
  • 例如: USER_GROUP、ORDER_GROUP

Data ID:

  • 配置的唯一标识
  • 格式: ${prefix}-${spring.profiles.active}.${file-extension}
  • 例如: user-service-dev.yaml

Nacos 配置最佳实践

多环境配置:

yaml
# bootstrap.yml
spring:
  application:
    name: user-service
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: ${spring.profiles.active}
        group: USER_GROUP
        file-extension: yaml

配置文件命名规范:

code
user-service.yaml          # 基础配置
user-service-dev.yaml      # 开发环境配置
user-service-test.yaml     # 测试环境配置
user-service-prod.yaml     # 生产环境配置
common.yaml                # 公共配置

共享配置:

yaml
spring:
  cloud:
    nacos:
      config:
        shared-configs:
          - data-id: common.yaml
            group: COMMON_GROUP
            refresh: true
          - data-id: redis.yaml
            group: COMMON_GROUP
            refresh: false

扩展配置:

yaml
spring:
  cloud:
    nacos:
      config:
        extension-configs:
          - data-id: datasource.yaml
            group: INFRA_GROUP
            refresh: false
          - data-id: mybatis.yaml
            group: INFRA_GROUP
            refresh: false

Nacos 配置动态刷新示例

配置内容 (user-service.yaml):

yaml
app:
  config:
    threshold: 100
    enabled: true
    rules:
      - rule1
      - rule2

配置类:

java
@Configuration
@RefreshScope
public class AppConfig {
    
    @Value("${app.config.threshold:100}")
    private int threshold;
    
    @Value("${app.config.enabled:true}")
    private boolean enabled;
    
    @Bean
    @RefreshScope
    public SomeService someService() {
        return new SomeService(threshold, enabled);
    }
    
    // getter methods
}

配置变更监听:

java
@Component
public class ConfigChangeListener {
    
    @NacosConfigListener(dataId = "user-service.yaml", groupId = "USER_GROUP")
    public void onConfigChange(String newConfig) {
        log.info("配置变更: {}", newConfig);
        // 处理配置变更逻辑
    }
}

四、高可用与集群部署

4.1 注册中心高可用

集群部署架构

标准三节点集群:

code
                ┌─────────────┐
                │   Nginx     │
                │ (负载均衡)   │
                └─────────────┘
                      ↓
    ┌─────────────────┼─────────────────┐
    ↓                 ↓                 ↓
┌───────────┐   ┌───────────┐   ┌───────────┐
│  Nacos    │   │  Nacos    │   │  Nacos    │
│  Node 1   │◄──┤  Node 2   ├──►│  Node 3   │
│ (Leader)  │   │(Follower) │   │(Follower) │
└───────────┘   └───────────┘   └───────────┘
    └─────────────────┼─────────────────┘
                      ↓
                ┌─────────────┐
                │   MySQL     │
                │  (主从)      │
                └─────────────┘

部署要点:

  • 至少 3 个节点 (奇数个)
  • 节点之间网络延迟 < 50ms
  • 使用负载均衡器分发请求
  • 数据持久化到 MySQL 主从集群

节点故障处理

故障检测:

  • 心跳超时检测
  • 节点间健康检查
  • 自动剔除异常节点

故障恢复:

  • 节点恢复后自动重新加入集群
  • 数据自动同步
  • 服务自动重新注册

跨机房部署

同城双机房:

code
机房A                          机房B
┌─────────────────┐          ┌─────────────────┐
│  Nacos Cluster  │◄────────►│  Nacos Cluster  │
│  (2节点)         │   同步    │  (2节点)         │
└─────────────────┘          └─────────────────┘
        ↓                            ↓
┌─────────────────┐          ┌─────────────────┐
│   MySQL 主      │◄────────►│   MySQL 从      │
└─────────────────┘   主从    └─────────────────┘

异地多活:

code
北京机房                      上海机房
┌─────────────────┐          ┌─────────────────┐
│  Nacos Cluster  │          │  Nacos Cluster  │
│  (独立集群)      │          │  (独立集群)      │
└─────────────────┘          └─────────────────┘
        ↓                            ↓
┌─────────────────┐          ┌─────────────────┐
│  本地服务实例    │          │  本地服务实例    │
│  (优先本地调用)  │          │  (优先本地调用)  │
└─────────────────┘          └─────────────────┘

4.2 配置中心高可用

数据备份与恢复

MySQL 数据备份:

bash
# 全量备份
mysqldump -h 192.168.1.100 -u root -p nacos > nacos_backup.sql

# 增量备份 (binlog)
mysqlbinlog --start-datetime="2024-01-01 00:00:00" \
            --stop-datetime="2024-01-02 00:00:00" \
            mysql-bin.000001 > increment.sql

配置导出:

bash
# Nacos 配置导出
curl -X GET "http://nacos:8848/nacos/v1/cs/configs?export=true&dataId=&group=&tenant=prod" \
     -o config_export.zip

容灾演练

演练场景:

  1. 配置中心单节点故障
  2. 配置中心集群故障
  3. MySQL 主库故障
  4. 网络分区故障

演练步骤:

code
1. 模拟故障 (关闭节点/断网)
    ↓
2. 观察服务行为
    - 是否从本地缓存读取配置
    - 服务是否正常启动
    ↓
3. 恢复故障
    ↓
4. 观察恢复过程
    - 配置是否自动同步
    - 服务是否自动恢复

4.3 客户端容错

注册中心故障时的降级策略

本地缓存:

  • 服务消费者缓存服务实例列表
  • 注册中心不可用时,使用本地缓存继续调用
  • 定期重试连接注册中心

硬编码兜底:

yaml
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        # 注册中心故障时使用的兜底实例
        fallback:
          enabled: true
          instances:
            - host: 192.168.1.100
              port: 8080
            - host: 192.168.1.101
              port: 8080

配置中心故障时的降级策略

本地快照:

java
// Nacos 本地快照配置
@Configuration
public class NacosConfig {
    
    @Bean
    public NacosConfigProperties nacosConfigProperties() {
        NacosConfigProperties properties = new NacosConfigProperties();
        // 开启本地快照
        properties.setCacheEnabled(true);
        return properties;
    }
}

Failfast 策略:

yaml
spring:
  cloud:
    nacos:
      config:
        fail-fast: true  # 配置中心不可用时启动失败

五、实战场景与示例

5.1 场景一:订单服务调用用户服务

问题场景

如果订单服务把用户服务地址硬编码成固定 IP:

  • 用户服务扩容时,订单服务无感知
  • 容器重建后地址变化,调用立即失败
  • 多实例之间也无法做健康负载均衡

解决方案

服务提供者 (用户服务):

java
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
yaml
# application.yml
server:
  port: 8081

spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: prod
        cluster-name: BEIJING
        metadata:
          version: 1.0.0

服务消费者 (订单服务):

java
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
java
@Service
public class OrderService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    public UserDTO getUser(Long userId) {
        // 按服务名调用,无需知道具体实例地址
        return restTemplate.getForObject(
            "http://user-service/internal/users/" + userId, 
            UserDTO.class
        );
    }
}

Feign 声明式调用:

java
@FeignClient(name = "user-service")
public interface UserClient {
    
    @GetMapping("/internal/users/{id}")
    UserDTO queryById(@PathVariable("id") Long id);
}
java
@Service
public class OrderService {
    
    @Autowired
    private UserClient userClient;
    
    public UserDTO getUser(Long userId) {
        return userClient.queryById(userId);
    }
}

5.2 场景二:线上限流阈值动态调整

问题场景

大促期间,如果某个接口的限流阈值要从每秒 200 调到每秒 500:

  • 如果配置写死在本地文件里,就要改配置、发版、重启
  • 如果放在配置中心,并且该配置允许安全热更新,就可以快速调整并观察效果

解决方案

配置中心配置:

yaml
# user-service.yaml (Nacos)
rate:
  limit:
    user-query: 200
    order-create: 100

限流配置类:

java
@Configuration
@RefreshScope
public class RateLimitConfig {
    
    @Value("${rate.limit.user-query:200}")
    private int userQueryLimit;
    
    @Value("${rate.limit.order-create:100}")
    private int orderCreateLimit;
    
    @Bean
    @RefreshScope
    public RateLimiter userQueryLimiter() {
        return RateLimiter.create(userQueryLimit);
    }
    
    @Bean
    @RefreshScope
    public RateLimiter orderCreateLimiter() {
        return RateLimiter.create(orderCreateLimit);
    }
}

限流拦截器:

java
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
    
    @Autowired
    private RateLimiter userQueryLimiter;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler) throws Exception {
        if (!userQueryLimiter.tryAcquire()) {
            response.setStatus(429);
            response.getWriter().write("Rate limit exceeded");
            return false;
        }
        return true;
    }
}

动态调整流程:

code
1. 在 Nacos 控制台修改配置
    rate.limit.user-query: 500
    ↓
2. 配置推送到服务实例
    ↓
3. 触发配置刷新事件
    ↓
4. RateLimitConfig 重新创建
    ↓
5. 新的限流阈值生效
    ↓
6. 无需重启服务

5.3 场景三:多环境与灰度配置

多环境配置管理

命名空间隔离:

code
Nacos
├── dev (命名空间)
│   ├── user-service.yaml
│   └── order-service.yaml
├── test (命名空间)
│   ├── user-service.yaml
│   └── order-service.yaml
└── prod (命名空间)
    ├── user-service.yaml
    └── order-service.yaml

配置切换:

yaml
# bootstrap.yml
spring:
  profiles:
    active: dev  # 切换环境只需修改这里
  cloud:
    nacos:
      config:
        namespace: ${spring.profiles.active}

灰度配置

灰度实例标记:

yaml
# 灰度实例配置
spring:
  cloud:
    nacos:
      discovery:
        metadata:
          gray: true
          version: 2.0.0

灰度配置推送:

java
@Configuration
@ConditionalOnProperty(name = "gray.enabled", havingValue = "true")
@RefreshScope
public class GrayConfig {
    
    @Value("${gray.feature.enabled:false}")
    private boolean featureEnabled;
    
    @Bean
    public FeatureService featureService() {
        return new FeatureService(featureEnabled);
    }
}

灰度路由:

java
@Configuration
public class GrayLoadBalancerConfig {
    
    @Bean
    ReactorLoadBalancer<ServiceInstance> grayLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory factory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new GrayLoadBalancer(
            factory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name
        );
    }
}

public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        // 优先选择灰度实例
        ServiceInstance instance = selectGrayInstance(instances);
        return Mono.just(new DefaultResponse(instance));
    }
}

5.4 场景四:服务优雅上下线

优雅上线

延迟注册:

java
@Component
public class GracefulStartup implements ApplicationRunner {
    
    @Autowired
    private NacosRegistration registration;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 等待服务就绪
        Thread.sleep(10000);
        
        // 健康检查通过后再注册
        if (healthCheck()) {
            registration.start();
        }
    }
    
    private boolean healthCheck() {
        // 检查数据库、Redis 等依赖
        return true;
    }
}

预热:

java
@Component
public class ServiceWarmup implements ApplicationRunner {
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 预热缓存
        warmupCache();
        
        // 预热连接池
        warmupConnectionPool();
        
        // 预热 JVM
        warmupJVM();
    }
}

优雅下线

主动注销:

java
@Component
public class GracefulShutdown implements DisposableBean {
    
    @Autowired
    private NacosRegistration registration;
    
    @Override
    public void destroy() throws Exception {
        // 1. 从注册中心注销
        registration.stop();
        
        // 2. 等待正在处理的请求完成
        Thread.sleep(30000);
        
        // 3. 关闭服务
    }
}

K8s 优雅终止:

yaml
# deployment.yaml
spec:
  template:
    spec:
      containers:
      - name: user-service
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 30"]
      terminationGracePeriodSeconds: 60

六、常见问题与排查

6.1 服务发现问题排查

问题一:服务注册失败

现象:

  • 服务启动后,在 Nacos 控制台看不到该服务

排查步骤:

  1. 检查网络连通性
bash
# 测试 Nacos 连通性
curl http://nacos:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=10
  1. 检查配置是否正确
yaml
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 正确的地址
        namespace: 正确的命名空间
        enabled: true  # 是否启用服务发现
  1. 检查日志
bash
# 查看 Nacos 客户端日志
tail -f logs/nacos/nacos-discovery.log
  1. 检查权限
  • Nacos 是否开启了鉴权
  • 命名空间是否有权限

问题二:服务调用失败

现象:

  • 服务明明启动了,但调用时报错 "No instances available"

排查步骤:

  1. 检查服务是否注册成功
bash
# 查询服务实例列表
curl "http://nacos:8848/nacos/v1/ns/instance/list?serviceName=user-service"
  1. 检查实例是否健康
bash
# 查询实例详情
curl "http://nacos:8848/nacos/v1/ns/instance?serviceName=user-service&ip=192.168.1.100&port=8080"
  1. 检查客户端缓存
  • 重启消费者服务,刷新本地缓存
  • 检查消费者订阅的服务名是否正确
  1. 检查网络
bash
# 测试实例连通性
curl http://192.168.1.100:8080/health

问题三:服务下线后仍被调用

现象:

  • 服务已经下线,但仍有请求打到该实例

排查步骤:

  1. 检查实例是否已从注册中心移除
bash
# 查询实例列表
curl "http://nacos:8848/nacos/v1/ns/instance/list?serviceName=user-service"
  1. 检查消费者本地缓存
  • 消费者是否及时收到下线通知
  • 消费者本地缓存是否更新
  1. 检查心跳和超时配置
yaml
spring:
  cloud:
    nacos:
      discovery:
        heart-beat-timeout: 15000  # 心跳超时
        ip-delete-timeout: 30000   # IP 删除超时

6.2 配置中心问题排查

问题一:配置不生效

现象:

  • 在 Nacos 控制台修改了配置,但服务行为没有变化

排查步骤:

  1. 检查配置是否匹配
yaml
# Data ID 格式
${spring.application.name}-${spring.profiles.active}.${file-extension}

# 示例
user-service-dev.yaml
  1. 检查命名空间和分组
yaml
spring:
  cloud:
    nacos:
      config:
        namespace: 正确的命名空间
        group: 正确的分组
  1. 检查是否支持动态刷新
  • 配置类是否加了 @RefreshScope
  • 配置项是否支持动态刷新
  1. 检查配置优先级
  • 本地配置是否覆盖了远端配置
  • 命令行参数是否覆盖了配置中心配置

问题二:配置中心连接失败

现象:

  • 服务启动时报错,无法连接配置中心

排查步骤:

  1. 检查网络连通性
bash
# 测试 Nacos 连通性
curl http://nacos:8848/nacos/v1/console/health/readiness
  1. 检查配置是否正确
yaml
spring:
  cloud:
    nacos:
      config:
        server-addr: 正确的地址
        namespace: 正确的命名空间
  1. 检查是否开启了 Failfast
yaml
spring:
  cloud:
    nacos:
      config:
        fail-fast: false  # 关闭快速失败,使用本地快照
  1. 检查本地快照
  • 查看 ${user.home}/nacos/config 目录是否有快照
  • 快照文件是否存在且内容正确

6.3 高可用问题排查

问题一:注册中心集群故障

现象:

  • Nacos 集群某个节点宕机,服务发现异常

排查步骤:

  1. 检查集群状态
bash
# 查询集群节点状态
curl "http://nacos:8848/nacos/v1/ns/operator/servers"
  1. 检查 Raft 状态
bash
# 查询 Raft 状态
curl "http://nacos:8848/nacos/v1/ns/raft/state"
  1. 检查客户端配置
  • 客户端是否配置了多个 Nacos 地址
  • 负载均衡器是否正常工作

问题二:配置中心集群脑裂

现象:

  • 配置中心集群出现多个 Leader,数据不一致

排查步骤:

  1. 检查集群配置
properties
# cluster.conf
# 确保所有节点配置一致
192.168.1.1:8848
192.168.1.2:8848
192.168.1.3:8848
  1. 检查网络延迟
  • 节点之间网络延迟是否过高
  • 是否存在网络分区
  1. 重启集群
  • 停止所有节点
  • 清理数据目录
  • 依次启动节点

七、面试要点

7.1 服务发现核心问题

Q1: 服务发现解决的核心问题是什么?

答案:

实例地址动态变化后,调用方仍能按服务名稳定访问目标服务。

展开:

  • 解耦服务名与实例地址
  • 动态感知实例上下线
  • 支持负载均衡
  • 提高系统弹性和可扩展性

Q2: 注册中心为什么需要心跳和健康剔除?

答案:

因为"注册过"不等于"当前可用",异常实例必须及时从可用列表中移除。

展开:

  • 心跳机制确保实例在线状态
  • 健康剔除防止故障实例接收流量
  • 及时感知实例状态变化
  • 保证服务发现的准确性

Q3: AP 和 CP 架构的注册中心如何选择?

答案:

AP 架构 (Eureka, Nacos 默认):

  • 优先可用性,容忍短暂不一致
  • 适合对可用性要求高的场景
  • 网络分区时仍可提供服务发现

CP 架构 (Consul, Zookeeper):

  • 优先一致性,牺牲部分可用性
  • 适合对一致性要求高的场景
  • 网络分区时部分节点不可用

选择建议:

  • 服务发现场景通常选择 AP
  • 配置管理场景通常选择 CP
  • Nacos 可以在 AP 和 CP 之间切换

7.2 配置中心核心问题

Q4: 配置中心为什么不能只谈集中存储?

答案:

因为真正难的是变更生效、灰度、回滚、权限治理和配置优先级控制。

展开:

  • 变更生效机制 (推送/拉取/长轮询)
  • 配置版本管理和回滚
  • 灰度发布和分批生效
  • 权限控制和审计追踪
  • 配置优先级和覆盖规则

Q5: 哪些配置适合动态刷新?

答案:

适合动态刷新:

  • 阈值类配置 (限流阈值、超时时间)
  • 开关类配置 (Feature Toggle、灰度开关)
  • 部分业务参数 (规则参数、策略参数)

不适合动态刷新:

  • 数据源配置 (连接池参数)
  • 线程模型配置 (核心线程数)
  • 关键依赖拓扑 (注册中心地址、数据库地址)

原因:

  • 数据源配置热更新可能导致连接抖动
  • 线程模型热更新可能导致行为不一致
  • 关键依赖变更需要重启才能完全生效

Q6: 配置中心的高可用如何保证?

答案:

服务端:

  • 集群部署 (至少 3 节点)
  • 数据持久化到数据库 (主从集群)
  • 负载均衡分发请求
  • 跨机房容灾

客户端:

  • 本地快照缓存
  • Failfast/Failover 策略
  • 重试和退避机制
  • 监控告警

7.3 Nacos 相关问题

Q7: Nacos 的 AP 和 CP 模式如何切换?

答案:

切换方式:

properties
# AP 模式 (默认)
nacos.core.protocol.raft.data.enabled=false
nacos.core.protocol.distro.enabled=true

# CP 模式
nacos.core.protocol.raft.data.enabled=true
nacos.core.protocol.distro.enabled=false

使用场景:

  • AP 模式: 服务发现场景,优先可用性
  • CP 模式: 配置管理场景,优先一致性

Q8: Nacos 如何实现配置的灰度发布?

答案:

方案一: 命名空间隔离

  • 灰度实例使用独立的命名空间
  • 灰度实例读取灰度配置

方案二: 元数据标记

  • 灰度实例添加 metadata 标记
  • 客户端根据标记选择配置

方案三: Beta 发布

  • Nacos 控制台支持 Beta 发布
  • 先推送到部分 IP,观察后再全量

Q9: Nacos 的长轮询机制是如何实现的?

答案:

原理:

  1. 客户端发起长轮询请求 (超时时间 30 秒)
  2. 服务端挂起请求
  3. 配置变更时,立即返回响应
  4. 超时时间内无变更,返回空响应
  5. 客户端立即发起下一次长轮询

优势:

  • 实时性好,配置变更能快速感知
  • 减少无效查询,降低服务器压力
  • 相比纯推送,实现更简单

7.4 综合问题

Q10: 服务发现和负载均衡的关系是什么?

答案:

职责划分:

  • 服务发现: 负责"有哪些实例可用"
  • 负载均衡: 负责"这次该打到谁"

协作方式:

  1. 服务发现组件提供可用实例列表
  2. 负载均衡组件从列表中选择一个实例
  3. 请求发送到选中的实例

集成方式:

  • 客户端负载均衡: Ribbon、Spring Cloud LoadBalancer
  • 服务端负载均衡: Nginx、Spring Cloud Gateway

Q11: 如何实现服务的优雅上下线?

答案:

优雅上线:

  • 服务就绪后再注册 (延迟注册)
  • 预热缓存、连接池
  • 健康检查通过后接收流量

优雅下线:

  • 先从注册中心注销
  • 等待正在处理的请求完成
  • 关闭连接和资源

实现方式:

  • Spring Boot Actuator 的 health 端点
  • K8s 的 preStop hook
  • Nacos 的主动注销 API

Q12: 注册中心抖动时如何保证服务可用?

答案:

客户端降级策略:

  • 本地缓存实例列表
  • 注册中心不可用时,使用缓存继续调用
  • 定期重试连接注册中心

兜底机制:

  • 硬编码兜底实例
  • 多注册中心备份
  • 服务网格 Sidecar 代理

监控告警:

  • 注册中心连通性监控
  • 实例注册/注销监控
  • 客户端缓存更新监控

八、最佳实践总结

8.1 服务发现最佳实践

命名规范

  • 服务名使用小写字母和连字符: user-service
  • 避免使用特殊字符和中文
  • 统一命名风格,便于管理和识别

元数据管理

  • 合理使用元数据标记实例属性
  • 版本、机房、权重等关键信息要完整
  • 便于实现灰度发布和机房亲和

健康检查

  • 配置合理的心跳间隔和超时时间
  • 实现自定义健康检查端点
  • 区分存活探针和就绪探针

容错设计

  • 客户端启用本地缓存
  • 设置合理的重试和超时
  • 实现兜底策略

8.2 配置中心最佳实践

配置分类

  • 启动配置 vs 运行配置
  • 环境配置 vs 业务配置
  • 静态配置 vs 动态配置

配置规范

  • 统一配置文件命名规范
  • 明确配置优先级规则
  • 配置变更有审批流程

变更管理

  • 核心配置变更走发布流程
  • 动态配置支持灰度和回滚
  • 配置变更有审计日志

安全管理

  • 敏感配置加密存储
  • 配置访问权限控制
  • 定期审计配置变更记录

8.3 运维监控最佳实践

监控指标

注册中心监控:

  • 集群节点状态
  • 服务注册数量
  • 实例上下线频率
  • 心跳成功率

配置中心监控:

  • 配置变更次数
  • 配置推送成功率
  • 配置加载耗时
  • 客户端连接数

告警策略

  • 注册中心节点宕机告警
  • 服务实例大量下线告警
  • 配置变更失败告警
  • 配置中心不可用告警

日志管理

  • 服务注册/注销日志
  • 配置变更日志
  • 异常错误日志
  • 访问审计日志

九、常见误区

误区一:把注册中心当成静态地址簿

错误。注册中心不只是存一下地址,更重要的是健康检查、实例摘除、变更通知和可用实例维护。

正确理解:

  • 注册中心是动态的服务目录
  • 需要实时感知实例状态
  • 提供变更通知机制
  • 支持服务元数据管理

误区二:所有配置都适合热更新

错误。开关和阈值类配置通常适合动态刷新,但核心连接、线程模型和依赖拓扑类配置通常更适合通过发布生效。

正确理解:

  • 区分配置类型和变更风险
  • 核心配置变更走发布流程
  • 动态配置要有回滚机制
  • 变更前要在测试环境验证

误区三:配置中心只做存储,不做审计和回滚

错误。没有审计、权限和回滚能力的配置中心,很容易把一次配置变更演变成线上事故。

正确理解:

  • 配置变更有审计日志
  • 核心配置需要审批
  • 支持版本回滚
  • 权限管理和访问控制

误区四:一个服务同时存在本地配置和远端配置,但优先级没人说得清

错误。配置来源多不可怕,可怕的是优先级不清、变更路径不清、生效范围不清。

正确理解:

  • 明确配置优先级规则
  • 文档化配置来源
  • 统一配置管理入口
  • 定期清理冗余配置

误区五:过度依赖注册中心,连退化策略都没有

错误。注册中心抖动时,调用方仍应具备合理的本地缓存、重试边界和降级策略,不能让基础设施抖动直接放大成全链路故障。

正确理解:

  • 客户端启用本地缓存
  • 设置合理的重试策略
  • 准备兜底实例
  • 监控注册中心状态

十、小结

理解服务发现与配置中心,至少要掌握这些点:

  • 服务发现解决的是实例动态变化后的通信秩序问题
  • 注册中心真正关键的是实例续约、摘除、恢复和变更感知
  • 配置中心解决的不只是统一存储,更是配置治理和安全生效
  • 并不是所有配置都适合热更新
  • 把服务发现和配置中心用稳,靠的是治理体系,而不只是组件接入

核心技术栈:

  • 注册中心: Nacos、Eureka、Consul、Zookeeper
  • 配置中心: Nacos、Apollo、Spring Cloud Config
  • 负载均衡: Spring Cloud LoadBalancer、Ribbon
  • 服务调用: OpenFeign、RestTemplate、Dubbo

核心能力:

  • 服务注册与发现
  • 配置集中管理
  • 动态配置刷新
  • 高可用集群部署
  • 跨机房容灾

持续演进:

  • 服务网格 (Service Mesh) 的兴起
  • Kubernetes 原生服务发现
  • 多语言、多框架支持
  • 云原生架构演进

版本差异(Spring Cloud 旧版 → 2025.x)

特性旧版(本文编写时)当前(Spring Cloud 2025.x / Boot 3.5.x)
架构组件Eureka/Ribbon/Hystrix/Zuul(已停止维护)LoadBalancer/Gateway/Resilience4j/Nacos
版本对应Hoxton/2020.x + Boot 2.x + JDK 82025.x + Boot 3.5.x + JDK 17+
云原生初步支持全面云原生(K8s、虚拟线程、GraalVM 支持)
链路追踪Sleuth + ZipkinMicrometer Tracing + Zipkin/OTel

本文为通用微服务概念讲解,架构思想长期有效;落地时请采用 Spring Cloud 2025.x + Spring Boot 3.5.x 技术栈(JDK 17+)。