go-zero 框架 · 快速上手

一站式 Go 微服务框架:内置服务治理,开箱即用的代码生成能力。本篇先说清「框架到底给了你什么能力」,再配上可直接抄的代码示例。

rest / zrpc 双协议 限流 · 熔断 · 降载 goctl 代码生成 etcd 服务发现
框架能力总览 HTTP API RPC 服务 限流 熔断 缓存 日志监控 中间件

go-zero 到底给你什么能力

go-zero 不是「又一个 Web 框架」,而是一整套「工程化 + 服务治理」的解决方案。下面这张图把它的能力分层画出来。

goctl 代码生成(api / rpc / model / docker / kube) 一条命令产出可运行的服务骨架,约定优于配置 rest HTTP 服务 路由 / 参数绑定 / JWT / 中间件 zrpc RPC 服务 gRPC + etcd 发现 + 负载均衡 服务治理(core 包) 限流periodlimit 熔断breaker 自适应降载shedding 缓存cache store 日志/监控logx / metric

六大核心能力一句话说清:

🚀
双协议服务
rest 写 HTTP 接口,zrpc 写内部 RPC,两者可共存于同一工程。
🛡️
服务治理
限流、熔断、自适应降载、超时控制开箱即用,无需自己拼装。
🧰
代码生成
goctl 把 .api / .proto / SQL 一键生成可运行代码,减少样板。
🔍
服务发现
基于 etcd 自动注册与发现,配合负载均衡调用对端。
📊
可观测性
logx 结构化日志 + Prometheus 指标 + 链路追踪一体化。
💾
数据访问
model 层自动生成 CRUD,内置缓存与防穿透(singleflight)。

用 rest 写 HTTP 服务

go-zero 推荐「先写 .api 描述文件,再用 goctl 生成」的方式。下面三步跑通一个登录接口。

第 1 步:定义 .api 文件(接口契约)

用一套极简 DSL 描述路由、入参、出参与 handler 名称。这是 go-zero 的「源代码」。

syntax = "v1"

type LoginReq {
    Username string `json:"username"`
    Password string `json:"password"`
}

type LoginResp {
    Token string `json:"token"`
}

service user-api {
    @handler Login
    post /user/login (LoginReq) returns (LoginResp)
}

第 2 步:goctl 生成代码

一条命令生成 handler、logic、svc、路由注册与 yaml 配置。

# 生成 HTTP 服务骨架到当前目录
goctl api go -api user.api -dir .

生成后你会拿到 internal/handler/loginhandler.gointernal/logic/loginlogic.go 等文件,业务逻辑只需填在 logic 里。

第 3 步:在 logic 里写业务(自动生成的样子)

func (l *LoginLogic) Login(req *types.LoginReq) (*types.LoginResp, error) {
    // 这里写你的业务:查库、校验密码、签发 token
    if req.Username == "" || req.Password == "" {
        return nil, errors.New("参数缺失")
    }
    token := signToken(req.Username)
    return &types.LoginResp{Token: token}, nil
}
约定:所有业务写在 logic 层,handler 只做请求/响应适配,依赖(如 DB、RPC 客户端)统一放进 svc.ServiceContext

手写方式(不使用 api 文件,直接注册路由)

如果不想用代码生成,也能像普通 HTTP 框架那样直接写:

func main() {
    var c config.Config
    conf.MustLoad("etc/user-api.yaml", &c)
    server := rest.MustNewServer(c.RestConf)
    defer server.Stop()

    server.AddRoutes([]rest.Route{{
        Method:  http.MethodGet,
        Path:    "/ping",
        Handler: func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("pong"))
        },
    }})
    server.Start()
}

用 zrpc 写内部 RPC

zrpc 基于 gRPC,但帮你把 etcd 注册发现、超时、负载均衡都默认接好了。定义 proto,生成代码,填 logic 即可。

定义 proto

syntax = "proto3";
package user;

service User {
  rpc Login(LoginRequest) returns (LoginResponse);
}

message LoginRequest {
  string username = 1;
  string password = 2;
}
message LoginResponse {
  string token = 1;
}

生成并调用

# 生成 RPC 代码(含 zrpc 客户端封装)
goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.

调用端直接用生成的 client 即可,无需手写 gRPC 连接与拨号:

