Skip to content
第 9 章 架构 ⏱ 12 分钟阅读

第 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 定理与选择 ​

组件一致性特点
EurekaAP优先可用,数据可能略滞后
ConsulCP优先一致,选举期间不可用
NacosAP/CP默认 AP,可配置 CP
ZookeeperCP强一致,选举期间不可用
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_GROUP
java
// 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特点
EurekaAP优先可用,简单
NacosAP/CP双栈,国产生态最好
ConsulCP多数据中心,服务网格
ZKCP老牌,大数据常用
关键点经验值
心跳5-10s
过期15-30s
健康检查关键依赖
集群至少 3 节点

动手练习 ​

  1. Eureka 部署:用 Docker Compose 起 3 个 Eureka 节点 + 2 个服务
  2. Nacos 接入:把上面的 payment-service 改成 Nacos 注册,观察实例列表
  3. 负载均衡:写一个 IpHashLoadBalancer,测试同一 IP 落到同一实例
  4. 健康检查:加一个 /health 接口,故意关闭 Redis,观察实例被标记为不健康

下一章:第 10 章:配置中心 →

本站基于 VitePress 构建 · 由 StackHub 团队维护