This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/MS/02-服务治理/04-服务发现.md
T
2026-05-17 22:00:24 +08:00

7.8 KiB
Raw Blame History

tags, create time
tags create time
microservice
service-discovery
consul
nacos
etcd
kubernetes
2026-05-05 00:00

服务发现

概述

服务实例的动态变化(扩容、缩容、故障重启)让硬编码地址成为不可能。服务要回答的核心问题是:我怎么找到你?

[!question] 引出问题 假设你的订单服务有 3 个实例,运行在 10.0.1.1:8080、10.0.1.2:8080、10.0.1.3:8080。当其中一个实例扩容下线时,调用方如何得知最新的地址列表?手动改配置滚动重启?还是……有更好的办法?

两种发现模式

graph TB
    subgraph Client["客户端发现模式"]
        A[Service A] -->|1.查询注册中心| R1[(Registry)]
        R1 -->|2.返回实例列表| A
        A -->|3.自选实例直连| B[Service B]
        noteA["客户端内置负载均衡逻辑"]
        B -.-> noteA
    end

    subgraph Server["服务端发现模式"]
        C[Service C] -->|请求| P[LoadBalancer Proxy]
        P -->|查询注册中心| R2[(Registry)]
        R2 -->|返回实例| P
        P -->|转发| D[Service D]
        noteC["客户端无感知,透明代理"]
        D -.-> noteC
    end

对比:

维度 客户端发现 服务端发现
典型实现 Eureka、Consul、Nacos Nginx、Envoy、K8s Service
耦合度 客户端嵌入注册逻辑 透明代理,客户端无感知
性能 直连调用,延迟最低 多一跳代理开销
运维复杂度 每门语言需独立 SDK 统一部署代理,技术无关
灵活性 客户端可自定义路由策略 受限于代理能力

客户端发现的优劣势分析

优势:

  • 直连调用,少了一跳网络开销
  • 客户端可以根据本地缓存做智能选择(如就近节点优先)
  • 框架成熟,Eureka / Consul 社区案例丰富

劣势:

  • 每个服务实现中都需要集成 SDK,跨语言迁移成本高
  • 客户端需要处理实例列表的缓存与刷新逻辑
  • 新增服务时需要修改调用方的代码或配置

服务端发现的优劣势分析

优势:

  • 完全解耦——调用方只需知道 Proxy 地址
  • 统一治理入口,可以在代理层加限流、熔断等逻辑
  • 适合多语言团队,无需各自维护 SDK

劣势:

  • 额外一跳增加延迟(通常 <1ms,高吞吐场景累积明显)
  • Proxy 本身成为单点瓶颈,必须做横向扩展
  • 调测复杂度上升——出了问题要看两层日志

主流注册中心选型

Nacos(推荐国内团队首选)

阿里开源,支持 AP(临时实例)+ CP(持久实例)双模型,同时提供配置管理功能。

// Nacos 服务注册与实例拉取
import (
    "github.com/nacos-group/nacos-sdk-go/v2/clients"
    "github.com/nacos-group/nacos-sdk-go/v2/common/constant"
    "github.com/nacos-group/nacos-sdk-go/v2/vo"
)

// 1. 创建服务端配置(连接哪个 Nacos)
sc := constant.ServerConfig{
    IpAddr: "127.0.0.1",
    Port:   8848,
}

上面仅展示了 serverConfig,实际创建时还需传入 ClientConfig(命名空间、超时等)。为简洁起见此处省略。

// 2. 创建 NamingClient
client, _ := clients.NewNamingClient(
    map[string]any{"serverConfigs": []constant.ServerConfig{sc}},
)

// 3. 注册服务实例(默认临时实例,走心跳保活)
_ = client.RegisterInstance(vo.RegisterInstanceParam{
    Ip:          "10.0.0.1",
    Port:        8080,
    ServiceName: "order-service",
    ClusterName: "DEFAULT",
})

