服务示例
概述
go-zero 提供了 gRPC server 能力,其提供了:
- 服务发现能力(etcd 作为注册中心)
- 负载均衡能力(p2c 算法)
- 节点亲和性处理
- 多节点直连支持
- 超时处理
- 限流,熔断能力
- 鉴权能力
- 异常捕获
使用示例
在 go-zero 中,我们可以使用 goctl 来快速生成一个 gRPC 服务,也可以通过 goctl 0 代码生成一个 gRPC 服务示例。
温馨提示
通过 goctl 快速生成并启动一个 gRPC 服务示例可以参考 《快速开始•微服务篇》
我们这里以一个 proto 来创建一个完整的 gRPC 服务。
1. 创建服务目录,初始化 go module 工程
$ mkdir demo && cd demo
$ go mod init demo
2. 快速生成一个 proto 文件
$ goctl rpc -o greet.proto
3. 根据 proto 生成 gRPC 服务
$ goctl rpc protoc greet.proto --go_out=. --go-grpc_out=. --zrpc_out=.
温馨提示
- goctl 安装请参考 《goctl 安装》
- rpc 代码生成指令教程请参考 《goctl rpc》
- proto 使用相关问题请参考 《proto 代码生成常见问题》
4. 参考目录结构
demo
├── etc
│ └── greet.yaml
├── go.mod
├── greet
│ ├── greet.pb.go
│ └── greet_grpc.pb.go
├── greet.go
├── greet.proto
├── greetclient
│ └── greet.go
└── internal
├── config
│ └── config.go
├── logic
│ └── pinglogic.go
├── server
│ └── greetserver.go
└── svc
└── servicecontext.go
8 directories, 11 files
温馨提示
服务目录结构介绍请参考 《项目结构》
5. 服务发现/直连模式
在 go-zero 中,支持 etcd 服务注册和直连模式,我们仅对 etc 目录下的静态配置文件稍作调整即可。
温馨提示
gRPC 服务配置可参考 《gRPC 服务配置》
除了 go-zero rpc 内置的 ectd 作为服务注册组件外,社区还提供了对 nacos,consul 等的服注册支持,详情可参考 更多服务注册组件
- etcd 服务注册
- 直连模式
使用 etcd 作为注册中心,只需要在静态配置文件中添加 etcd 配置即可,最简参考配置如下(灰色底纹部分):
Name: greet.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:
- 127.0.0.1:2379
Key: greet.rpc
服务注册的 key 为 greet.rpc,我们可以在 etcd 中通过一下方法查看到:
$ etcdctl get --prefix greet.rpc
greet.rpc/7587870460981677828
192.168.72.53:8080
由于 etcd 注册的 key 都是 greet.rpc
, 从业务表现层来看,是将一个 key 注册到了 etcd,实则 go-zero 底层是将该 key 拼上了一个 etcd
的租户 id 来存储到 etcd 的,因此,在服务发现时,也会通过 etcdctl get --prefix
指令去获取所有可用的 ip 节点。
相反,使用直连模式则去除 etcd 配置即可,go-zero 自动识别,最简配置参考:
Name: greet.rpc
ListenOn: 0.0.0.0:8080
6. stub 实现
通过 goctl 生成的代码不需要用户手动去实现 stub 方法了,goctl 工具会帮你全部实现掉,参考代码如下:
// Code generated by goctl. DO NOT EDIT.
// Source: greet.proto
package server
import (
"context"
"demo/greet"
"demo/internal/logic"
"demo/internal/svc"
)
type GreetServer struct {
svcCtx *svc.ServiceContext
greet.UnimplementedGreetServer
}
func NewGreetServer(svcCtx *svc.ServiceContext) *GreetServer {
return &GreetServer{
svcCtx: svcCtx,
}
}
func (s *GreetServer) Ping(ctx context.Context, in *greet.Request) (*greet.Response, error) {
l := logic.NewPingLogic(ctx, s.svcCtx)
return l.Ping(in)
}
7. 编写业务代码
通过 goctl 生成代码后,我们只需要在 logic 文件中填写我们的业务代码即可,参考业务代码(灰色底纹部分):
package logic
import (
"context"
"demo/greet"
"demo/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type PingLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic {
return &PingLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *PingLogic) Ping(in *greet.Request) (*greet.Response, error) {
return &greet.Response{
Pong: "pong",
}, nil
}
8. 开启 gRPC 调试开关
gRPC 提供了调试功能,以便于我们可以通过 grpcurl 等工具进行调试,
在 go-zero,建议在开发环境和测试环境开启,预生产环境和正式环境建议关闭,因此我们在静态配置文件中将环境模式配置为 dev
或者 test
时才会开启(默认为 dev 环境),相关代码如下:
- demo/greet.go
- demo/etc/greet.yaml
package main
...
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
greet.RegisterGreetServer(grpcServer, server.NewGreetServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})
...
}
Name: greet.rpc
ListenOn: 0.0.0.0:8080
Mode: dev
Etcd:
Hosts:
- 127.0.0.1:2379
Key: greet.rpc
9. 中间件使用
内置中间件
go-zero rpc 内置了非常丰富的中间件,详情可查看serverinterceptors
- 鉴权中间件:StreamAuthorizeInterceptor|UnaryAuthorizeInterceptor
- 熔断中间件:StreamBreakerInterceptor|UnaryBreakerInterceptor
- 指标统计中间件: UnaryPrometheusInterceptor
- 异常捕获中间件:StreamRecoverInterceptor|UnaryRecoverInterceptor
- 服务降载中间件:UnarySheddingInterceptor
- 时长统计中间件:UnaryStatInterceptor
- 超时控制中间件:UnaryTimeoutInterceptor
- 链路追踪中间件:StreamTraceInterceptor|UnaryTraceInterceptor
在以上内置中间件中,链路追踪中间件、指标统计中间件、时长统计中间件、异常捕获中间件、熔断中间件可以通过配置来开启或关闭,其他中间件默认开启。 具体配置可参考服务配置
自定义中间件
package main
...
var configFile = flag.String("f", "etc/greet.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
greet.RegisterGreetServer(grpcServer, server.NewGreetServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})
defer s.Stop()
s.AddUnaryInterceptors(exampleUnaryInterceptor)
s.AddStreamInterceptors(exampleStreamInterceptor)
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}
func exampleUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
// TODO: fill your logic here
return handler(ctx, req)
}
func exampleStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
// TODO: fill your logic here
return handler(srv, ss)
}
10. metadata 传值
参考 《Metadata》