内部服务(自己家的 RPC)和外部服务(第三方 HTTP 接口)调用方式不同。这篇讲清规范、配置、svc 注入,以及两端各自的示例代码。
go-zero 把「调用」也纳入了它的治理哲学:内部微服务用 gRPC(zrpc),外部 HTTP 接口用 httpc。两者都通过 svc.ServiceContext 注入,不在 logic 里现建连接。
核心规范:无论内部外部,客户端都在 main → svc.ServiceContext 里建一次,logic 只管「调」,不负责「连」。 这样连接数可控、配置外置、测试可替换 —— 和「不用全局变量」那篇是一回事。
如果你调用的是另一个 go-zero 服务,双方用 proto 定义契约,goctl 生成客户端,注入 svc 后直接调。
// user.proto syntax = "proto3"; package user; service User { rpc GetUser (GetUserReq) returns (GetUserResp); } message GetUserReq { int64 id = 1; } message GetUserResp { int64 id = 1; string name = 2; }
goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=. \
--style gozero # 生成 user.pb.go + 服务端 + 客户端
// internal/svc/servicecontext.go import user "yourorg/app/user/rpc/user" // 生成的客户端包 type ServiceContext struct { Config config.Config UserRpc user.User // ✅ RPC 客户端,注入进来 } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, UserRpc: user.NewUser(c.UserRpc), // 连接只建一次 } }
// internal/logic/ordercreate.go func (l *OrderCreateLogic) OrderCreate(req *types.OrderCreateReq) (*types.OrderCreateResp, error) { // 直接通过 svcCtx 调内部 user 服务,无需自己建连接 u, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.GetUserReq{Id: req.UserId}) if err != nil { return nil, err } // ...用 u.Name 继续写下单逻辑 return &types.OrderCreateResp{Ok: true}, nil }
调外部系统(支付、短信、地图、天气等)走 HTTP。go-zero 自带 rest/httpc:类型化请求、内置超时、熔断、还能走服务发现。
// internal/svc/servicecontext.go import "github.com/zeromicro/go-zero/rest/httpc" type ServiceContext struct { Config config.Config PayHttp httpc.Service // ✅ 外部支付网关客户端 } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, PayHttp: httpc.NewService(c.PayHttp), // 按配置建客户端 } }
// internal/logic/pay.go var payResp PayQueryResp err := l.svcCtx.PayHttp.Do(l.ctx, http.MethodPost, "/v1/pay/query", req, &payResp) // 自动超时+熔断 if err != nil { return nil, err }
// 只想简单发个请求、不配服务发现 var resp WeatherResp err := httpc.Do(ctx, http.DefaultClient, http.MethodGet, "https://api.weather.com/now?city=bj", nil, &resp)
net/http?可以用,但 httpc 帮你统一了超时、熔断(breaker)、重试和服务发现,和 go-zero 的服务治理理念一致,也更容易被 svc 统一注入管理。内部 zrpc 和外部 httpc 的配置同样写在 etc/xxx.yaml,由 config.go 映射,main 启动时注入 svc。
type Config struct { rest.RestConf UserRpc zrpc.RpcClientConf // 内部 PayHttp httpc.ServiceConf // 外部 }
UserRpc:
Etcd:
Hosts: [127.0.0.1:2379]
Key: user.rpc
PayHttp:
Target: "https://pay.example.com"
Timeout: 3s这套「配置外置 + main 建连接 + svc 注入 + logic 调用」的流程,正是 go-zero 替代全局变量、统一管理外部依赖的标准做法(详见「全局变量与 svc」篇)。
| 维度 | 内部服务(zrpc) | 外部服务(httpc) |
|---|---|---|
| 协议 | gRPC(protobuf) | HTTP / JSON |
| 契约来源 | 双方共享 .proto | 对方文档 / OpenAPI |
| 生成方式 | goctl rpc protoc 出客户端 | 手写请求/响应 struct |
| 配置位置 | zrpc.RpcClientConf(含 etcd 发现) | httpc.ServiceConf(Target/超时) |
| svc 字段类型 | user.User(接口) | httpc.Service(接口) |
| 服务发现 | etcd 自动 | 可配 Target(也支持 etcd) |
| 治理(超时/熔断) | 内置 | 内置(breaker/timeout) |
每次请求 new 一个连接成本极高且无法复用。客户端必须在 svc 建一次,logic 复用。
一律走 etc/xxx.yaml + config.go,方便多环境切换和保密。
全局 client 难测试、难替换、难感知生命周期。交给 svc 注入,与「不用全局变量」规范一致。