Skip to content
Go back

Consul服务注册与发现——告别手动Nginx配置

Consul 服务注册与发现:告别手动 Nginx 配置

一句话结论(30s)

Consul 解决的不是”怎么找到服务”(那是 DNS 查询),而是”在动态扩缩容环境中始终知道谁活着、谁挂了、谁新来”——因为实例 IP 会随容器重启变化、故障节点会让 Nginx 继续把请求打到已死的实例,所以用「服务注册 + 健康检查摘除 + consul-template 自动渲染 Nginx」三件套,把运维从半夜改 nginx.conf 变成全自动无感扩缩容。

核心原理(2min)

底层深入(5-10min)

传统运维的噩梦

每次新上两个服务实例,运维需要做的:

# 1. SSH 到 Nginx 服务器
# 2. 编辑 /etc/nginx/conf.d/upstream.conf
upstream order-service {
    server 10.0.1.10:8080 weight=1;
    server 10.0.1.11:8080 weight=1;
    # server 10.0.1.12:8080 weight=1;  ← 新增这一行
}
# 3. nginx -t  # 检查语法
# 4. nginx -s reload  # 重载配置

看起来不难?考虑现实场景:

手动维护 Nginx upstream 在微服务时代是不可持续的。 服务注册与发现(Service Registry & Discovery)正是为解决这个问题而生。

想一想:为什么手动维护在微服务时代必然失效? 因为实例 IP 随容器重启持续变化、扩缩容又动态发生,配置的”更新速度”永远追不上实例的”变化速度”。手工改配置本质是”人肉轮询”,而故障和扩容是随机事件——当系统规模到几十个服务、上百个实例,人工维护的延迟和遗漏会让 Nginx 长期拿着过期清单,把请求打到已死的实例上。

Consul 的位置与工作原理

服务实例启动 → 向 Consul Agent 注册(HTTP PUT)→ Consul Server 存储

Nginx ← consul-template 监听变更 ← Consul Watch(长轮询)

三大组件

组件作用部署方式
Consul Server存储服务注册信息,强一致性(Raft)3-5 节点集群
Consul Agent运行在每个服务节点上,负责健康检查每个节点一个
consul-template监听 Consul 变更,动态渲染 Nginx 配置与 Nginx 同机部署

第一步:服务注册

Spring Boot 服务启动时,自动向 Consul 注册:

# application.yml
spring:
  cloud:
    consul:
      host: consul-server-1
      port: 8500
      discovery:
        service-name: order-service
        instance-id: ${spring.application.name}:${random.value}
        health-check-path: /actuator/health
        health-check-interval: 15s

服务一启动,Consul 里就会出现:

order-service:
  - 10.0.1.10:8080  [healthy]
  - 10.0.1.11:8080  [healthy]
  - 10.0.1.12:8080  [healthy]  ← 新增实例,自动出现

第二步:健康检查——自动摘除故障节点

Consul Agent 每 15 秒请求一次 /actuator/health

order-service:
  - 10.0.1.10:8080  [healthy]
  - 10.0.1.11:8080  [critical] ← 挂了,请求不再打到这个节点
  - 10.0.1.12:8080  [healthy]

不需要人工发现故障、不需要手动改配置、不需要 reload Nginx——故障节点的流量自动被其他健康节点接替。故障恢复后,健康检查重新通过,节点自动回到服务列表。

想一想:为什么健康检查是”主动探测”,而不是等实例自己上报? 因为实例挂掉时往往已经没机会”上报自己死了”——进程被 kill、OOM、网络中断,都不可能主动发一条”我挂了”的消息。只有靠 Agent 从外部主动探测、超时判死,才能可靠发现故障。这也是 Consul(主动探测)比 Eureka(客户端心跳)在故障发现上更及时的原因:心跳只能靠”收不到”来推断,还容易误判。

第三步:consul-template 动态更新 Nginx

consul-template 的核心能力:监听 Consul 的服务列表变更,渲染模板,reload Nginx。

# nginx-upstream.ctmplconsul-template 模板文件
upstream order-service {
    {{ range service "order-service" }}
    server {{ .Address }}:{{ .Port }} weight=1;
    {{ end }}
}

consul-template 运行时:

consul-template \
  -template "nginx-upstream.ctmpl:/etc/nginx/conf.d/upstream.conf:nginx -s reload"

当 Consul 中 order-service 的服务列表发生变更(节点上线/下线/故障),consul-template:

  1. 重新渲染模板 → 生成新的 upstream.conf
  2. 执行 nginx -s reload(平滑重载,不中断现有连接)

整个过程 完全自动化,延迟 < 5 秒

想一想:为什么 consul-template 要 reload Nginx,而不是让 Nginx 直接查 Consul? 因为 Nginx 本身不理解服务发现,它只会读自己的静态 upstream 配置。consul-template 扮演的是”翻译官”——把 Consul 里动态的服务列表渲染成 Nginx 认识的配置格式,再触发平滑 reload。而 nginx -s reload 是平滑重载、不中断现有连接,所以对在线请求零影响。

