安装
goctl
goctl是 go-zero内置的脚手架工具
# 安装最新版本
$ go install github.com/zeromicro/go-zero/tools/goctl@latest
goctl api
goctl的核心模块之一,可以通过 .api文件快速生成一个 api服务。
TODO
go env
# 查看一些环境变量
$ goctl env
# 检查 goctl 所依赖的环境是否安装完全,如果缺少依赖项会提示
# 有三个可选参数
# -i, --install 如果有缺少的组件 那么直接安装
# -f, --force 静默安装组件,不传递的话安装前需要手动确认
# -v, --verbose 是否输出执行日志
$ goctl env check
# 安装goctl需要的依赖 其作用和 goctl env check --install一致
# 也有 goctl env check 的 -f 和 -v 参数
$ gocel env install
protoc安装
protoc是用于 proto文件的代码生成工具,可以生成多种语言的代码。golang的 grpc代码生成依赖于 protoc-gen-go和 protoc-gen-go-grpc。
可以通过 goctl工具一键安装相关组件
$ goctl env check --install --verbose --force
go-zero安装
在项目根目录执行 go mod init初始化后,将 go-zero假如当前项目依赖。
$ go get -u github.com/zeromicro/go-zero@latest
DSL 语法
api 语法
syntax 语句
syntax语句用于标记 api语言的版本,当前版本为 v1
syntax = "v1"
info语句
info语句是 api语言的 meta信息,仅用于到当前文件进行描述,目前不参与代码生成
// 包含key, value 的info语句块
info(
name: "demo",
summay:
)
import 语句
import语句用于在当前 api文件中引入其它 api文件的语句块,支持相对路径和绝对路径。
// 单个import
import "/path/api"
// 多个import
import (
"base",
"/path/api"
)
数据类型
api中的数据类型基本沿用了 golang的数据类型,用于对服务/接口的请求/响应体结构进行描述。
int | bool | string | byte | float32 | float64 这些基本类型,以及 [4]int 这样的数组和切片类型以及 map 都是可用的。
同时和 go一样,也支持自定义结构体,如若需要使用结构体接收参数,api 中也同样和 go 使用 tag 接收参数。
| 规则 | 说明 | 应用 | tag示例 | 请求示例 |
|---|---|---|---|---|
json |
json序列化 |
请求体和响应体 | json:"name" | {"name": "aa"} |
path |
路由参数 | 请求体 | path:"id" | /info/:id |
form |
post请求的表单和 get请求的 query参数 |
请求体 | form:"name" | /query?name=aa |
header |
接收请求头 | 请求体 | header:"User-Agent" |
go-zero不支持多个tag来接收参数,一个结构体字段只能有一个tag
**同时还可以通过 **tag 来进行接收参数的校验,校验规则进队请求体有效。
| 规则 | 说明 | 示例 |
|---|---|---|
optional |
可选参数,允许为零值 | json:"name,optional" |
options |
当前参数仅可接收的枚举值 | json:"type,options=1 |
default |
当前参数默认值 | json:"sort,default=1" |
range |
当前参数值有效范围 | json:"age,range=[0:100]" |
range表达式规则左开右闭区间:
(min:max],表示大于min小于等于max。min和max可以缺省,缺省时分别表示0和 无穷大,但是不能同时缺省左开右闭区间:
[min:max),表示大于等于min小于max,缺省分别表示无穷小和0闭区间:
[min:max],表示大于等于min小于等于max,缺省和左开右闭相同开区间:
(min:max),表示大于min小于max,缺省和闭区间相同
// 单个结构体
type Student {
StudentId uint64 `json:"studentId"`
Name string `json:"name"`
Tags []string `json:"tags"` // 切片
Unknown map[string]string `json:"unknown"` // map
}
// 嵌套结构体
type Class {
ClassId uint `json:"classId"`
ClassName string `json:"className"`
Students []*Student `json: "students"` // 指针类型
Major {
MajorId uint `json:"majorId"`
MajorName string `json:"majorName"`
}
}
// 结构体组
type (
Integer int
Long int64
Query {
Name string `json:"name"`
Num Integer `json:"num"`
}
)
service 语句
service语句是对 http服务的直观描述,包含请求的 handler、请求方法、请求路由、请求和响应体、鉴权、中间件等定义。
@server 语句是对服务语句的 meta 信息描述,其特性包括但不限于
jwt开关- 中间件
- 路由分组
- 路由前缀
@server (
// jwt声明,value为生成的Go代码中配置文件的结构体名称 (会把jwt的token信息方式到Auth字段中)
jwt: Auth
// 路由前缀,字符串必须以 / 开头
prefix: /v1
// 路由分组,value为具体的分组名称,会按此值进行文件夹分组
group: Base
// 中间件声明,value为具体的中间件函数名称。可以声明多个中间件,使用 , 号分隔
// AuthMiddleware,LogMiddleware 会被生成为两个同名的 go 结构体,结构体有一个 Handle 函数,用于实现中间件的逻辑
middleware: AuthMiddleware
// 超时控制,value为具体的超时时间
timeout: 5s
// 除了内置的 key-value 外,其它 key-value 也可做为 annotation 信息传递给goctl及其插件
source: demo
)
@doc 语句用于对单个路由的 meta 信息进行描述,一般为 key-value 值,可以传递给 goctl及其插件进行扩展生成。
// 单行 doc
@doc "demo"
// doc 组
@doc (
source: "demo"
)
@handler 语句用于对单个路由的 handler 信息控制,主要用于生成 http.HandleFunc 的实现转换方法。
@handler demo
路由语句是单词 HTTP请求的具体描述,包括请求方法,请求路径,请求体,响应体信息。
路由规则
**路由必须以 **
/开头,节点之间必须以/分隔**路由节点可以包含 **
:用于接收路径参数,:必须是路由节点的第一个字符,并且节点值中必须要在请求结构体中以pathtag声明接收路由参数
// 没有请求体和响应体的方法
// /hello 为路由路径
get /hello
// 只有响应体的方法
get /hello (Query)
// 包含请求体和响应体的方法
get /hello (Query) returns (Student)
// type UserInfoReq {
// UserId uint64 `path:"id"`
// }
// 路由节点的 id 参数必须在请求结构体中以 path tag 接收
get /user/:id (UserInfoReq) returns (UserInfoResp)
完整示例
// 有 @server 的写法
@server (
// 路由前缀和中间件
prefix: /v1
middleware: AuthMiddleware
)
service user {
@doc "登录"
@handler login
post /user/login (LoginReq) returns (LoginResp)
@doc "用户信息"
@handler userInfo
get /user/info () returns (UserInfoResp)
}
// 可以不包含 @server
service user {
@doc "登录"
@handler login
post /user/login (LoginReq) returns (LoginResp)
@doc "用户信息"
@handler userInfo
get /user/info () returns (UserInfoResp)
}
proto 语法
参考 grpc
goctl 生成 grpc 代码时所有的 message 必须在主 proto 中声明,不支持从包外包 import 的 message。
服务分组
当服务不分组时,一个 proto文件中的 service所生成的代码都会在一个目录中,并且不分组的情况下,不支持在 proto文件中定义多个 service。
服务分组后,会按照 service名称创建文件夹分别存放,目录结构更清晰。
syntax = "proto3";
package user;
// 在定义 message 后紧跟定义 service ,就是分组写法
message UserLoginReq {}
message UserLoginResp {}
message UserInfoReq {}
message UserInfoResp {}
service UserService {
rpc Login(UserInfoReq) returns (UserLoginResp);
rpc UserInfo(UserInfoReq) returns (UserInfoResp);
}
message UserRoleListReq {}
message UserRoleListResp {}
message UserRoleCreateReq {}
message UserRoleUpdateReq {}
service UserRoleService {
rpc UserRoleList(UserRoleListReq) returns (UserRoleListResp);
rpc UserRoleCreate(UserRoleCreateReq) returns (UserRoleListResp);
}
配置
go-zero 提供了 conf 包用于加载配置,支持 yaml | json | toml 三种格式的配置文件。
配置的数据绑定均通过结构体 tag的 json 字段,tag 支持数据类型的参数校验规则且大小写不敏感。此外,在 1.4.3版本以上还可以通过 env tag 来支持环境变量配置的加载。
配置加载的优先级:env > 配置文件 > tag 中的 default 定义
// 配置结构体
type Config struct {
Host string `json:",default=0.0.0.0"`
// tag 支持数据类型的参数校验
Port uint `json:",default=8080,range(1024,65535)"`
}
func main() {
var config Config
// MustLoad 加载配置不会返回 error,加载错误会直接 fatal 掉程序
// Load() 函数加载会返回错误,不会 fatal 程序
conf.MustLoad(".\\configs\\config.yaml", &config)
fmt.Printf("%s:%d", config.Host, config.Port)
}
使用环境变量加载配置,env 后面添加环境变量的名称即可
type Config struct {
Host string `json:",default=0.0.0.0,env=SERVER_HOST"`
Port uint `json:",default=8080,range(1024,65535),env=SERVER_PORT"`
}
**此外,为了省略很多重复的配置,配置加载还支持继承策略,往 **tag 添加了 inherit 关键字之后,会自动向上一层寻找配置。
**在不使用继承的情况下,需要编写很多重复的 **etcd 配置。
type Config struct {
Etcd discov.EtcdConf `json:""`
UserRpc UserServiceConfig `json:""`
}
type UserServiceConfig struct {
Etcd discov.EtcdConf `json:""`
}
Etcd:
Key: rpcServer
Hosts:
- '127.0.0.1:6377'
- '127.0.0.1:6376'
- '127.0.0.1:6375'
UserRpc:
Etcd:
Key: userService
Hosts:
- '127.0.0.1:6377'
- '127.0.0.1:6376'
- '127.0.0.1:6375'
使用继承之后,可以省略公共配置
type UserServiceConfig struct {
// tag 添加继承关键字
Etcd discov.EtcdConf `json:",inherit"`
}
Etcd:
Key: rpcServer
Hosts:
- '127.0.0.1:6377'
- '127.0.0.1:6376'
- '127.0.0.1:6375'
UserRpc:
# 会自动在上一级寻找 etcd 的配置
Etcd:
Key: userService
服务配置
service.ServiceConf 配置用来表示一个独立的服务,例如 job ,search 等,可以被 rest 和 zrpc 等引用。也可以简单定义为自己的服务。
type MqConfig struct {
service.ServiceConf
StoreRpc zrpc.RpcClientConf
}
func main() {
var config MqConfig
conf.MustLoad("config.yaml", &config)
// 提供基础的服务配置
config.MustSetUp()
}
**这样就相当于启动了一个服务 **service ,并依赖 ServiceConf 自动集成了 Metrics、Trace、DevServer、Log 等能力。
type ServiceConf struct {
Name string // 服务名称 会显示在 log 和 trace 中
Log logx.LogConf // 日志配置
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`// 服务所处的环境
MetricsUrl string `json:",optional"` // 打点上报的url,如果为空则不会上传
Prometheus prometheus.Config `json:",optional"`
Telemetry trace.Config `json:",optional"`
DevServer DevServerConfig `json:",optional"`
}
日志配置
logx.LogConf用于 go-zero 中的日志配置,logx.MustSetUp() 会提供基础的日志配置。
func main() {
var logConfig logx.LogConf
// 使用纯文本格式输出
logConfig.Encoding = "plain"
logx.MustSetup(logConfig)
logx.Info(context.Background(), "hello go-zero")
}
// 日志配置定义
type LogConf struct {
// 服务名称
ServiceName string `json:",optional"`
// 日志打印模式 console 控制台,file 输出到文件
Mode string `json:",default=console,options=[console,file,volume]"`
// 日志格式化 json 或 plain 纯文本
Encoding string `json:",default=json,options=[json,plain]"`
// 日期格式化
TimeFormat string `json:",optional"`
// 日志在文件模式下的输出路径
Path string `json:",default=logs"`
// 日志的输出级别
Level string `json:",default=info,options=[debug,info,error,severe]"`
// 日志 content 内容的长度限制 超出会裁剪
MaxContentLength uint32 `json:",optional"`
// 是否压缩日志
Compress bool `json:",optional"`
// 是否开启 stat 日志
Stat bool `json:",default=true"`
// 日志保留天数 文件模式生效
KeepDays int `json:",optional"`
// 堆栈打印冷却时间
StackCooldownMillis int `json:",default=100"`
// 文件模式按照大小分割时的保留文件个数
MaxBackups int `json:",default=0"`
// 文件模式按照大小分割时单个文件的最大大小
MaxSize int `json:",default=0"`
// 文件模式日志分割的模式 daily 日期,size 大小
Rotation string `json:",default=daily,options=[daily,size]"`
// 文件名日期格式
FileTimeFormat string `json:",optional"`
}
HTTPServer
服务配置
针对 HTTP服务主机、端口、证书等进行配置
// 配置定义
type RestConf struct {
service.ServiceConf
// 服务监听的地址
Host string `json:",default=0.0.0.0"`
// 服务绑定的端口
Port int
// https 的证书文件
CertFile string `json:",optional"`
// https 私钥文件
KeyFile string `json:",optional"`
// 是否打印详细日志
Verbose bool `json:",optional"`
// 最大连接数
MaxConns int `json:",default=10000"`
// ContentLength 的最大长度
MaxBytes int64 `json:",default=1048576"`
// 超时时间 单位:ms
Timeout int64 `json:",default=3000"`
CpuThreshold int64 `json:",default=900,range=[0:1000)"`
Signature SignatureConf `json:",optional"`
// 需要使用的中间件
Middlewares MiddlewaresConf
TraceIgnorePaths []string `json:",optional"`
}
请求参数
go-zero 中,支持 http.Request 中 Body 、Form 、query 、path 和请求头的参数获取,这些都是通过 httpx.Parse 函数将数据解析到结构体中。结构体的 tag 定义和数据类型中相同.
响应参数
go-zero 中,依然使用 http 库原生的 http.ResponseWriter 来返回响应数据和响应头。httpx 包内包含一个扩展函数用于便捷的返回数据。例如 httpx.OkJson(w http.ResponseWriter, result any)。
type RequestData struct {
Name string `path:"name"`
}
// 简单的 httpServer 示例
func main() {
// 配置 httpServer
restServer := rest.MustNewServer(rest.RestConf{
Host: "127.0.0.1",
Port: 4001,
})
// 添加路由
restServer.AddRoute(rest.Route{
Method: http.MethodGet,
Path: "/:name",
// 路由的处理函数
Handler: func(writer http.ResponseWriter, request *http.Request) {
data := &RequestData{}
// 获取请求参数
if err := httpx.Parse(request, data); err != nil {
// 响应错误信息
httpx.Error(writer, errors.New("request params not"))
return
}
// 响应数据
httpx.OkJson(writer, map[string]string{"name": data.Name})
},
})
// 启动httpServer
restServer.Start()
}
中间件
go-zero 内置支持多种功能的中间件,其列表为:
AuthorizeHandler:鉴权管理中间件BreakerHandler:熔断中间件ContentSecurityHandler:内容安全中间件CryptionHandler:解密中间件GunzipHandler:压缩管理中间件LogHandler:日志中间件,每次http请求都会输出对应的请求日志,默认会在日志content中显示statuc_code、uri、host:port、user-agent等信息。MaxBytesHandler:ContentLength管理中间件MaxConnsHandler:限流中间件,限制http最大请求并发数,当并发请求超过指定数值时会返回http.StatusServiceUnavailable错误码。MetricHandler:指标统计中间件RecoverHandler:panic恢复中间件SheddingHandler:负载监控中间件TimeoutHandler:超时中间件TraceHandler:链路追踪中间件,默认集成了Opentelemetry进行链路追踪,如果想把追踪信息上报到jaeger,只需要修改配置即可。链路请求信息默认会携带host、method、route、status_code等信息。需要提前启动jaeger服务Name: hello Host: 127.0.0.1 Port: 4001 Middlewares: Metrics: true Telemetry: Name: hello # jaeger 上报地址 Endpoint: http://127.0.0.1:14268/api/traces Batcher: jaeger Sampler: 1.0
以上中间件默认启用
// 中间件配置
type MiddlewaresConf struct {
Trace bool `json:",default=true"` // 是否启用链路追踪中间件
Log bool `json:",default=true"` // ...日志中间件
Prometheus bool `json:",default=true"` // ...普罗米修斯指标中间件
MaxConns bool `json:",default=true"` // ...限流中间件
Breaker bool `json:",default=true"` // ...熔断中间件
Shedding bool `json:",default=true"` // ...负载监控中间件
Timeout bool `json:",default=true"` // ...超时中间件
Recover bool `json:",default=true"` // ...panic恢复中间件
Metrics bool `json:",default=true"` // ...指标统计中间件
MaxBytes bool `json:",default=true"` // ...ContentLength 管理中间件
Gunzip bool `json:",default=true"` // ...压缩管理中间件
}
**如果需要加载自定义中间件,只需要创建一个 **Middleware 类型的函数并传递给 Server 的 Use 函数即可。
// 一个简单的基础认证中间件
func BasicAuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
username, password, ok := request.BasicAuth()
if !ok {
writer.WriteHeader(http.StatusUnauthorized)
return
}
if username != "admin" || password != "password" {
writer.WriteHeader(http.StatusUnauthorized)
_, _ = writer.Write([]byte("用户名或密码错误"))
}
next(writer, request)
}
}
func main() {
restServer := rest.MustNewServer(rest.RestConf{
Host: "127.0.0.1",
Port: 4001,
})
// 通过 use 函数使用中间件
restServer.Use(BasicAuthMiddleware)
}
JWT 认证
go-zero 简单化了 jwt 在项目中的引用,内置了其解密和验证功能 ,只需要通过可选参数控制是否启用 JWT 即可。
func main() {
restServer := rest.MustNewServer(rest.RestConf{
Host: "127.0.0.1",
Port: 4001,
})
// 通过 rest.WithJwt 为当前地址开启 jwt 验证,在验证时使用传入的字符串做为 secret
restServer.AddRoute(rest.Route{
Method: http.MethodGet,
Path: "/:name",
Handler: func(writer http.ResponseWriter, request *http.Request) {
data := &RequestData{}
if err := httpx.Parse(request, data); err != nil {
httpx.Error(writer, errors.New("request params not"))
return
}
httpx.OkJson(writer, map[string]string{"name": data.Name})
},
}, rest.WithJwt("hello world"))
defer restServer.Stop()
restServer.Start()
}
**默认情况下 **JWT 验证失败会返回 401 状态码和错误信息,如果需要自定义处理 JWT 验证失败的逻辑,添加一个自定义的处理函数即可。
func main() {
// rest.WithUnauthorizedCallback 用户处理 jwt 验证失败
restServer := rest.MustNewServer(rest.RestConf{
Host: "127.0.0.1",
Port: 4001,
}, rest.WithUnauthorizedCallback(func(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusUnauthorized)
// 自定义验证失败的返回信息
}))
}
错误处理
**处理 **HTTP 的错误,根据不同的错误类型响应不同的状态码和错误消息
type RequestData struct {
Name string `path:"name"`
}
func main() {
restServer := rest.MustNewServer(rest.RestConf{
Host: "127.0.0.1",
Port: 4001,
}, rest.WithUnauthorizedCallback(func(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusUnauthorized)
}))
// 自定义错误函数,此处理仅在使用 httpx.Error 时有效
httpx.SetErrorHandler(func(err error) (int, any) {
switch e := err.(type) {
case *errors.CodeMsg:
return e.Code, map[string]any{
"code": e.Code,
"message": e.Msg,
}
default:
return http.StatusInternalServerError, map[string]any{
"code": http.StatusInternalServerError,
"message": "服务器内部错误",
}
}
})
restServer.AddRoute(rest.Route{
Method: http.MethodGet,
Path: "/:name",
Handler: func(writer http.ResponseWriter, request *http.Request) {
data := &RequestData{}
if err := httpx.Parse(request, data); err != nil {
// 为请求端返回错误状态码和错误消息
httpx.Error(writer, errors.New(http.StatusBadRequest, "request params not"))
return
}
httpx.OkJson(writer, map[string]string{"name": data.Name})
},
}, rest.WithJwt("hello world"))
defer restServer.Stop()
restServer.Start()
}
gRPC
Server
go-zero 提供了 gRPC Server 能力,内置了多种功能支持:
- 服务发现(
etcd作为注册中心) - 负载均衡(
p2c算法) - 节点亲和性处理
- 多节点直连支持
- 超时处理
- 限流、熔断
- 鉴权
- 异常捕获
gRPC server 使用 zrpc.RpcServerConf 作为通用的服务端配置,用于控制 rpc 的监听地址、服务注册发现、链路追踪、日志和中间件能功能。
type RpcServerConf struct {
// 基础服务配置
service.ServiceConf
// 监听地址
ListenOn string
Etcd discov.EtcdConf `json:",optional,inherit"`
// 是否开启Auth
Auth bool `json:",optional"`
Redis redis.RedisKeyConf `json:",optional"`
// 是否Strict模式,Strict模式下错误都被认为是Auth失败
StrictControl bool `json:",optional"`
// 超时时间 ms
Timeout int64 `json:",default=2000"`
// 降载阈值 可设置范围 0-1000
CpuThreshold int64 `json:",default=900,range=[0:1000)"`
// 是否开启健康检查
Health bool `json:",default=true"`
Middlewares ServerMiddlewaresConf
MethodTimeouts []MethodTimeoutConf `json:",optional"`
}
// Etcd 配置
type EtcdConf struct {
Hosts []string // etcd集群地址
Key string // 服务的唯一 key,用于服务注册发现,不同服务不可重复
ID int64 `json:",optional"`
User string `json:",optional"` // etcd 用户
Pass string `json:",optional"` // etcd 密码
CertFile string `json:",optional"` // 证书文件
CertKeyFile string `json:",optional=CertFile"` // 私钥文件
CACertFile string `json:",optional=CertFile"` // CA证书文件
InsecureSkipVerify bool `json:",optional"`
}
// 当使用 redis 管理 rpc 认证时需要配置的参数,如果不使用redis进行rpc认证,可以不配置
type RedisConf struct {
Host string // redis地址
Type string `json:",default=node,options=node|cluster"` // 类型 node:单机 cluster 集群
Pass string `json:",optional"` // 密码
Tls bool `json:",optional"` // 使用使用tls
Key string // redis key
}
// 中间件配置
type ServerMiddlewaresConf struct {
Trace bool `json:",default=true"` // 是否启用链路追踪中间件
Recover bool `json:",default=true"` // ...异常捕获中间件
Stat bool `json:",default=true"` // ...统计中间件
StatConf StatConf `json:",optional"` // 统计配置
Prometheus bool `json:",default=true"` // ...Prometheus中间件
Breaker bool `json:",default=true"` // ...熔断中间件
}
使用示例
**创建一个 **proto 文件
syntax = "proto3";
package book;
// pb.go 和 grpc_bp.go 存放的目录
option go_package=".\\book";
message Empty {}
message Book {
uint64 bookId = 1;
string bookName = 2;
double price = 3;
string summary = 4;
}
message BookInfoReq {
uint64 bookId = 1;
}
message BookListResp {
repeated Book books = 1;
}
service BookService {
rpc BookInfo(BookInfoReq) returns (Book);
rpc BookList(Empty) returns (BookListResp);
}
**使用 **goctl 工具生成代码,生成完毕后,client 客户端代码存放在当前目录的 <serviceName> 目录下,server 端代码存放在 internal/server 目录下。
$ goctl rpc protoc greet.proto --go_out=. --go-grpc_out=. --zrpc_out=.
对于微服务的服务间调用,go-zero 支持 etcd 服务注册和直连模式,go-zero 会自动识别。如果需要使用 etcd 服务注册,需要将 etcd 的配置添加到 etc 目录中当前服务的配置文件中。
Name: book.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:
- 127.0.0.1:2379
# 当前rpc服务的key,和name保持相同
Key: book.rpc
**待实现的业务代码位于 **logic 目录下,在没有服务分组的情况下,每个 rpc 服务都会创建一个同名文件,以 BookInfo 为例,生成的文件名为 bookinflogic.go。
type BookInfoLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewBookInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BookInfoLogic {
return &BookInfoLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// 实现业务处理逻辑
func (l *BookInfoLogic) BookInfo(in *book.BookInfoReq) (*book.Book, error) {
// todo: add your logic here and delete this line
return &book.Book{}, nil
}
**当需要在 **logic 层调用其它层或者需要创建对象时,所依赖的对象必须在 svc/servicecontext 中显式注入,所有的 rpc 服务都依赖于这一个 context。
// 一个简单的 book data层
type BookRepo struct {
state map[uint64]book.Book
}
func NewBookRepo() *BookRepo {
return &BookRepo{
state: map[uint64]book.Book{
1: {
BookId: 1,
BookName: "Go从入门到实践",
Price: 10.11,
Summary: "Go入门书籍",
},
},
}
}
func (self *BookRepo) GetBookById(bookId uint64) *book.Book {
item, ok := self.state[bookId]
if ok {
return &item
}
return nil
}
func (self *BookRepo) ListBook() []book.Book {
books := make([]book.Book, len(self.state))
for _, value := range self.state {
books = append(books, value)
}
return books
}
**在 **ServiceContext 中以结构体参数的形式显式注入
type ServiceContext struct {
Config config.Config
BookRepo *data.BookRepo
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
BookRepo: data.NewBookRepo(),
}
}
**然后,便可在 **logic 层使用注入的依赖来实现具体的业务逻辑
func NewBookInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BookInfoLogic {
return &BookInfoLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (self *BookInfoLogic) BookInfo(in *book.BookInfoReq) (*book.Book, error) {
// 调用 svcContext 中注入的依赖
return self.svcCtx.BookRepo.GetBookById(in.BookId), nil
}
中间件
go-zero 的 zrpc 包中内置了很多常用的中间件,包括
- 鉴权中间件:
StreamAuthorizeInterceptor|UnaryAuthorizeInterceptor - 熔断中间件:
StreamBreakerInterceptor|UnaryBreakerInterceptor - 指标统计中间件:
UnaryPrometheusInterceptor - 异常捕获中间件:
StreamRecoverInterceptor|UnaryRecoverInterceptor - 服务降级中间件:
UnarySheddingInterceptor - 时长统计中间件:
UnaryStatInterceptor - 请求超时中间件:
UnaryTimeoutInterceptor - 链路追踪中间件:
StreamTraceInterceptor|UnaryTraceInterceptor
除了链路追踪、指标统计、时长统计、异常捕获、熔断中间件可以通过配置来开启或关闭,其余中间件都默认开启。
如果想要添加自定义的请求拦截逻辑,也可以添加自定义中间件
// DemoUnaryInterceptor 普通请求中间件
func DemoUnaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
// 自定义的中间件处理逻辑
logx.Infof("Method: %s", info.FullMethod)
return handler(ctx, req)
}
// DemoStreamInterceptor 流式请求中间件
func DemoStreamInterceptor(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
// 自定义的中间件处理逻辑
logx.Info(info.IsServerStream, info.IsClientStream)
return handler(srv, stream)
}
func main() {
// ...
ctx := svc.NewServiceContext(c)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
book.RegisterBookServiceServer(grpcServer, server.NewBookServiceServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})
// 添加流式请求中间件
s.AddStreamInterceptors(DemoStreamInterceptor)
// 添加普通请求中间件
s.AddUnaryInterceptors(DemoUnaryInterceptor)
// ...
}
Client
gRPC client 的开发主要是修改配置文件,具体的客户端代码已经由 gRPC 生成好了。
type RpcClientConf struct {
// Etcd 注册中心配置 和 server 一致
Etcd discov.EtcdConf `json:",optional,inherit"`
// RPC server地址列表,直连模式下配置
Endpoints []string `json:",optional"`
// 域名解析地址
Target string `json:",optional"`
// rpc 认证的app名称,仅在rpc server开启认证时使用
App string `json:",optional"`
// rpc认证的token,同上
Token string `json:",optional"`
// 是否阻塞模式
NonBlock bool `json:",optional"`
// 超时时间
Timeout int64 `json:",default=2000"`
// 保活时间
KeepaliveTime time.Duration `json:",default=20s"`
// 中间件配置
Middlewares ClientMiddlewaresConf
}
type ClientMiddlewaresConf struct {
// 链路追踪
Trace bool `json:",default=true"`
// 异常恢复
Recover bool `json:",default=true"`
// 统计
Stat bool `json:",default=true"`
// 统计配置
StatConf StatConf `json:",optional"`
Prometheus bool `json:",default=true"`
// 熔断
Breaker bool `json:",default=true"`
}
// 直连模式,通过地址访问
conn := zrpc.MustNewClient(zrpc.RpcClientConf{
Endpoints: []string{"127.0.0.1:8080"},
})
// 创建 rpc client 并调用对应的接口
client := book.NewBookServiceClient(conn.Conn())
bookInfo, err := client.BookInfo(context.Background(), &book.BookInfoReq{
BookId: 1,
})
// 通过 etcd 服务注册调用
conn := zrpc.MustNewClient(zrpc.RpcClientConf{
Etcd: discov.EtcdConf{
Hosts: []string{"127.0.0.1:2379"},
Key: "book.rpc",
},
})
client := book.NewBookServiceClient(conn.Conn())
消息队列
go-zero 在 kafka-go 的基础上封装了 go-queue ,对 kafka 消息队列提供了更加便捷的支持。
$ go get github.com/zeromicro/go-queue@latest
生产者
**创建 **kafka 的生产者 pusher 主要的参数需要 kafka 的地址列表和 topic 的分区名称。此外,NewPusher 的第三个参数是几个可选参数:
chunkSize:kq client在提交消息时是批量提交,此参数指定批量块的大小flushInterval:提交的间隔时间,间隔时间触发后即使未达到chunkSize的大小也会提交消息
// addrs kafka 的集群地址 topic 需要发送的 topic 名称
func NewPusher(addrs []string, topic string, opts ...PushOption) *Pusher {}
**创建成功后便可通过 **KPush , Push, PushWithKey 这三个函数向 kafka 发送消息
// 发送消息 需要传递 cxt, key, 和消息值
// kafka接收到消息后会通过key将消息存入对应的分区
func (p *Pusher) KPush(ctx context.Context, k, v string) error {}
// 发送消息 无需 key,默认的key为时间戳
func (p *Pusher) Push(ctx context.Context, v string) error {}
// 发送消息 和 KPush 同理
func (p *Pusher) PushWithKey(ctx context.Context, key, v string) error {}
// 最简单的消息发送示例
if err := pusher.Push(context.Background(), message); err != nil {
logx.Errorf("kafka 消息发送失败,error: %s, message: %s", err, message)
}
消费者
**创建消费者依赖于 **kq.KqConf 配置
type KqConf struct {
service.ServiceConf
Brokers []string // kafka集群地址
Group string // 消费者群组名称
Topic string // 需要消费的topic名称
CaFile string `json:",optional"` // ca证书
Offset string `json:",options=first|last,default=last"` // 读取时的偏移量设置 first:头 last:尾
Conns int `json:",default=1"` // 消费者数量
Consumers int `json:",default=8"` // 从kafka获取信息写入进程的channel数量
Processors int `json:",default=8"` // 消费消息的goroutine数量
MinBytes int `json:",default=10240"` // 一次返回的最小字节数,如果不够就等待
MaxBytes int `json:",default=10485760"` // 一次返回的最大字节数
Username string `json:",optional"` // kafka的用户
Password string `json:",optional"` // kafka的密码
ForceCommit bool `json:",default=true"`
CommitInOrder bool `json:",default=false"`
}
**实现消费者依赖于 **ConsumeHandler 接口,需要实现该接口以读取消息并自定义消费逻辑
type DemoConsumeHandler struct{}
func NewDemoConsumeHandler() kq.ConsumeHandler {
return &DemoConsumeHandler{}
}
// 消费消息,会传入ctx和消息的key,value
func (self *DemoConsumeHandler) Consume(ctx context.Context, key, value string) error {
logx.Infof("接收 kafka 消息成功,key: %s, value: %s", key, value)
return nil
}
**实现了消息的消费逻辑之后,需要将消费者添加到 **ServiceGroup 中并启动等待消息消费。
// 创建消费者
queue := kq.MustNewQueue(kq.KqConf{
Brokers: brokers,
Group: "consumer",
Topic: topic,
Offset: "last",
Consumers: 1,
Processors: 1,
}, NewDemoConsumeHandler())
// 创建一个服务组
serviceGroup := service.NewServiceGroup()
defer serviceGroup.Stop()
// 将消费者添加到服务组中,可以添加多个
serviceGroup.Add(queue)
// 启动,等待消息并进行消费
serviceGroup.Start()
MustNewQueue 在初始化时也有几个可选参数:
commitInterval:提交给kafka borker的间隔时间,默认1squeueCapacity:kafka内部队列长度maxWait:从kafka批量获取数据时,当数据量不够等待新数据到来的最大时间metrics:上报每个消息的消费时间,默认会初始化,一般不需要指定
延时队列
延时队列在一些超时的场景会经常使用,例如订单未支付关闭等。go-queue 也实现了延时队列 dq ,其底层使用 beanstalkd。
生产者
type (
// beanstalkd 节点配置
Beanstalk struct {
Endpoint string
Tube string
}
DqConf struct {
// 多个 beanstalkd 节点配置
Beanstalks []Beanstalk
// redis配置,本身不需要依赖redis 主要使用redis的Setnx进行去重 防止重复消费
Redis redis.RedisConf
}
)
**创建一个生产者,需要传入 **beanstalkd 的节点配置
// 实际使用中 此配置应该读取配置文件
beanstalks := []dq.Beanstalk{
{
Endpoint: "127.0.0.1:7771",
Tube: "tube1",
},
}
pusher := dq.NewProducer(beanstalks)
而后便可发送延迟消息或者在指定时间消费的消息
// 指定延迟时间的消息,在 1 分钟后执行
delay, err := pusher.Delay([]byte("timeout message"), 1*time.Minute)
if err != nil {
logx.Errorf("延迟消息发送失败,error: %s", err)
}
logx.Infof("resp: %s", delay)
// 发送在指定时间执行的消息,传入具体的 time 对象
resp, err := pusher.At([]byte("message"), time.Now().Add(24*time.Hour))
消费者
**消费者和生产者的配置相同,但是需要传入 **redis 配置,同样也是先初始化一个消费者
// 配置使用完整的 dqConf Beanstalks和生产者相同 但是需要传入redis配置
consumer := dq.NewConsumer(dq.DqConf{
Redis: redis.RedisConf{
Host: "127.0.0.1:6379",
Type: "node",
},
Beanstalks: beanstalks,
})
**调用消费者的 **Consume 方法即可定义消息的消费逻辑,非常方便
consumer.Consume(func(body []byte) {
logx.Infof("接收到 beanstalkd 消息,message:%s", body)
})
Redis
go-zero 提供了 redis 组件,其底层基于 redis-go 。但是没有提供 db选择,默认使用 db0 ,主要因为 redis cluster 也仅支持 db0 。
conf := redis.RedisConf{
Host: "127.0.0.1:6379",
Type: "node",
}
// 创建一个redisClient
redisClient, err := redis.NewRedis(conf)
if err != nil {
log.Fatalf("创建 redis 客户端失败, error: %s", err)
}
// 最简单的 set 操作
if err = redisClient.Set("demo", "hello go-zero"); err != nil {
logx.Errorf("添加redis key失败,error:%s", err)
}
// 最简单的 get 操作
value, err := redisClient.Get("demo")
if err != nil {
logx.Errorf("获取redis key失败,error:%s", err)
} else {
logx.Info("redis key获取成功,value:", value)
}
**除了最简单的 **get|set 操作,go-zero 的 redis 组件提供了绝大部分 redis 操作的封装,更便于使用。
Redis 锁
go-zero 的 redis 组件集成了 redis 锁的操作,可以非常方便的使用 redis 创建分布式锁。
redisClient, err := redis.NewRedis(conf)
if err != nil {
log.Fatalf("创建 redis 客户端失败, error: %s", err)
}
// 先创建 redis 客户端,再通过客户端和锁的key创建一个锁
redisLock := redis.NewRedisLock(redisClient, "lock")
redis.RedisLock 包含 5 个常用方法:
SetExpire(expire):设置锁的过期时间,单位: 秒Acquire:获取锁,会返回bool, err,bool表示锁是否成功获取Release:释放锁,同样会返回bool, errAcquireCtx:可以传递context的获取锁操作ReleaseCtx:可以传递context的释放锁操作
redisLock := redis.NewRedisLock(redisClient, "lock")
// 设置过期时间 60 秒
redisLock.SetExpire(60)
// 尝试获取锁
isAcquire, err := redisLock.Acquire()
switch {
case err != nil:
logx.Errorf("获取 redis 锁异常,error:%s", err)
return
case !isAcquire:
logx.Errorf("获取 redis 锁失败,锁已被占用")
return
case isAcquire:
// 业务处理完成后释放锁
defer redisLock.Release()
logx.Info("获取 redis 锁成功")
// 业务逻辑
}
Redis 监控
redis 组件内置了两个用于监控相关的 metric
metricReqDur:用于对redis操作命令的耗时监控metricReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{ Namespace: namespace, Subsystem: "requests", Name: "duration_ms", Help: "redis client requests duration(ms).", Labels: []string{"command"}, Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000, 2500}, }metricReqErr:用于对redis操作的错误监控metricReqErr = metric.NewCounterVec(&metric.CounterVecOpts{ Namespace: namespace, Subsystem: "requests", Name: "error_total", Help: "redis client requests error count.", Labels: []string{"command", "error"}, }
gRPC Gateway
go-zero 的 gRPC Geteway 用于将 REST HTTP 请求转换为 gRPC 请求,其大致的工作流程如下:
- **从 **
proto文件中解析gRPC服务的定义 - **从配置文件中解析出 **
gRPC服务的HTTP映射规则 - **基于服务定义和映射规则,生成 **
gRPC服务的HTTP处理器 - **启动服务器,将 **
HTTP请求转换为gRPC请求 - **将 **
gRPC响应转换为HTTP响应并返回
gRPC Gateway 的定义也是基于配置来实现的,可以完全基于配置文件实现请求转换。
type (
// 网关配置
GatewayConf struct {
// http 服务配置
rest.RestConf
Upstreams []Upstream
}
// http 映射配置
RouteMapping struct {
// 映射的HTTP方法,仅限 GET, POST, PUT, DELETE.
Method string
// 映射的HTTP请求路径
Path string
// 访问的gRPC服务路径,package.service/method
RpcPath string
}
// gRPC服务配置
Upstream struct {
// 服务名称
Name string `json:",optional"`
// gRPC服务配置
Grpc zrpc.RpcClientConf
// proto文件列表
ProtoSets []string `json:",optional"`
// 服务映射,不填则默认映射所有gRPC服务
Mappings []RouteMapping `json:",optional"`
}
)
**有两种方式可以使用 **gRPC Gateway ,其分别是:
-
protoDescriptor:通过protoc解析proto生成pb文件,然后在gateway中使用该pb文件做rest-grpc的规则映射。 **创建 **proto文件,定义servicesyntax = "proto3"; package hello; option go_package = ".\\hello"; message Request {} message Response { string message = 1; } service Hello { rpc hello(Request) returns (Response); }**在同级目录下创建一个 **
gateway目录,用于存放生成的pb文件# 通过 proto 生成 pb 文件 $ protoc --descriptor_set_out=gateway/hello.pb hello.proto**再通过 **
proto初始化rpc服务,完善业务逻辑后修改配置文件并启动# 使用 goctl 工具初始化 rpc 服务 $ goctl rpc protoc hello.proto --go_out=server --go-grpc_out=server --zrpc_out=server// logic/hellologic.go // 完成业务逻辑 func (self *HelloLogic) Hello(in *hello.Request) (*hello.Response, error) { return &hello.Response{ Message: "hello", }, nil }**进入 **
gateway目录,创建etc/gateway.yaml配置文件,在配置文件中定义HTTP和RPC服务的映射规则Name: hello-gateway Host: localhost Port: 8888 Upstreams: - Grpc: # rpc服务的地址 Target: localhost:8080 # pb文件列表 ProtoSets: - hello.pb # 映射规则列表 Mappings: - Method: get Path: /hello # 映射的rpc方法 # 命名规则 <文件名称>./ RpcPath: hello.Hello/hello **新建一个 **
main函数文件,启动gateway网关访问对应的http地址即可// gateway.go var configFile = flag.String("f", "etc/gateway.yaml", "config file") func main() { flag.Parse() var c gateway.GatewayConf conf.MustLoad(*configFile, &c) gw := gateway.MustNewServer(c) defer gw.Stop() gw.Start() } -
grpcReflection:通过grpc反射做rest-grpc的规则映射,只在dev和test环境可用。
流量治理
限流
限流用于控制服务的并发调用量,一般有节点限流、集群限流(将限流数量对集群节点数求平均值,本质还是节点限流)和分布式限流。
**默认的限流实现只需要修复服务配置中的 **MaxConns 选项即可。配置完成后,当并发访问数量超过了限流数量时,超过的请求都会被限流,服务器直接返回 503 状态码。
gRPC 服务一般用于内网,默认情况下没有限流配置。如若确实需要限流,可以使用自定义中间件来实现。
// 自定义 grpc 普通请求限流中间件
func NewGrpcUnaryLimitInterceptors(maxCons uint32) grpc.UnaryServerInterceptor {
var conCount uint32 = 0
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
// 当前请求数量小于限制数量
// 增加请求数量,正常处理请求
if atomic.LoadUint32(&conCount) < maxCons {
atomic.AddUint32(&conCount, 1)
defer atomic.AddUint32(&conCount, -1)
return handler(ctx, req)
} else {
// 如果达到限流阈值 输出错误日志 直接返回错误状态码
logx.Errorf("concurrent connections over %d, rejected with code %d", atomic.LoadUint32(&conCount), http.StatusServiceUnavailable)
return nil, status.Error(codes.Unavailable, "concurrent connections over limit")
}
}
}
熔断
熔断用于保护服务调用链路中不会压垮。当某个服务出现异常时,熔断器会将拒绝调用该服务的所有请求,从而保护其它服务不被压垮。
go-zero 内置了熔断器组件 breaker.Breaker ,底层基于滑动窗口进行数据采集,默认以 10s 为一个窗口,单个窗口有 40 个桶,采集的数据通过 google 的 sre 算法判断是否开启熔断。
**在 **breaker.Breaker 中,提供了四个方法用于熔断,分别对应不同的场景:
Do:按照错误率来判断服务是否可用,不支持指标自定义和错误回调// 自定义错误类型 type BreakerError struct { Status int } func (self *BreakerError) Error() string { return fmt.Sprintf("Error Status: %d", self.Status) } func main() { for i := 0; i <= 100; i++ { // 会以传入的字符串做为唯一key创建breaker实例 if err := breaker.Do("demo", func() error { if i%4 == 0 { return &BreakerError{ Status: 500, } } return nil }); err != nil { fmt.Println(err) } } }DoWithAcceptable:支持自定义的采集指标,可以自主控制哪种情况需要假如熔断指标采集窗口for i := 0; i <= 100; i++ { if err := breaker.DoWithAcceptable("demo", func() error { if i%4 == 0 { return &BreakerError{Status: 500} } else if i%5 == 0 { return &BreakerError{Status: 400} } return nil }, func(err error) bool { // 状态码不等于500都认为正常,不记录 be, ok := err.(BreakerError) if ok { return be.Status != 500 } return false }); err != nil { fmt.Println(err) } }DoWithFallback:支持熔断回调,不支持指标自定义for i := 0; i <= 100; i++ { if err := breaker.DoWithFallback("demo", func() error { if i%4 == 0 { return &BreakerError{Status: 500} } else if i%5 == 0 { return &BreakerError{Status: 400} } return nil }, func(err error) error { // 自定义错误转换 return errors.New(err.Error()) }); err != nil { fmt.Println(err) } }DoWithFallbackAcceptable:支持熔断回调和指标自定义
**在实际的使用中,不需要对请求单独进行熔断处理,功能已经集成到了 **go-zero 中。HTTP 请求以请求方法 + 路由做为统计 key,用 500 状态码做为错误采集指标进行统计;gRPC 客户端以 rpc 方法名做为统计 key,gRPC 错误码为 codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss, codes.Unimplemented,做为错误采集指标进行统计。gRPC 服务端和客户端一致,只是错误采集指标变成了所有 gRPC 错误。
负载均衡
负载均衡用于将请求均匀的分发给集群中的不同节点,常见的算法有:
- 轮询:循环重复的将请求依次分发给每个节点,适用于节点处理能力相同的情况。
- 权重:将请求优先分配给权重更高的节点,节点的权重可以单独设置,更高权重的节点处理更多的请求。
- 最少连接数:优先将请求分配给连接数最少的节点,适用于单次请求连接时间较长的场景。
- IP散列:对请求的源
IP继续哈希运算,根据结果将请求分配给对应的节点,可以确保同一源IP始终分配给同一节点。 - P2C:从节点列表中随机选择两个节点,根据负载情况选择负载较小的节点,避免最劣选择。
go-zero 中,负载均衡算法主要应用在 gRPC 客户端,且采用的 P2C 算法。
监控
健康检查
go-zero 启动的服务,默认已经集成了了健康检查,默认端口和 path 为 6060 ,/healthz ,访问健康检查地址会返回 OK。
如果需要关闭或者修改端口、地址,可以通过修改配置实现。
srv := rest.MustNewServer(rest.RestConf{
Port: 8002,
ServiceConf: service.ServiceConf{
DevServer: devserver.Config{
// 开启健康检查
Enabled: true,
// 健康检查服务端口
Port: 8080,
// 健康检查服务地址
HealthPath: "/ping",
},
},
})
也可以使用配置文件
Name: user-api
Host: 0.0.0.0
Port: 8002
DevServer:
Enabled: true
Port: 8080
HealthPath: "/ping"
链路追踪
go-zero 基于 OpenTelemetry 集成了链路追踪,支持 jaeger/zipkin 只需修改配置即可启用。
type Config struct {
Name string `json:",optional"`
Endpoint string `json:",optional"` // trace信息上报的url
Sampler float64 `json:",default=1.0"` // 采样率
Batcher string `json:",default=jaeger,options=jaeger|zipkin|otlpgrpc|otlphttp"`
}
api 链路追踪
Name: user-api
Host: 0.0.0.0
Port: 8002
Mode: dev
#链路追踪
Telemetry:
Name: user-api
Endpoint: http://jaeger:14268/api/traces
Sampler: 1.0
Batcher: jaeger
rpc 链路追踪
Name: user-rpc
ListenOn: 0.0.0.0:9002
Mode: dev
#链路追踪
Telemetry:
Name: user-rpc
Endpoint: http://jaeger:14268/api/traces
Sampler: 1.0
Batcher: jaeger
指标监控
go-zero 中默认使用 prometheus 进行指标收集,默认端口和 path 为 6470、/metrics。可以通过 DevServer 配置指标监控 Path 和是否开启。
DevServer:
Enabled: true
Port: 6470
// 修改指标监控的地址
MetricsPath: /metrics
// 是否启用指标监控
EnableMetrics: false
**默认集成的 **prometheus 指标:
RPC Server:| 名称 | 标签 | 说明 | | ----------------------------------- | -------------------- | --------------------------------- | |rpc_server_requests_duration_ms|method|Histogram,耗时统计单位为毫秒 | |rpc_server_requests_code_total|method、code|Counter,错误码统计 |RPC client:和RPC Server一致HTTP Server:| 名称 | 标签 | 说明 | | ------------------------------------ | ------------------ | --------------------------------- | |http_server_requests_duration_ms|path|Histogram,耗时统计单位为毫秒 | |http_server_requests_code_total|path、code|Counter,错误码统计 |MySQL:| 名称 | 标签 | 说明 | | ----------------------------------- | ---------------------- | --------------------------------- | |sql_client_requests_duration_ms|command|Histogram,耗时统计单位为毫秒 | |sql_client_requests_error_total|command、error|Counter,错误统计 |Redis:| 名称 | 标签 | 说明 | | ------------------------------------- | ---------------------- | --------------------------------- | |redis_client_requests_duration_ms|command|Histogram,耗时统计单位为毫秒 | |redis_client_requests_error_total|command、error|Counter,错误统计 |
CLI工具
goctl 是 go-zero 配套的代码生成脚手架工具,集成了 HTTP 服务、RPC 服务 、数据库 model、k8s、docker 等生成功能。
| 指令 | 指令 | 指令 | 说明 |
|---|---|---|---|
goctl |
api |
dart |
生成 dart代码 |
doc |
文档生成 | ||
format |
api文件格式化 |
||
go |
HTTP服务生成 |
||
kt |
生成 kotlin代码 |
||
new |
快速创建 HTTP服务 |
||
plugin |
使用 goctl插件 |
||
ts |
生成 Typescript代码 |
||
validate |
api文件格式校验 |
||
bug |
Github issue上报 |
||
completion |
bash |
bash自动补全设置 |
|
fish |
|||
powershell |
|||
zsh |
|||
docker |
Dockerfile生成 |
||
env |
check |
goctl环境检测 |
|
install |
安装 goctl依赖环境 |
||
kube |
deploy |
k8s yaml文件生成 |
|
migrate |
go-zero项目迁移 |
||
model |
mongo |
MongoDB代码生成 |
|
mysql |
MySQL代码生成 |
||
pg |
PostgreSQL代码生成 |
||
quickstart |
快速启动并生成服务 | ||
rpc |
new |
gRPC服务生成 |
|
protoc |
gRPC代码生成 |
||
template |
创建 proto文件 |
||
template |
clean |
模板清理 | |
init |
模板文件初始化 | ||
revert |
模板回滚 | ||
update |
模板更新 | ||
upgrade |
goctl版本更新 |






0 条评论