深入理解 gRPC 服务发现:从原理到实现
前言
在微服务架构中,服务实例的地址是动态变化的。服务可能随时扩容、缩容、重启或迁移,如果客户端硬编码服务地址,将面临以下问题:
本文基于 Microg 框架讲解,项目地址:https://github.com/atliliw/microg
如果觉得有帮助,欢迎 Star ⭐
- 服务扩容时,需要修改客户端配置
- 服务宕机时,客户端无法自动切换
- 无法实现负载均衡
服务发现 是解决这些问题的关键机制。本文将深入讲解 gRPC 服务发现的完整流程,包括服务注册、服务发现、Resolver 解析等核心机制。
一、服务发现的三个角色
服务发现涉及三个核心角色:
| 角色 | 职责 | 说明 |
|---|---|---|
| Provider | 服务提供者 | 启动时注册到 Registry,停止时注销 |
| Registry | 注册中心 | 存储服务信息,提供查询和监听能力 |
| Consumer | 服务消费者 | 通过服务名发现实例,发起调用 |
Provider Registry Consumer
(服务提供者) (注册中心) (服务消费者)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ user-srv│ │ Consul │ │ client │
│ :9000 │ │ │ │ │
└─────────┘ └─────────┘ └─────────┘
职责: 职责: 职责:
- 启动服务 - 存储服务信息 - 发现服务
- 注册到 Registry - 提供查询/监听 - 调用服务
- 停止时注销 - 健康检查 - 负载均衡
二、服务注册流程
当 Provider 启动时,需要将自己注册到 Registry。完整流程如下:
┌─────────────────────┐
│ 1. Provider 启动 │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 2. 创建 Registry │
│ consul.New(client) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 3. 创建 gRPC Server │
│ rpcserver.New() │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 4. 创建 App │
│ app.New( │
│ WithName(), │
│ WithRPCServer(), │
│ WithRegistrar(), │
│ ) │
└──────────┬──────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 5. 构建服务实例 ServiceInstance │
│ { │
│ ID: "uuid-xxx" │
│ Name: "user-srv" │
│ Endpoints: ["grpc://192.168.1.10:9000"]│
│ } │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 6. Registrar.Register(ctx, instance) │
│ │
│ 调用 Consul API 注册服务 │
│ 配置健康检查 (TCP/HTTP) │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 7. Consul 存储 │
│ │
│ Services: │
│ user-srv: │
│ - ID: xxx, Address: 192.168.1.10:9000│
│ - Check: TCP health check (10s) │
└─────────────────────────────────────────────┘
代码示例
go
// Provider 启动代码
func main() {
// 1. 创建 Consul 客户端
consulClient, _ := api.NewClient(api.DefaultConfig())
// 2. 创建 Registry
registry := consul.New(consulClient,
consul.WithHealthCheck(true),
consul.WithHeartbeat(true),
)
// 3. 创建 gRPC Server
rpcServer := rpcserver.NewServer(
rpcserver.WithAddress(":9000"),
)
// 4. 创建 App 并启动
app.New(
app.WithName("user-srv"),
app.WithRPCServer(rpcServer),
app.WithRegistrar(registry),
).Run()
}
三、服务发现流程
当 Consumer 需要调用服务时,通过服务名从 Registry 发现服务实例。完整流程如下:
┌─────────────────────┐
│ 1. Consumer 启动 │
└──────────┬──────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 2. 创建 gRPC 连接 │
│ │
│ conn, _ := grpc.Dial( │
│ "discovery:///user-srv", // 服务名 │
│ grpc.WithResolvers(builder), │
│ ) │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 3. gRPC 解析 endpoint │
│ │
│ "discovery:///user-srv" │
│ │ │
│ ├── scheme = "discovery" │
│ └── serviceName = "user-srv" │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 4. Builder.Build() 创建 Resolver │
│ │
│ a. 提取服务名:user-srv │
│ b. 调用 Discovery.Watch() 创建 Watcher │
│ c. 创建 Resolver 实例 │
│ d. 启动后台监听 goroutine │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 5. Discovery.Watch() 创建监听器 │
│ │
│ a. 创建 serviceSet 管理实例和 watcher │
│ b. 创建 watcher 实例 │
│ c. 启动后台轮询 goroutine │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 6. 后台轮询 Consul(长轮询机制) │
│ │
│ for { │
│ // 携带 WaitIndex 查询 │
│ instances = consul.Service(name, idx)│
│ │
│ if 服务变化 { │
│ serviceSet.broadcast(instances) │
│ } │
│ } │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 7. Resolver.watch() 监听变化 │
│ │
│ for { │
│ instances := watcher.Next() // 阻塞│
│ resolver.update(instances) │
│ } │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 8. Resolver.update() 更新连接地址 │
│ │
│ // 转换为 gRPC 地址格式 │
│ addrs := []resolver.Address{ │
│ {Addr: "192.168.1.10:9000"}, │
│ {Addr: "192.168.1.11:9000"}, │
│ } │
│ │
│ // 通知 gRPC 更新 │
│ cc.UpdateState(resolver.State{ │
│ Addresses: addrs, │
│ }) │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 9. gRPC 负载均衡 │
│ │
│ 根据策略选择地址发起请求: │
│ - round_robin: 轮询 │
│ - pick_first: 选第一个 │
│ - p2c: Power of Two Choices │
└─────────────────────────────────────────────┘
代码示例
go
// Consumer 连接代码
func main() {
// 1. 创建 Registry
registry := consul.New(consulClient)
// 2. 创建 gRPC 连接(使用服务发现)
conn, _ := rpcserver.DialInsecure(ctx,
rpcserver.WithEndpoint("discovery:///user-srv"),
rpcserver.WithDiscovery(registry),
)
// 3. 创建客户端
client := pb.NewUserClient(conn)
// 4. 调用服务(自动负载均衡)
resp, _ := client.GetUser(ctx, &pb.Request{Id: 1})
}
四、核心组件详解
1. Registrar(注册器)
职责:将服务实例注册到 Registry,停止时注销。
go
type Registrar interface {
Register(ctx context.Context, service *ServiceInstance) error
Deregister(ctx context.Context, service *ServiceInstance) error
}
使用者:Provider
2. Discovery(发现器)
职责:从 Registry 获取服务实例列表,创建监听器。
go
type Discovery interface {
GetService(ctx context.Context, name string) ([]*ServiceInstance, error)
Watch(ctx context.Context, name string) (Watcher, error)
}
使用者:Consumer
3. Watcher(监听器)
职责:阻塞等待服务变化,返回最新的实例列表。
go
type Watcher interface {
Next() ([]*ServiceInstance, error) // 阻塞等待
Stop() error // 停止监听
}
使用者:Discovery(内部)
4. Builder(解析器工厂)
职责:gRPC 根据 scheme 匹配 Builder,调用 Build 创建 Resolver。
go
type Builder interface {
Build(target Target, cc ClientConn, opts BuildOptions) (Resolver, error)
Scheme() string // 返回 "discovery"
}
使用者:gRPC(内部)
5. Resolver(解析器)
职责:解析服务名为地址列表,通知 gRPC 更新连接。
go
type Resolver interface {
Close()
ResolveNow(ResolveNowOptions)
}
关键方法:
watch():后台监听服务变化update():将 ServiceInstance 转换为 resolver.Addresscc.UpdateState():通知 gRPC 更新连接
使用者:gRPC(内部)
五、Consul 长轮询机制
服务发现的核心是高效感知服务变化。Consul 提供了长轮询机制:
┌─────────────────────────────────────────────────────┐
│ Consul 长轮询机制 │
└─────────────────────────────────────────────────────┘
客户端请求:
GET /v1/catalog/service/user-srv?index=100&wait=120s
│ │
│ └── 最长等待时间
└── 上次的索引
Consul 行为:
1. 如果服务没变化(index 仍为 100)
→ 阻塞等待,最多 120 秒
2. 如果服务变化了(index 变为 101)
→ 立即返回新数据
3. 如果 120 秒内无变化
→ 返回空结果,客户端重新请求
优点:
- 实时感知变化(有变化立即返回)
- 减少无效查询(无变化时阻塞等待)
- 降低 Consul 压力
六、组件调用关系图
Provider 端 Consumer 端
┌──────────────────┐ ┌──────────────────┐
│ main.go │ │ main.go │
│ │ │ │
│ app.New().Run() │ │ DialInsecure() │
└────────┬─────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ App │ │ Builder │
│ │ │ │
│ - buildInstance │ │ Build() │
│ - Register │ └────────┬─────────┘
└────────┬─────────┘ │
│ ▼
│ ┌──────────────────┐
│ │ Discovery │
│ │ │
│ │ Watch() │
│ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Registrar │ │ Watcher │
│ │ │ │
│ Register() │◄───────────────────│ Next() │
└────────┬─────────┘ └────────┬─────────┘
│ │
│ │
▼ ▼
┌────────────────────────────────────────────────────────────┐
│ Consul Registry │
│ │
│ Service: user-srv │
│ - ID: xxx, Address: 192.168.1.10:9000, Check: TCP │
│ - ID: yyy, Address: 192.168.1.11:9000, Check: TCP │
│ │
│ 长轮询:WaitIndex 查询 → 无变化阻塞 → 有变化立即返回 │
└────────────────────────────────────────────────────────────┘
七、数据流向
服务注册数据流
Provider Microg Consul
│ │ │
│ app.Run() │ │
├──────────────────────────►│ │
│ │ │
│ │ ServiceInstance │
│ │ {ID, Name, Endpoints} │
│ ├─────────────────────────►│
│ │ │
│ │ │ 存储
│◄──────────────────────────┤ 注册成功 │
服务发现数据流
Consumer Resolver Consul
│ │ │
│ Dial("discovery:///...") │ │
├──────────────────────────►│ │
│ │ │
│ │ Watch("user-srv") │
│ ├─────────────────────────►│
│ │ │
│ │ 长轮询等待变化 │
│ │◄─────────────────────────┤
│ │ │
│ │ instances: [...] │
│ │ │
│◄──────────────────────────┤ UpdateState(addrs) │
│ │ │
│ gRPC 连接到新地址 │ │
八、总结
核心流程
Provider 用 Registrar 注册服务到 Consul
Consumer 用 Discovery 从 Consul 发现服务
Discovery 创建 Watcher 监听服务变化
gRPC 调用 Builder 创建 Resolver
Resolver 通过 Watcher 获取实例,通知 ClientConn 更新连接
组件职责一览
| 组件 | 职责 | 使用者 |
|---|---|---|
| Registrar | 注册/注销服务 | Provider |
| Discovery | 获取服务列表,创建监听器 | Consumer |
| Watcher | 监听服务变化 | Consumer(内部) |
| Builder | 创建 Resolver | gRPC(内部) |
| Resolver | 解析服务地址,通知更新 | gRPC(内部) |
设计亮点
- 角色清晰:Provider/Consumer/Registry 职责分明
- 实时感知:Watch 机制 + 长轮询,实时感知服务变化
- 高效节能:长轮询减少无效查询
- 标准接口:遵循 gRPC resolver 标准,易于扩展
本文完整讲解了 gRPC 服务发现的原理与实现。理解这些机制,有助于更好地构建微服务应用。
项目地址:https://github.com/atliliw/microg
如果觉得有帮助,欢迎点赞收藏,给个 Star ⭐!