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

它演示了什么

客户端拨号

来自 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 再排空在途调用
				
健康检查的时机。"监听"与"就绪"是同一时刻:探针看到 SERVING 时, 服务确实已经能接流量;关停时先转 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":""}。

target 不能填自己 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

相关