// 4. 拉取某服务的可用实例列表(同步快照,含健康过滤)
instances, _, _ := client.SelectInstances(vo.SelectInstanceParam{
    ServiceName: "order-service",
    HealthyOnly: true, // 只返回健康实例
})
for _, inst := range instances {
    // inst.Ip: inst.Port — 拿到后可直接发起 HTTP / gRPC 调用
}

Consul

HashiCorp 出品,基于 Raft 共识协议(CP),天然支持健康检查和 DNS 接口。

核心特点:

  • 内建 KV 存储,可作为轻量配置中心
  • 支持 multi-datacenter 部署
  • DNS 接口:order-service.service.consul 可直接解析 IP

与 Nacos 选型对比:

维度 Consul Nacos
一致性模型 CP(Raft) AP + CP 可切换
部署复杂度 需独立部署 Agent(Sidecar 模式) Go SDK 内置,无额外代理
生态集成 Terraform / Vault 深度绑定 Spring Cloud / Dubbo 原生支持
适用场景 多云 / 混合云、基础设施层 Java 技术栈、业务服务层

[!tip] Consul Sidecar 模式 Consul Agent 通常以 DaemonSet 方式跑在每台节点上,应用通过 localhost 向本地 Agent 注册。这种 Sidecar 模式天然适配服务端发现架构——应用把 Consul Agent 当作"本地注册中心"即可。

etcd + K8s Service

纯容器环境下最自然的选择。etcd 存数据,K8s 提供完整的发现和负载均衡体系。

# K8s Service — 服务端发现模式的代表
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP

调用方只需 http://order-service:80,K8s 通过 iptables/IPVS 自动实现负载均衡。

Eureka

Netflix 开源,AP 模型。注意:Eureka 2.x 已开源关闭,不建议新项目使用。已有系统在迁移到 Nacos 或 Consul 之前可以继续用。

关键机制

服务注册流程

sequenceDiagram
    participant S as 服务实例
    participant R as 注册中心
    participant H as 健康检查器

    S->>R: 1. Register(instance)
    R-->>S: 2. ACK
    Note over S,R: 实例写入内存 / etcd
    
    loop 定时心跳
        S->>R: 3. Heartbeat (每 5s)
        R-->>S: 4. ACK
    end
    
    R->>H: 5. 超过心跳间隔未收到 → 标记不健康
    H->>R: 6. 摘除实例

临时实例 vs 持久实例

类型 宕机检测方式 数据存储 适用场景
临时实例 (ephemeral) 心跳超时自动摘除 内存 Web 服务、应用实例
持久实例 (persistent) 主动注销才摘除 etcd 数据库连接、外部依赖

[!warning] 常见陷阱 把数据库当成临时实例注册 —— 数据库挂了不需要被 "检测到",它自己控制启停。应该用持久实例。

优雅上下线

服务下线时不能直接断网,否则正在处理的请求会失败。

上线流程:

  1. 进程启动 → 加载配置、初始化连接池
  2. /ready 探针返回 200 → 注册中心注册 + K8s 加入负载均衡池
  3. 开始接收流量

下线流程:

  1. 注册中心注销实例 → 停止接收新请求
  2. 等待正在处理的请求完成(最大等待 N 秒)
  3. 关闭连接池、保存状态
  4. 进程退出

[!tip] K8s Pod 优雅终止 以 sidecar 方式集成 Consul Agent 时,Pod 配置如下:

spec:
  terminationGracePeriodSeconds: 30    # 最多等 30 秒再 SIGKILL
  initContainers:
    - name: consul-init                 # 先启动 Sidecar
      image: consul:latest
      command: ["consul", "agent", "-bind", "-config-file=/etc/consul.d/server.json"]
  containers:
    - name: app
      lifecycle:
        preStop:
          exec:
            command: ["sh", "-c", "sleep 15"]   # 先停流量(让 LB / Consul 摘流)
        postStart:
          exec:
            command: ["sh", "-c", "sleep 5"]    # 等 Sidecar 就绪后再开流量

关键点:preStop sleep 留给 LB / Consul 摘流时间,postStart sleep 确保 Sidecar 已准备好再做健康检查。

关联笔记