第 9 章:服务注册发现
学习目标
- 理解服务注册发现的核心问题
- 掌握 Eureka / Nacos / Consul 三大方案
- 学会用客户端负载均衡
- 避免服务上下线感知不及时的坑
一、为什么需要服务发现?
text
单体时代
[Order] ──→ [DB] // IP 写死
微服务时代
[Order] ──→ [Payment-v1:8080] // 怎么找 Payment?
──→ [Payment-v2:8081] // 扩了 5 个实例,IP 怎么动态知道?服务发现核心问题:
java
// ❌ 写死 IP
public class OrderService {
private final String PAYMENT_URL = "http://192.168.1.10:8080"; // 写死寄了
}
// ✅ 服务发现
@FeignClient(name = "payment-service") // 名字而非 IP
public interface PaymentClient {
@PostMapping("/api/payment")
PaymentResult pay(@RequestBody PaymentRequest req);
}二、CAP 定理与选择
| 组件 | 一致性 | 特点 |
|---|---|---|
| Eureka | AP | 优先可用,数据可能略滞后 |
| Consul | CP | 优先一致,选举期间不可用 |
| Nacos | AP/CP | 默认 AP,可配置 CP |
| Zookeeper | CP | 强一致,选举期间不可用 |
java
// Eureka 配置(AP)
eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka/
instance:
lease-renewal-interval-in-seconds: 10 // 10s 心跳
lease-expiration-duration-in-seconds: 30 // 30s 过期⚠️ 坑 1:Eureka 心跳间隔配成 60s,服务挂了要 1 分钟才感知,用户体验就是"卡了一会才好"。生产环境配 10s 心跳、30s 过期。
三、Eureka 注册中心
java
// 1. 服务注册
@SpringBootApplication
@EnableEurekaClient
public class PaymentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentServiceApplication.class, args);
}
}
// 2. 服务发现
@RestController
public class OrderController {
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/services")
public List<String> services() {
return discoveryClient.getServices();
}
}yaml
# application.yml
eureka:
client:
service-url:
defaultZone: http://eureka-server:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${server.port}四、Nacos 注册中心(推荐)
java
// 1. 引入依赖
// spring-cloud-starter-alibaba-nacos-discovery
// 2. 配置
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: nacos:8848
namespace: prod
group: DEFAULT_GROUPjava
// 3. 使用 Feign + Ribbon
@FeignClient(name = "payment-service")
public interface PaymentClient {
@PostMapping("/api/payment")
PaymentResult pay(@RequestBody PaymentRequest req);
}五、客户端负载均衡(LoadBalancer)
java
// Spring Cloud LoadBalancer(替代 Ribbon)
@Bean
public LoadBalancer loadBalancer() {
return new RandomLoadBalancer(); // 随机
// return new RoundRobinLoadBalancer(); // 轮询
}java
// 自定义负载均衡
public class IpHashLoadBalancer implements ReactorServiceInstanceLoadBalancer {
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
// 根据请求 IP 哈希选实例
String clientIp = getClientIp(request);
int hash = clientIp.hashCode();
List<ServiceInstance> instances = getInstances();
int index = Math.abs(hash) % instances.size();
return Mono.just(new DefaultResponse(instances.get(index)));
}
}⚠️ 坑 2:负载均衡把请求打到了关闭中的实例,出现 503。开启健康检查,标记不健康实例。
六、健康检查与剔除
yaml
# Nacos 健康检查
spring:
cloud:
nacos:
discovery:
heart-beat-interval: 5000 # 5s 心跳
heart-beat-timeout: 15000 # 15s 失联视为不健康
ip-delete-timeout: 30000 # 30s 剔除java
// 自定义健康检查接口
@RestController
public class HealthController {
@GetMapping("/health")
public HealthStatus health() {
boolean dbOk = checkDatabase();
boolean redisOk = checkRedis();
return dbOk && redisOk
? HealthStatus.up()
: HealthStatus.down();
}
}⚠️ 坑 3:健康检查只查
/actuator/health,但数据库挂了应用没检测到。深入检查关键依赖(数据库、Redis、MQ)。
七、注册中心高可用
yaml
# Eureka 集群:3 个节点互相注册
eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/,http://eureka3:8763/eureka/java
// Nacos 集群:3 个节点
spring:
cloud:
nacos:
discovery:
server-addr: nacos1:8848,nacos2:8848,nacos3:8848至少 3 节点,容忍 1 节点故障;5 节点容忍 2 节点故障。
本章小结
| 组件 | CAP | 特点 |
|---|---|---|
| Eureka | AP | 优先可用,简单 |
| Nacos | AP/CP | 双栈,国产生态最好 |
| Consul | CP | 多数据中心,服务网格 |
| ZK | CP | 老牌,大数据常用 |
| 关键点 | 经验值 |
|---|---|
| 心跳 | 5-10s |
| 过期 | 15-30s |
| 健康检查 | 关键依赖 |
| 集群 | 至少 3 节点 |
动手练习
- Eureka 部署:用 Docker Compose 起 3 个 Eureka 节点 + 2 个服务
- Nacos 接入:把上面的 payment-service 改成 Nacos 注册,观察实例列表
- 负载均衡:写一个
IpHashLoadBalancer,测试同一 IP 落到同一实例 - 健康检查:加一个
/health接口,故意关闭 Redis,观察实例被标记为不健康
下一章:第 10 章:配置中心 →