完整架构对比

传统方式

运维手动操作 → 改 Nginx upstream → nginx reload

               人工发现新增/故障实例
               人工修改配置
               人工确认生效

Consul 方式

新实例启动 → 自动注册到 Consul ← → 健康检查自动摘除

           consul-template 自动渲染

           nginx 配置自动更新 + reload

无感扩缩容:用户感知不到实例增减

扩容

  1. K8s HPA 检测到 CPU > 80% → 启动 3 个新 Pod
  2. 新 Pod 启动 → 自动向 Consul 注册
  3. consul-template 检测变更 → 更新 Nginx upstream → reload
  4. 新的流量自动分发到新实例

用户体验:响应时间从 500ms 降到 80ms。他们不知道有加实例——只知道”变快了”。

缩容

  1. 流量下降 → K8s 缩减 Pod
  2. Consul Agent 检测到 SIGTERM → 服务反注册(deregister)
  3. consul-template 更新 upstream → 移除下线节点
  4. Nginx 把新流量发给剩余节点,已建立连接不受影响

关键:服务优雅下线前,先从 Consul 反注册 → 等待 Nginx reload(3-5 秒)→ 再真正关闭 → 确保没有请求被中断。

// Spring Boot 优雅下线
@PreDestroy
public void shutdown() {
    // 1. 从 Consul 反注册(停止接受新请求)
    consulDiscoveryClient.deregister();
    // 2. 等待 Nginx 感知变更(consul-template 会更新配置)
    Thread.sleep(5000);
    // 3. 处理完所有进行中的请求
    // Spring Boot 的 graceful shutdown 自动处理
}

想一想:优雅下线为什么要”先反注册、等几秒、再关进程”? 因为从反注册到 consul-template 渲染、再到 Nginx reload 生效,中间有几秒的传播延迟。如果反注册后立刻 kill 进程,这几秒内 Nginx 还在往这个即将关闭的实例转发请求,就会产生 502。先反注册、再等 Nginx 感知变更,才能保证在真正关闭前,不再有新的请求被分给它。

Consul vs Eureka vs Nacos

ConsulEurekaNacos
CAPCP(强一致性)AP(可用性)CP + AP 可切换
健康检查Agent 主动探测客户端心跳上报客户端心跳 + 服务端探测
多数据中心原生支持不支持支持
生态consul-template、EnvoySpring Cloud 深度集成Spring Cloud Alibaba
学习成本低(单一二进制文件)

Consul 的最大优势:独立于 Java 生态——consul-template 让 Nginx、HAProxy、Envoy 这些非 JVM 基础设施也能无缝接入服务发现。

总结

Consul 解决的不是”怎么找人”的问题(那是一个简单的 DNS 查询),而是”怎么在动态变化的环境中始终知道谁能干活、谁挂了、谁新来的”。

三件套:

  1. 服务注册:实例启动自动注册
  2. 健康检查:故障节点自动摘除
  3. consul-template:Nginx 配置自动更新

从”运维半夜起来改 nginx.conf”到”自动扩缩容无感知”——这就是服务发现从”手动模式”到”自动驾驶”的跨越。

章末提问

Q1:Consul 和 Eureka 的本质区别是什么? 结论先行:本质区别在 CAP 取舍和健康检查机制——Consul 是 CP、Agent 主动探测,Eureka 是 AP、客户端心跳上报。 因为 Consul 用 Raft 保证服务列表强一致,Agent 从外部主动探测每个实例,宁可短暂不可用也要列表准确;Eureka 靠客户端定时心跳,集群分区时宁可返回可能过期的列表也保持可用。所以故障发现及时性上 Consul 更强,可用性上 Eureka 更宽松。

Q2:健康检查为什么能自动摘除故障节点?摘除的代价是什么? 结论先行:因为 Agent 每 15 秒主动探活,非 200 或超时就标记 critical 并从服务列表移除,流量自然不再打过去;代价是存在”探活间隔内的误判窗口”。 因为探测是周期性的,一个实例在两次探活之间挂掉,这十几秒内它仍在服务列表里、仍可能收到请求;反过来探测太频繁又会给实例和 Consul 带来额外开销。所以 15 秒是”发现及时性”和”探测成本”之间的折中。

Q3:consul-template 触发 Nginx reload 会不会中断正在处理的请求? 结论先行:不会,nginx -s reload 是平滑重载,旧 worker 处理完存量连接后才退出。 因为 Nginx 的 reload 不是”停掉再启动”,而是拉起新 worker 加载新配置,旧 worker 继续把已在处理的请求走完,新连接交给新 worker,整个过程对在线请求无感。这正是 consul-template 选 nginx -s reload 而不是 restart 的原因。


Share this post on:

Previous Post
HikariCP连接池优化——从参数配置到底层原理
Next Post
面试回答策略——信息密度与"先结论后细节