Consul 是什么?从零理解配置中心与服务发现
写给自己的 Consul 入门笔记。搞清楚"它是什么、解决什么问题、怎么工作的"。
一、背景:没有 Consul 之前
假设你有三个服务:订单服务、用户服务、支付服务。
问题 1:配置怎么管?
# 每个服务各自写死配置
DB_URL=postgres://localhost:5432/order
REDIS_ADDR=localhost:6379
PAY_SECRET=abc123改一个配置 → 要重新部署 → 所有服务都要改自己的配置文件 → 乱。
问题 2:服务之间怎么找到对方?
# 订单服务硬编码调用用户服务
user_service_url = "http://192.168.1.10:8080"用户服务换机器了 → 订单服务也要改代码重新部署 → 乱。
Consul 就是来解决这两个问题的。
二、Consul 是什么
Consul 是 HashiCorp 公司用 Go 语言写的一个开源工具,核心能力三件套:
| 能力 | 解决什么问题 |
|---|---|
| 服务注册与发现 | 服务自动上报自己在哪,其他人自动找到它 |
| 配置中心(KV Store) | 配置集中存储,修改实时推送,不用重启服务 |
| 健康检查 | 自动踢掉挂掉的服务,只返回健康的实例 |
一句话:Consul 是分布式系统的"电话本 + 公告栏"。
三、核心概念
3.1 Agent
Consul 以 Agent 进程的形式运行,有两种角色:
┌─────────────────────────────────────┐
│ Consul 集群 │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ Server │◄──►│ Server │ ← 存储数据,做决策(通常 3 或 5 个)
│ │ (Leader)│ │ │ │
│ └────┬────┘ └─────────┘ │
│ │ │
│ ┌────▼────┐ ┌─────────┐ │
│ │ Client │ │ Client │ ← 每台业务机器上跑,转发请求
│ └────┬────┘ └────┬────┘ │
└────────┼─────────────┼─────────────┘
│ │
[订单服务] [用户服务]- Server:负责存数据、共识投票(Raft 协议),通常部署 3 或 5 个保证高可用
- Client:轻量代理,跑在业务机器上,负责转发请求给 Server
3.2 Raft 共识算法(为什么 Consul 的数据是可信的)
Consul Server 之间用 Raft 算法保证一致性:
写操作流程:
Client 发写请求
→ 打到任意 Server
→ 转发给 Leader
→ Leader 写入本地 Log
→ 同步给超过半数的 Follower(如 3 节点需 2 个确认)
→ 确认后 Commit
→ 返回成功给 Client关键点:只要多数节点存活,数据就不会丢。3 节点集群可以容忍 1 个节点挂掉。
四、配置中心原理
4.1 数据模型
Consul 的配置存在 KV Store 里,就是一个树形的键值对:
config/
├── app/
│ ├── db-url = "postgres://..."
│ ├── redis-addr = "localhost:6379"
│ └── pay-secret = "abc123"
└── feature-flags/
└── new-checkout = "false"4.2 读配置
# HTTP API 读
curl http://localhost:8500/v1/kv/config/app/db-url
# 返回(Base64 编码)
{
"Key": "config/app/db-url",
"Value": "cG9zdGdyZXM6Ly8u...", ← Base64("postgres://...")
"ModifyIndex": 42 ← 版本号,Watch 用的
}4.3 Watch 机制(配置热更新的关键)
Consul 用长轮询(Long Polling)实现配置变更推送:
┌─────────────┐ ┌─────────┐
│ 你的服务 │ │ Consul │
└──────┬──────┘ └────┬────┘
│ │
│ GET /v1/kv/config/app/db-url │
│ ?wait=60s&index=42 ─────────►│ ← 带上当前版本号,最多等 60 秒
│ │
│ (配置没变,挂着等...) │
│ │
│ (管理员改了配置) │
│ │
│◄──────── 立即返回新值 ──────────│ ← 版本号变成 43
│ index=43 │
│ │
│ 服务收到 → 热更新配置,不重启 │
│ │
│ GET ...?wait=60s&index=43 ───►│ ← 继续 Watch 下一次变化效果:管理员在 Consul UI 改一个配置 → 服务几秒内自动感知 → 无需重启。
五、服务发现原理
5.1 服务注册
服务启动时,向本机的 Consul Agent 注册自己:
POST /v1/agent/service/register
{
"ID": "user-service-1",
"Name": "user-service",
"Address": "192.168.1.10",
"Port": 8080,
"Tags": ["v2", "prod"],
"Check": {
"HTTP": "http://192.168.1.10:8080/health",
"Interval": "10s",
"Timeout": "3s"
}
}5.2 健康检查
Consul 定时主动探测你的服务是否存活:
三种健康检查方式:
1. HTTP Check(最常用)
Consul 每 10s GET 你的 /health 接口
200 → 健康 ✅
非 200 / 超时 → 不健康 ❌
2. TCP Check
Consul 每 10s TCP connect 你的端口
连得上 → 健康 ✅
3. TTL Check(适合没有 HTTP 端点的 worker)
你自己定时 PUT 心跳给 Consul
超时没收到 → 不健康 ❌5.3 服务查询
其他服务来查"我要调用 user-service,它在哪?":
GET /v1/health/service/user-service?passing=true
# 只返回健康的实例
[
{ "Service": { "Address": "192.168.1.10", "Port": 8080 } },
{ "Service": { "Address": "192.168.1.11", "Port": 8080 } }
]关键:?passing=true 过滤掉不健康的实例,调用方只会拿到可用的地址。
5.4 完整流程图
[用户服务] 启动
│
▼
向 Consul 注册(IP:Port + 健康检查地址)
│
▼
Consul 每 10s 检查 /health ──→ 正常:标记 passing
挂掉:标记 critical,从列表移除
[订单服务] 要调用用户服务
│
▼
问 Consul:"user-service 有哪些健康实例?"
│
▼
Consul 返回健康实例列表
│
▼
订单服务自己选一个(随机 / 轮询)→ 发起调用六、本地快速体验
6.1 Docker 一条命令启动
docker run -d \
--name consul \
-p 8500:8500 \
consul:latest agent -dev -ui -client=0.0.0.0打开 http://localhost:8500 → 可以看到 Web UI。
6.2 写一个配置
# 写入
curl -X PUT -d 'postgres://localhost:5432/mydb' \
http://localhost:8500/v1/kv/config/app/db-url
# 读取
curl http://localhost:8500/v1/kv/config/app/db-url?raw
# 输出:postgres://localhost:5432/mydb6.3 注册一个服务(模拟)
curl -X PUT -d '{
"ID": "user-svc-1",
"Name": "user-service",
"Address": "127.0.0.1",
"Port": 8080,
"Check": {
"TTL": "30s"
}
}' http://localhost:8500/v1/agent/service/register在 UI 的 Services 页面里就能看到它了。
七、Go 接入代码
package main
import (
"fmt"
"log"
"github.com/hashicorp/consul/api"
)
func main() {
// 连接本地 Consul(默认 localhost:8500)
client, err := api.NewClient(api.DefaultConfig())
if err != nil {
log.Fatal(err)
}
// ── 配置中心:读配置 ──────────────────────────
kv := client.KV()
pair, _, err := kv.Get("config/app/db-url", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("DB URL:", string(pair.Value))
// ── 服务发现:查询健康实例 ────────────────────
health := client.Health()
services, _, err := health.Service("user-service", "", true, nil)
if err != nil {
log.Fatal(err)
}
for _, s := range services {
fmt.Printf("发现实例: %s:%d\n", s.Service.Address, s.Service.Port)
}
}八、和其他方案对比
| Consul | etcd | Nacos | |
|---|---|---|---|
| 定位 | 服务发现 + 配置 | 分布式 KV | 微服务治理 |
| 语言 | Go | Go | Java |
| 内存占用 | ~30MB | ~50MB | ~300MB |
| 内置健康检查 | ✅ 丰富 | ⚠️ 仅 TTL | ✅ 丰富 |
| Web UI | ✅ | ❌ | ✅ |
| 适合场景 | 无 K8s 本地/中小集群 | K8s 内部 | Java 生态 |
九、总结
Consul 解决的核心问题:
配置中心
服务不用重启就能感知配置变化
→ KV Store + 长轮询 Watch
服务发现
服务不用硬编码对方地址
→ 注册 + 健康检查 + 查询
数据可靠性
多节点不丢数据
→ Raft 共识算法本地开发首选 Consul:一个 Docker 命令,UI、配置、服务发现全有,不依赖 Java,Go 原生支持。
参考:Consul 官方文档