微服务一旦拆开,系统面对的第一个现实问题通常不是"代码怎么写",而是:
单体时代可以把地址和参数直接写进本地配置文件,但到了微服务场景:
所以服务发现和配置中心,本质上是在解决微服务运行过程中的"基本秩序"问题。
在单体或很早期的分布式系统里,调用方经常直接配置目标地址,例如:
http://10.0.0.8:8080http://user-service-prod.example.com这种方式在服务实例很少、变化不频繁时还能工作,但在微服务场景下会快速暴露问题:
所以调用方真正需要的不是"某个实例地址",而是:
至于这个服务当前有哪些可用实例、该打到哪一个实例,应由注册发现机制和负载均衡机制来决定。
服务发现的目标,是让调用方只关心逻辑服务名,而不用手写实例地址。
核心价值:
一个典型的服务发现流程包含以下步骤:
服务提供者 注册中心 服务消费者
| | |
|--1. 服务注册------------->| |
| (服务名,IP,端口) | |
| | |
|--2. 心跳续约------------->| |
| (定时发送) | |
| | |
| |<--3. 订阅服务-------------|
| | (服务名) |
| | |
| |--4. 返回实例列表--------->|
| | (IP列表) |
| | |
| |--5. 实例变更推送--------->|
| (实例下线) | (推送新列表) |
| | |核心流程:
工作原理:
服务消费者 注册中心 服务提供者
| | |
|--查询实例列表------------>| |
|<--返回所有实例-------------| |
| | |
|--选择实例(负载均衡)-------| |
| | |
|--------------------------直接调用--------------------->|
| | |特点:
优点:
缺点:
代表实现:
工作原理:
服务消费者 负载均衡器/网关 注册中心 服务提供者
| | | |
|--调用服务名-------------->| | |
| |--查询实例列表------------>| |
| |<--返回实例列表-------------| |
| |--负载均衡选择实例---------| |
| |--------------------------直接调用------->|
|<--返回响应----------------| | |特点:
优点:
缺点:
代表实现:
在实际生产环境中,经常会结合两种模式:
实例启动后会把自己的信息上报到注册中心,常见内容包括:
基础信息:
元数据 (Metadata):
注册方式:
// Spring Cloud Nacos 服务注册示例
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}# 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"注册过一次"不代表"当前还活着"。因此实例通常需要定期上报心跳,告诉注册中心:
心跳机制设计:
| 参数 | 说明 | 典型值 |
|---|---|---|
| 心跳间隔 | 实例发送心跳的时间间隔 | 5-30秒 |
| 超时时间 | 注册中心多久没收到心跳认为实例失效 | 15-90秒 |
| 过期时间 | 实例被标记为不健康后的保留时间 | 90-180秒 |
心跳超时的影响:
典型配置:
spring:
cloud:
nacos:
discovery:
heart-beat-interval: 5000 # 心跳间隔 5秒
heart-beat-timeout: 15000 # 心跳超时 15秒
ip-delete-timeout: 30000 # IP删除超时 30秒除了心跳,注册中心还可以主动对实例进行健康检查:
健康检查类型:
应用层健康检查
平台层健康检查
健康检查端点示例:
@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):
spring:
cloud:
nacos:
discovery:
health-check-enabled: true
health-check-interval: 10000实例摘除的触发条件:
实例摘除流程:
实例恢复流程:
调用方通常不会每次调用都去查注册中心,而是:
订阅模式:
推送模式 (Push)
拉取模式 (Pull)
长轮询模式 (Long Polling)
本地缓存策略:
消费者启动
↓
订阅服务实例列表
↓
缓存到本地内存
↓
┌──────┐
│ 定时更新 ←─────┐
└──────┘ │
↓ │
变更通知 ─────────┘
↓
更新本地缓存
↓
触发负载均衡器刷新缓存带来的问题:
这也是很多"服务明明好了但还调不到""服务明明挂了却还在被调用"的根源。
服务发现通常和负载均衡一起工作。调用方按服务名发起请求后,实际还要解决两个问题:
调用链路:
user-service从架构职责上看:
负载均衡策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 轮询 (Round Robin) | 按顺序依次选择实例 | 实例性能相近 |
| 随机 (Random) | 随机选择实例 | 实例性能相近 |
| 加权轮询 (Weighted Round Robin) | 按权重分配流量 | 实例性能差异大 |
| 最少连接 (Least Connections) | 选择当前连接最少的实例 | 长连接场景 |
| 一致性哈希 (Consistent Hash) | 相同请求参数打到相同实例 | 有状态服务 |
| 地域感知 (Zone Aware) | 优先选择同可用区实例 | 多机房部署 |
Spring Cloud LoadBalancer 示例:
@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
);
}
}spring:
cloud:
loadbalancer:
# 仅迁移期(类路径上同时存在 Ribbon)需要,当前版本 Ribbon 已移除,无需此配置
ribbon:
enabled: false
configurations: random # 使用随机负载均衡目前主流的服务注册中心包括:
| 注册中心 | 开发语言 | 一致性协议 | 健康检查 | 配置中心 | 社区活跃度 |
|---|---|---|---|---|---|
| Nacos | Java | AP/CP 可选 | 客户端心跳 + 服务端检查 | √ 支持 | 高 |
| Eureka | Java | AP | 客户端心跳 | × 不支持 | 低 (维护模式) |
| Consul | Go | CP | 服务端检查 | √ 支持 | 中 |
| Zookeeper | Java | CP | 客户端心跳 | × 不支持 | 中 |
在分布式系统中,CAP 理论指出:
P 是必须的,因此只能在 C 和 A 之间权衡:
代表: Eureka, Nacos (默认)
特点:
优点:
缺点:
适用场景:
代表: Zookeeper, Consul, Nacos (可切换)
特点:
优点:
缺点:
适用场景:
Nacos (Dynamic Naming and Configuration Service) 是阿里巴巴开源的服务发现和配置管理平台。
核心特性:
优势:
┌─────────────────────────────────────────────────────────┐
│ Nacos Console │
│ (管理控制台) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Nacos Server │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Open API │ │ Config │ │ Naming │ │
│ │ (HTTP API) │ │ Service │ │ Service │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Consistency Protocol │ │
│ │ (Raft / Distro) │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Storage Layer │
│ (MySQL / Derby Embedded) │
└─────────────────────────────────────────────────────────┘核心组件:
命名空间 (Namespace):
分组 (Group):
服务 (Service):
${group_name}@@${service_name}集群 (Cluster):
实例 (Instance):
数据模型层级:
Namespace (命名空间)
└── Group (分组)
└── Service (服务)
└── Cluster (集群)
└── Instance (实例)部署架构:
┌─────────────┐
│ Nginx │
│ (负载均衡) │
└─────────────┘
↓
┌─────────────────┼─────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Nacos │ │ Nacos │ │ Nacos │
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ (Leader) │ │ (Follower) │ │ (Follower) │
└─────────────┘ └─────────────┘ └─────────────┘
└─────────────────┼─────────────────┘
↓
┌─────────────┐
│ MySQL │
│ (主从集群) │
└─────────────┘集群配置 (cluster.conf):
# cluster.conf
192.168.1.1:8848
192.168.1.2:8848
192.168.1.3:8848application.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:8848Nacos 并不通过服务端开关整体切换 AP/CP,而是按实例注册方式区分:
注册持久实例的示例:
spring:
cloud:
nacos:
discovery:
ephemeral: false # 注册为持久实例(默认为 true,临时实例)依赖:
<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):
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: trueEureka 是 Netflix 开源的服务发现组件,Spring Cloud Netflix 的核心组件之一。
核心组件:
架构特点:
┌─────────────────────────────────────────────────────────┐
│ Application Service │
│ (服务提供者) │
└─────────────────────────────────────────────────────────┘
↓ Register/Renew/Cancel ↑ Query
┌─────────────────────────────────────────────────────────┐
│ Eureka Server │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Registry │ │ Peer │ │ Service │ │
│ │ (注册表) │ │ Replication │ │ Status │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
↑ Query ↓ Replicate
┌─────────────────────────────────────────────────────────┐
│ Application Client │
│ (服务消费者) │
└─────────────────────────────────────────────────────────┘触发条件:
自我保护行为:
配置:
eureka:
server:
enable-self-preservation: true # 开启自我保护
renewal-percent-threshold: 0.85 # 续约比例阈值
eviction-interval-timer-in-ms: 60000 # 剔除任务间隔维护状态:
架构局限:
Consul 是 HashiCorp 开源的服务网格解决方案,提供服务发现、配置管理、健康检查等功能。
核心特性:
架构特点:
┌─────────────────────────────────────────────────────────┐
│ Consul Server │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ HTTP API │ │ DNS │ │ Raft │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
↓ Register ↑ Query ↓ RPC
┌─────────────────────────────────────────────────────────┐
│ Consul Agent │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Service │ │ Health │ │ Proxy │ │
│ │ (服务) │ │ Check │ │ (代理) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘服务定义文件:
{
"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 集成:
spring:
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: user-service
health-check-path: /health
health-check-interval: 10sZookeeper 是 Apache 顶级项目,最初用于 Hadoop 生态,后来被广泛用于服务发现、配置管理、分布式锁等场景。
核心特性:
架构特点:
服务注册:
/services
/user-service
/instance-00000001 (临时节点)
数据: {"host":"192.168.1.100","port":8080}
/instance-00000002 (临时节点)
数据: {"host":"192.168.1.101","port":8080}服务发现:
/services/user-service 目录Spring Cloud Zookeeper:
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| 维度 | 权重 | 说明 |
|---|---|---|
| 社区活跃度 | 高 | 社区活跃意味着更好的支持和生态 |
| 功能完整性 | 高 | 是否同时支持服务发现和配置管理 |
| 性能 | 中 | 高并发场景下的性能表现 |
| 易用性 | 中 | 文档、API、控制台的易用程度 |
| 生态集成 | 高 | 与 Spring Cloud、Dubbo 的集成程度 |
| 高可用 | 高 | 集群部署和容灾能力 |
| 一致性模型 | 中 | AP 还是 CP,是否支持切换 |
Spring Cloud 微服务架构:
Dubbo 微服务架构:
Kubernetes 环境:
多语言异构系统:
对一致性要求极高的场景:
微服务拆分后,每个服务通常都有大量运行时配置,例如:
基础配置:
业务配置:
安全配置:
如果这些配置分散在各台机器、本地文件或多个仓库里,常见问题会很快出现:
配置中心的目标,就是把配置从服务本地抽离出来,统一管理、统一变更、统一追踪。
核心价值:
配置存储结构:
Namespace (命名空间)
└── Group (分组)
└── Data ID (配置ID)
├── Content (配置内容)
├── Version (版本号)
├── Create Time (创建时间)
├── Modify Time (修改时间)
└── History (历史版本)版本管理能力:
推送模式 (Push):
配置中心 ──推送变更──> 服务实例拉取模式 (Pull):
服务实例 ──定时拉取──> 配置中心长轮询模式 (Long Polling):
服务实例 ──发起长轮询──> 配置中心
↓ (等待30秒)
配置变更 ──立即返回──> 服务实例配置刷新流程:
配置变更
↓
推送到服务实例
↓
触发配置刷新事件
↓
Spring Cloud Context 刷新
↓
重新绑定配置到 Bean
↓
业务逻辑生效@RefreshScope 注解:
@RestController
@RefreshScope // 支持配置动态刷新
public class ConfigController {
@Value("${app.config.threshold:100}")
private int threshold;
@GetMapping("/threshold")
public int getThreshold() {
return threshold;
}
}刷新范围控制:
@Configuration
public class RefreshConfig {
@Bean
@RefreshScope
public SomeService someService() {
return new SomeService();
}
}| 配置类型 | 变更频率 | 是否支持热更新 | 管理方式 |
|---|---|---|---|
| 启动配置 | 低 | × 不支持 | 本地配置文件 |
| 环境配置 | 中 | × 不支持 | 配置中心 |
| 业务参数 | 高 | √ 支持 | 配置中心 |
| 开关配置 | 高 | √ 支持 | 配置中心 |
Spring Cloud 配置优先级 (从高到低):
优先级问题:
标准流程:
变更申请 ─> 审批 ─> 测试环境验证 ─> 预发环境验证 ─> 灰度发布 ─> 全量发布 ─> 监控观察配置变更规范:
┌─────────────┐
│ 客户端 │
└─────────────┘
↓
┌─────────────────┼─────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Config │ │ Config │ │ Config │
│ Server 1 │ │ Server 2 │ │ Server 3 │
└─────────────┘ └─────────────┘ └─────────────┘
└─────────────────┼─────────────────┘
↓
┌─────────────┐
│ MySQL │
│ (主从集群) │
└─────────────┘本地缓存:
配置快照:
Nacos 客户端在成功拉取配置后,会自动把内容写入本地快照目录 ${user.home}/nacos/config;配置中心不可用时,重启的服务实例会优先加载快照,保证不会因配置中心故障而无法启动。因此无需手动开启"快照开关",只需保证应用运行账号对快照目录有写权限。
Failfast vs Failover:
┌─────────────────────────────────────────────────────────┐
│ Nacos Config │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Config │ │ Config │ │ Config │ │
│ │ Open API │ │ Service │ │ History │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
↓ Pull/Push ↑ Long Polling
┌─────────────────────────────────────────────────────────┐
│ Spring Cloud App │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Nacos │ │ Property │ │ Refresh │ │
│ │ Config │ │ Source │ │ Scope │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘命名空间 (Namespace):
分组 (Group):
Data ID:
${prefix}-${spring.profiles.active}.${file-extension}user-service-dev.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配置文件命名规范:
user-service.yaml # 基础配置
user-service-dev.yaml # 开发环境配置
user-service-test.yaml # 测试环境配置
user-service-prod.yaml # 生产环境配置
common.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扩展配置:
spring:
cloud:
nacos:
config:
extension-configs:
- data-id: datasource.yaml
group: INFRA_GROUP
refresh: false
- data-id: mybatis.yaml
group: INFRA_GROUP
refresh: false配置内容 (user-service.yaml):
app:
config:
threshold: 100
enabled: true
rules:
- rule1
- rule2配置类:
@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
}配置变更监听:
@Component
public class ConfigChangeListener {
@NacosConfigListener(dataId = "user-service.yaml", groupId = "USER_GROUP")
public void onConfigChange(String newConfig) {
log.info("配置变更: {}", newConfig);
// 处理配置变更逻辑
}
}标准三节点集群:
┌─────────────┐
│ Nginx │
│ (负载均衡) │
└─────────────┘
↓
┌─────────────────┼─────────────────┐
↓ ↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Nacos │ │ Nacos │ │ Nacos │
│ Node 1 │◄──┤ Node 2 ├──►│ Node 3 │
│ (Leader) │ │(Follower) │ │(Follower) │
└───────────┘ └───────────┘ └───────────┘
└─────────────────┼─────────────────┘
↓
┌─────────────┐
│ MySQL │
│ (主从) │
└─────────────┘部署要点:
故障检测:
故障恢复:
同城双机房:
机房A 机房B
┌─────────────────┐ ┌─────────────────┐
│ Nacos Cluster │◄────────►│ Nacos Cluster │
│ (2节点) │ 同步 │ (2节点) │
└─────────────────┘ └─────────────────┘
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ MySQL 主 │◄────────►│ MySQL 从 │
└─────────────────┘ 主从 └─────────────────┘异地多活:
北京机房 上海机房
┌─────────────────┐ ┌─────────────────┐
│ Nacos Cluster │ │ Nacos Cluster │
│ (独立集群) │ │ (独立集群) │
└─────────────────┘ └─────────────────┘
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ 本地服务实例 │ │ 本地服务实例 │
│ (优先本地调用) │ │ (优先本地调用) │
└─────────────────┘ └─────────────────┘MySQL 数据备份:
# 全量备份
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配置导出:
# Nacos 配置导出
curl -X GET "http://nacos:8848/nacos/v1/cs/configs?export=true&dataId=&group=&tenant=prod" \
-o config_export.zip演练场景:
演练步骤:
1. 模拟故障 (关闭节点/断网)
↓
2. 观察服务行为
- 是否从本地缓存读取配置
- 服务是否正常启动
↓
3. 恢复故障
↓
4. 观察恢复过程
- 配置是否自动同步
- 服务是否自动恢复本地缓存:
静态实例兜底 (SimpleDiscoveryClient):
# 注册中心不可用时的兜底方案之一:直接声明静态实例列表
# Spring Cloud Commons 的 SimpleDiscoveryClient 会按服务名返回这些实例
spring:
cloud:
nacos:
discovery:
enabled: false # 关闭常规注册发现,调用方退化为静态寻址
discovery:
client:
simple:
instances:
user-service:
- uri: http://192.168.1.100:8080
- uri: http://192.168.1.101:8080本地快照:
Nacos 客户端会自动维护本地快照目录 ${user.home}/nacos/config,配置中心不可用时,服务可基于快照继续读取最近一次的配置内容。NacosConfigProperties 中并无"开启快照"的开关,该行为由客户端内置,只需保证快照目录可写。
Failfast 策略:
# 注意:spring.cloud.nacos.config 下并无 fail-fast 属性
# fail-fast 是 Spring Cloud Config Client 提供的标准开关,勿混淆
spring:
cloud:
config:
fail-fast: true # 配置中心不可用时启动失败(Spring Cloud Config Client)Nacos 客户端启动时按内置重试策略拉取配置,拉取失败会重试,并在无法获取远端配置时回退到本地快照,因此通常无需显式 fail-fast 开关。
如果订单服务把用户服务地址硬编码成固定 IP:
服务提供者 (用户服务):
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}# 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服务消费者 (订单服务):
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}@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 声明式调用:
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/internal/users/{id}")
UserDTO queryById(@PathVariable("id") Long id);
}@Service
public class OrderService {
@Autowired
private UserClient userClient;
public UserDTO getUser(Long userId) {
return userClient.queryById(userId);
}
}大促期间,如果某个接口的限流阈值要从每秒 200 调到每秒 500:
配置中心配置:
# user-service.yaml (Nacos)
rate:
limit:
user-query: 200
order-create: 100限流配置类:
@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);
}
}限流拦截器:
@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;
}
}动态调整流程:
1. 在 Nacos 控制台修改配置
rate.limit.user-query: 500
↓
2. 配置推送到服务实例
↓
3. 触发配置刷新事件
↓
4. RateLimitConfig 重新创建
↓
5. 新的限流阈值生效
↓
6. 无需重启服务命名空间隔离:
Nacos
├── dev (命名空间)
│ ├── user-service.yaml
│ └── order-service.yaml
├── test (命名空间)
│ ├── user-service.yaml
│ └── order-service.yaml
└── prod (命名空间)
├── user-service.yaml
└── order-service.yaml配置切换:
# bootstrap.yml
spring:
profiles:
active: dev # 切换环境只需修改这里
cloud:
nacos:
config:
namespace: ${spring.profiles.active}灰度实例标记:
# 灰度实例配置
spring:
cloud:
nacos:
discovery:
metadata:
gray: true
version: 2.0.0灰度配置推送:
@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);
}
}灰度路由:
@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 {
private final ServiceInstanceListSupplier supplier;
public GrayLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) {
this.supplier = supplier;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
// 示意:获取实例列表后,优先选择 metadata 中 gray=true 的灰度实例
return supplier.get().next()
.map(instances -> new DefaultResponse(selectGrayInstance(instances)));
}
private ServiceInstance selectGrayInstance(List<ServiceInstance> instances) {
// 灰度筛选逻辑:按 metadata.gray 标记筛选,未命中则退回普通实例
return instances.stream()
.filter(i -> "true".equals(i.getMetadata().get("gray")))
.findFirst()
.orElse(instances.get(0));
}
}延迟注册:
@Component
public class GracefulStartup implements ApplicationRunner {
@Autowired
private NacosAutoServiceRegistration registration;
@Override
public void run(ApplicationArguments args) throws Exception {
// 等待服务就绪
Thread.sleep(10000);
// 健康检查通过后再注册
if (healthCheck()) {
registration.start();
}
}
private boolean healthCheck() {
// 检查数据库、Redis 等依赖
return true;
}
}预热:
@Component
public class ServiceWarmup implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 预热缓存
warmupCache();
// 预热连接池
warmupConnectionPool();
// 预热 JVM
warmupJVM();
}
}主动注销:
@Component
public class GracefulShutdown implements DisposableBean {
@Autowired
private NacosAutoServiceRegistration registration;
@Override
public void destroy() throws Exception {
// 1. 从注册中心注销
registration.stop();
// 2. 等待正在处理的请求完成
Thread.sleep(30000);
// 3. 关闭服务
}
}K8s 优雅终止:
# deployment.yaml
spec:
template:
spec:
containers:
- name: user-service
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 30"]
terminationGracePeriodSeconds: 60现象:
排查步骤:
# 测试 Nacos 连通性
curl http://nacos:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=10spring:
cloud:
nacos:
discovery:
server-addr: 正确的地址
namespace: 正确的命名空间
enabled: true # 是否启用服务发现# 查看 Nacos 客户端日志
tail -f logs/nacos/nacos-discovery.log现象:
排查步骤:
# 查询服务实例列表
curl "http://nacos:8848/nacos/v1/ns/instance/list?serviceName=user-service"# 查询实例详情
curl "http://nacos:8848/nacos/v1/ns/instance?serviceName=user-service&ip=192.168.1.100&port=8080"# 测试实例连通性
curl http://192.168.1.100:8080/health现象:
排查步骤:
# 查询实例列表
curl "http://nacos:8848/nacos/v1/ns/instance/list?serviceName=user-service"spring:
cloud:
nacos:
discovery:
heart-beat-timeout: 15000 # 心跳超时
ip-delete-timeout: 30000 # IP 删除超时现象:
排查步骤:
# Data ID 格式
${spring.application.name}-${spring.profiles.active}.${file-extension}
# 示例
user-service-dev.yamlspring:
cloud:
nacos:
config:
namespace: 正确的命名空间
group: 正确的分组@RefreshScope现象:
排查步骤:
# 测试 Nacos 连通性
curl http://nacos:8848/nacos/v1/console/health/readinessspring:
cloud:
nacos:
config:
server-addr: 正确的地址
namespace: 正确的命名空间# Nacos 客户端会优先加载 ${user.home}/nacos/config 下的本地快照
ls -la ${user.home}/nacos/config${user.home}/nacos/config 目录是否有快照现象:
排查步骤:
# 查询集群节点状态
curl "http://nacos:8848/nacos/v1/ns/operator/servers"# 查询 Raft 状态
curl "http://nacos:8848/nacos/v1/ns/raft/state"现象:
排查步骤:
# cluster.conf
# 确保所有节点配置一致
192.168.1.1:8848
192.168.1.2:8848
192.168.1.3:8848答案:
实例地址动态变化后,调用方仍能按服务名稳定访问目标服务。
展开:
答案:
因为"注册过"不等于"当前可用",异常实例必须及时从可用列表中移除。
展开:
答案:
AP 架构 (Eureka, Nacos 默认):
CP 架构 (Consul, Zookeeper):
选择建议:
答案:
因为真正难的是变更生效、灰度、回滚、权限治理和配置优先级控制。
展开:
答案:
适合动态刷新:
不适合动态刷新:
原因:
答案:
服务端:
客户端:
答案:
Nacos 不提供整体开关把集群切到 AP 或 CP,而是按实例注册方式区分:
临时实例 (ephemeral=true,默认):
持久实例 (ephemeral=false):
配置管理: Nacos Config 固定使用 Raft 保证配置一致性,与实例类型无关。
答案:
方案一: 命名空间隔离
方案二: 元数据标记
方案三: Beta 发布
答案:
原理:
优势:
答案:
职责划分:
协作方式:
集成方式:
答案:
优雅上线:
优雅下线:
实现方式:
答案:
客户端降级策略:
兜底机制:
监控告警:
user-service注册中心监控:
配置中心监控:
错误。注册中心不只是存一下地址,更重要的是健康检查、实例摘除、变更通知和可用实例维护。
正确理解:
错误。开关和阈值类配置通常适合动态刷新,但核心连接、线程模型和依赖拓扑类配置通常更适合通过发布生效。
正确理解:
错误。没有审计、权限和回滚能力的配置中心,很容易把一次配置变更演变成线上事故。
正确理解:
错误。配置来源多不可怕,可怕的是优先级不清、变更路径不清、生效范围不清。
正确理解:
错误。注册中心抖动时,调用方仍应具备合理的本地缓存、重试边界和降级策略,不能让基础设施抖动直接放大成全链路故障。
正确理解:
理解服务发现与配置中心,至少要掌握这些点:
核心技术栈:
核心能力:
持续演进:
| 特性 | 旧版(本文编写时) | 当前(Spring Cloud 2025.x / Boot 3.5.x) |
|---|---|---|
| 架构组件 | Eureka/Ribbon/Hystrix/Zuul(已停止维护) | LoadBalancer/Gateway/Resilience4j/Nacos |
| 版本对应 | Hoxton/2020.x + Boot 2.x + JDK 8 | 2025.x + Boot 3.5.x + JDK 17+ |
| 云原生 | 初步支持 | 全面云原生(K8s、虚拟线程、GraalVM 支持) |
| 链路追踪 | Sleuth + Zipkin | Micrometer Tracing + Zipkin/OTel |
本文为通用微服务概念讲解,架构思想长期有效;落地时请采用 Spring Cloud 2025.x + Spring Boot 3.5.x 技术栈(JDK 17+)。