Skip to main content

服务示例

概述

go-zero 提供了 gRPC server 能力,其提供了:

  1. 服务发现能力(etcd 作为注册中心)
  2. 负载均衡能力(p2c 算法)
  3. 节点亲和性处理
  4. 多节点直连支持
  5. 超时处理
  6. 限流,熔断能力
  7. 鉴权能力
  8. 异常捕获

使用示例

在 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=.
温馨提示
  1. goctl 安装请参考 《goctl 安装》
  2. rpc 代码生成指令教程请参考 《goctl rpc》
  3. 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 配置即可,最简参考配置如下(灰色底纹部分):

demo/etc/greet.yaml
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 节点。

6. stub 实现

通过 goctl 生成的代码不需要用户手动去实现 stub 方法了,goctl 工具会帮你全部实现掉,参考代码如下:

demo/internal/server/greetserver.go
// 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 文件中填写我们的业务代码即可,参考业务代码(灰色底纹部分):

demo/internal/logic/pinglogic.go
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 环境),相关代码如下:

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)
}
})
...
}

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》