grpc — 服务注册与 fail-fast 客户端
pkg/core/grpcx 管 gRPC 的生命周期:服务端由
verticle.Run 按 grpc: 段创建并优雅关停,客户端在
Setup 里用 env.GRPCClient 拨号并自动注册关闭。
因为 grpcx 内置了标准健康服务,这个示例不需要 .proto、不需要 protoc。
运行
完全离线,不需要 make dev。同时开两个端口:HTTP :8080、
gRPC :9090。配置文件是目录里的 config.yaml,
-conf 默认指向它。
go run ./examples/grpc # 等价于 make example NAME=grpc
# http :8080 + grpc :9090
grpcurl -plaintext localhost:9090 grpc.health.v1.Health/Check # 开了 reflection,免 proto
grpcurl -plaintext localhost:9090 list
curl -i localhost:8080/api/v1/health # 未配 target → 503
它演示了什么
- 服务端由框架建:
grpc:段配了地址才会创建 server; 没配就不起 gRPC,HTTP 照常工作。 - 内置健康服务:
grpcx自己注册标准grpc.health.v1,示例代码里没有一行注册代码; K8s gRPC 探针与负载均衡直接可用(health: true,默认开)。 - 用于调试的 reflection 开关:
reflection: true时 grpcurl/grpcui 免 proto(本示例的 config.yaml 打开了它;生产按需关闭)。 - fail-fast 客户端:
env.GRPCClient在grpc.dial_timeout(示例 3s)内确认连接 READY,连不上就让启动失败。 - 连接由框架关:
env.GRPCClient内部AddCloser(conn.Close),你不手动关。 - BFF 形态:HTTP handler 调对端 gRPC,把结果暴露成 JSON。
客户端拨号
来自 examples/grpc/main.go 的 Setup。
拨号放在 Setup 是刻意的:不可达的依赖应该在启动时失败,而不是第一个请求。
target 留空则只做服务端。
// A peer client. Dialing here is deliberate: an unreachable
// dependency should fail the startup, not the first request.
if s.cfg.Target != "" {
conn, err := env.GRPCClient(ctx, s.cfg.Target)
if err != nil {
return err
}
s.client = healthpb.NewHealthClient(conn)
obs.Info(ctx, "grpc client ready", "target", s.cfg.Target)
}
健康检查的时机
来自 check。没有配 target 时返回
errs.Unavailable(503);调不通对端也是
503,但用不同的业务 code。
if s.client == nil {
return nil, errs.Unavailable(50301, "no gRPC peer configured (set grpcdemo.target)")
}
resp, err := s.client.Check(ctx, &healthpb.HealthCheckRequest{Service: s.cfg.Service})
if err != nil {
return nil, errs.Unavailable(50302, "gRPC check failed: %v", err)
}
return map[string]any{
"target": s.cfg.Target,
"service": s.cfg.Service,
"status": resp.GetStatus().String(),
}, nil
sequenceDiagram
participant O as verticle.Run
participant S as grpcx.Server :9090
participant C as env.GRPCClient
participant P as 对端 gRPC
O->>S: New(addr, health=true, reflection=true)
Note over S: 启动时 NOT_SERVING,Serve 后才 SERVING
O->>C: Setup 里 Dial(target, dial_timeout=3s)
C->>P: 等到连接 READY,否则返回错误
P-->>C: READY
Note over O: 服务启动;HTTP :8080 同时可用
O->>S: Shutdown:先 NOT_SERVING 再排空在途调用
验证
# 服务端:自带健康服务 + reflection
grpcurl -plaintext localhost:9090 grpc.health.v1.Health/Check
grpcurl -plaintext localhost:9090 list
# BFF 端点:未配 grpcdemo.target 时是 503
curl -i localhost:8080/api/v1/health
未配 target 时返回
503 {"code":50301,"msg":"no gRPC peer configured (set grpcdemo.target)"}。
要验证客户端路径:再起一个实例(或任何提供 grpc.health.v1 的对端),
把本实例的 grpcdemo.target 指向它的 gRPC 地址,然后
curl -i localhost:8080/api/v1/health 会得到
{"status":"SERVING","target":"…","service":""}。
Setup 阶段自己的 gRPC server 还没开始 serve,所以把
target 填成 :9090(自己)会让 dial 超时、启动失败。
测试
main_test.go 完全离线:它在临时端口上起一个真实的
grpcx.New server,用 grpcx.Dial 拨号,
断言健康检查返回 SERVING;另有一例把示例的 BFF handler
接到一个真实对端上,断言 status 能穿到 HTTP 结果里;
还有一例断言未配对端时 check 会报错。它没有 .proto、没有网络,
因为 grpcx 默认就提供健康服务。
go test ./examples/grpc
相关
- gRPC 与 rpcx:两种 RPC 的对比与选型。
- 可观测性:gRPC 的 traceparent 传播。
- examples/rpcx:无需 IDL 的另一种 RPC。
- Skill:
skills/matex-setup-grpc/SKILL.md。 - 示例索引。