client := user.NewUser(zrpc.MustNewClient(c.UserRpc))
resp, err := client.Login(ctx, &user.LoginRequest{
    Username: "alice",
    Password: "secret",
})
zrpc 客户端默认从 etcd 拉取可用节点并做负载均衡,配置里写 etcd 地址即可,调用方无感知。

限流:periodlimit 与 tokenlimit

防止突发流量打垮服务。go-zero 提供两种限流器,底层依赖 Redis 共享计数。

periodlimit(固定窗口)

限制「每个时间窗口内最多 N 次」,适合简单配额。例:每 1 秒最多 100 次。

store := redis.NewRedis("localhost:6379", "node", "")
limiter := periodlimit.New(store, 1, 100,
    periodlimit.Align())

code, err := limiter.Take("user:login")
// code == limitallow → 放行
// code == limitover → 拒绝

tokenlimit(令牌桶)

平滑限流,突发可借令牌。适合对抖动敏感的场景。

limiter := tokenlimit.New(store,
    tokenlimit.WithTotal(100),
    tokenlimit.WithRate(10))

code, err := limiter.Take("order:create")
if code == tokenlimit.OverQuota {
    return errors.New("too many requests")
}

熔断:breaker 保护下游

当被调服务持续失败时,自动「断开」一段时间,避免雪崩。go-zero 用的是 Google SRE 的滑动窗口算法。

包裹一次下游调用

var resp *user.LoginResponse
err := breaker.Do("user-rpc", func() error {
    var e error
    resp, e = client.Login(ctx, req)
    return e
}, func(err error) {
    // 熔断期间或失败时的兜底逻辑
    resp = &user.LoginResponse{Token: ""}
})
if err != nil {
    return nil, err
}
熔断是「自愈」的:断开后到半开状态试探,成功则恢复。不要把熔断当错误吞掉,记得在 fallback 里返回降级数据。

缓存:防穿透、防击穿

go-zero 的 model 层与 cache 包内置了 singleflight 与批量查询,避免缓存击穿、雪崩。

model 自动缓存

用 goctl 生成 model 时加 -c 参数即可开启缓存(基于主键的自动读写缓存)。

goctl model mysql ddl -src user.sql -dir . -c

生成的 model 在 FindOne 时会先查缓存,未命中才查库,并用 singleflight 合并并发回源。

手动用 Cache 封装

cache := cache.NewNode("localhost:6379", "node", "", false)
var name string
err := cache.Get("user:1:name", &name, func(v *string) error {
    // 缓存未命中时回源(自动 singleflight)
    *v = "alice"
    return nil
})

可观测性:logx + 指标

统一的结构化日志,直接对接 Prometheus 与告警。

结构化日志

logx.Info("user login success")
logx.Infof("uid=%d cost=%dms", uid, cost)
logx.Errorf("db query failed: %v", err)

// 带字段的上下文日志
logx.WithContext(ctx).Infow("rpc call",
    logx.Field("method", "Login"),
    logx.Field("code", 0))

开启 Prometheus 指标

在 yaml 配置里打开即可暴露 /metrics,无需额外代码:

Prometheus:
  Host: 0.0.0.0
  Port: 9091
  Path: /metrics

同时框架会自动采集 QPS、耗时、熔断次数等核心指标。

中间件:统一横切逻辑

鉴权、跨域、链路追踪等都通过中间件注入,不污染业务代码。

自定义 HTTP 中间件

func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            w.WriteHeader(http.StatusUnauthorized)
            return
        }
        next(w, r)
    }
}
// 注册:server.Use(AuthMiddleware)

RPC 拦截器(等效中间件)

srv := zrpc.MustNewServer(c.RpcServerConf, func(s *grpc.Server) {
    s.Use(func(ctx context.Context, req interface{},
        info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
        start := time.Now()
        resp, err := handler(ctx, req)
        logx.Infof("%s cost=%v", info.FullMethod, time.Since(start))
        return resp, err
    })
})
小结:go-zero 把「写服务」拆成了两条主线——rest 写对外 HTTP 接口zrpc 写对内 RPC 调用,再叠加限流/熔断/降载/缓存/日志这套服务治理全家桶。真正动手时,90% 的样板都由 goctl 帮你生成,你只需要填 logic。