OVERVIEW
一图看懂库的分层
上层依赖下层;请求从接入层进入,经过治理层,落到存储层,全程由基础层支撑。
接入层 · rest(HTTP)/ zrpc(gRPC)
对外暴露服务、路由、序列化、内置中间件链
治理层 · limit / breaker / load / discov
限流 · 熔断 · 自适应过载保护 · 服务发现(etcd)
存储层 · sqlx / cache / redis / mon
数据库访问 · 缓存封装 · 缓存击穿保护 · 文档库
基础层 · conf / logx / proc / syncx / mr / stat / prometheus / trace / bloom / collection
配置 · 日志 · 优雅退出 · 并发原语 · 指标 · 链路追踪
最常用
高并发必看
数据相关
无处不在
这套分层是 go-zero 的设计哲学:把「每个微服务都需要但写起来很烦」的能力(配置、日志、限流、熔断、缓存)做成独立库,框架把它接进请求链路,业务只管写 logic。
LAYER 1 · 接入
rest 提供 HTTP 服务,zrpc 提供 gRPC 服务,二者共享同一套配置 / 治理中间件。
rest HTTP 服务框架
干啥:基于 net/http 封装,加载 yaml 配置、注册路由、自动挂载 CORS / 超时 / 日志 / 熔断 / 限流等中间件,产出可直接 run 的 HTTP 服务。
var c rest.RestConf // 来自 conf.MustLoad 的配置
server := rest.MustNewServer (c, rest.WithCustomCors (nil , nil , "*" ))
defer server.Stop ()
server.AddRoutes ([]rest.Route{{
Method: http.MethodGet,
Path: "/ping" ,
Handler: pingHandler, // func(http.ResponseWriter,*http.Request)
}})
server.Start () // 阻塞监听
rest.MustNewServer
按配置建服务(失败 panic)。常用 option:WithCustomCors / WithUnauthorizedCallback。
rest.ToMiddleware
把 http.HandlerFunc 包成 rest.Middleware,接入全局链。
rest.AddRoutes
批量注册路由;rest.WithMiddlewares 给部分路由加局部中间件。
rest.Handler
对单个 handler 应用内置中间件(超时/日志/熔断),返回标准 handler。
日常开发几乎不手写这些 —— goctl api go 已生成 main + handler + logic,你只填 logic。详见 go-zero-commands.html 与本目录 api 全流程。
zrpc gRPC 服务框架
干啥:基于 gRPC-go 封装,集成 etcd 服务发现、负载均衡、超时/熔断/限流/追踪拦截器。内部服务互相调用首选。
// 服务端
zrpc.MustNewServer (c.RpcServerConf, func (s *zrpc.RpcServer) {
pb.RegisterUserServer (s.Server, user.NewUserServer(ctx))
})
// 客户端(从 etcd 发现并建连)
client := zrpc.MustNewClient (c.RpcClientConf)
userClient := pb.NewUserClient(client.Conn())
zrpc.MustNewServer
第二个参数是注册回调,把 pb 生成的 Server 挂上去。
zrpc.MustNewClient
读 Etcd / Target 配置自动发现节点,返回可建 Stub 的 Conn。
RpcServerConf
含 ListenOn、Etcd、Timeout、中间件开关。
client.Conn()
拿到 *grpc.ClientConn,用于 NewXxxClient。
zrpc/interceptor 拦截器(治理接入点)
干啥:go-zero 把限流/熔断/过载保护/追踪/指标做成 gRPC 拦截器,框架默认串好;也能手动显式加。
// 服务端额外拦截器
s := zrpc.NewServer (c, zrpc.WithUnaryServerInterceptor (
interceptor.TimeoutInterceptor (3*time.Second)))
// 客户端侧
zc := zrpc.MustNewClient (cc, zrpc.WithUnaryClientInterceptor (
interceptor.BreakerInterceptor ()))
拦截器 作用
TimeoutInterceptor超时控制(配置 Timeout 自动生效)
BreakerInterceptor对下游调用做熔断
SheddingInterceptor自适应过载保护,拒绝时返回 过载
TracingInterceptorOpenTelemetry 链路追踪
PrometheusInterceptor暴露 QPS / 耗时指标
RecoverInterceptor捕获 panic 转 error,防进程崩
zrpc/resolver etcd 服务发现解析器
干啥:实现 gRPC resolver.Builder,让客户端按 etcd 里的 key 自动发现、监听、负载均衡后端节点。框架已内置,配置 Etcd 即生效;这里讲手动用法。
r := resolver.MustNewEtcdResolver ([]string {"127.0.0.1:2379" }, "user.rpc" , resolver.Direct)
conn, _ := grpc.Dial ("etcd:///" +r.Key(), grpc.WithResolvers (r))
绝大多数情况你不用 手动调 resolver —— 在 zrpc 客户端配置里写 Etcd: {Hosts: [...], Key: "user.rpc"} 即可,框架自动注册 resolver。
LAYER 2 · 治理
限流、熔断、过载保护。go-zero 把它们做成独立库,可脱离框架单独用。
core/limit 限流
干啥:两种算法,都依赖 redis 做分布式限流。PeriodLimit = 固定窗口(period 秒内容许 quota 次);TokenLimit = 令牌桶(rate 速率 / burst 突发)。
rds := redis.MustNewRedis (rc)
l := limit.NewPeriodLimit (10 , 100 , rds, "rate:" ) // 10s 内 100 次
switch res, _ := l.Take ("user:1" ); res {
case limit.Allow: fmt.Println ("放行" )
case limit.Deny: fmt.Println ("拒绝" )
}
// 令牌桶
tb := limit.NewTokenLimit (10 , 20 , rds, "tok:" )
为什么不能只在网关限流?网关是入口维度,业务里还要按「用户 / 接口 / 资源」精细限流,且要防单实例击穿。go-zero 把限流下沉到库,业务可随用随限。
core/breaker 熔断器
干啥:Google SRE 式熔断(错误率超阈值则打开,冷却后半开探活)。保护调用方不被下游拖垮。默认对下游调用自动熔断。
brk := breaker.NewBreaker (breaker.WithName ("orderSvc" ))
resp, err := brk.Do (func () (any , error) {
return callDownstream()
})
if err != nil { // 熔断打开时 err = ErrServiceUnavailable
return fallback()
}
breaker.NewBreaker
可配 WithName / WithMaxAccept 等。
breaker.Do
执行请求;熔断打开直接走 err,不真打下游。
core/load 自适应过载保护
干啥:根据 CPU 与平均时延自适应「丢弃」请求,保护进程不雪崩。比固定阈值更聪明,go-zero 服务端默认开启。
shedder, _ := load.NewAdaptiveShedder ()
if err := shedder.Allow (); err != nil {
return err // ErrServiceOverloaded,直接拒绝
}
// 业务正常处理...
限流是「提前卡数量」,过载保护是「扛不住就丢」,熔断是「下游挂了别连」。三者互补,go-zero 默认全串在 zrpc/rest 的拦截器里。
core/discov 服务注册与发现
干啥:封装 etcd 的发布/订阅。Publisher 把自身地址写进 etcd;Subscriber 监听 key 变化,回调拿到最新节点列表。
p, _ := discov.NewPublisher (discov.EtcdConf{Hosts: []string {"127.0.0.1:2379" }}, "user.rpc" , "127.0.0.1:8080" )
p.KeepAlive () // 心跳续租,进程退出自动摘掉
sub, _ := discov.NewSubscriber (etcd, "user.rpc" )
sub.AddListener (func (addrs []string ) {
fmt.Println ("节点变化:" , addrs)
})
实际项目里服务发现交给 zrpc 配置 Etcd 自动完成,discov 多半用于定制注册逻辑或旁路监控。
LAYER 3 · 存储
sqlx 访问数据库、cache 做缓存封装、redis 直接操作、三者协作防缓存击穿。
core/stores/sqlx SQL 访问
干啥:对 database/sql 的封装,统一 MySql / Pg / SqlServer 连接,提供 QueryRow / Exec / Transact,并自动接日志与慢查询统计。
conn := sqlx.NewMysql (dsn) // 或用 NewSqlConnFromConf
var name string
conn.QueryRow (&name, "select name from user where id=?" , id)
conn.Exec ("insert into user(name) values(?)" , "a" )
conn.Transact (func (s sqlx.Session) error {
s.Exec ("update account set bal=bal-?" , 10)
return nil
})
NewMysql
直接传 DSN;生产用 NewSqlConnFromConf(c) 接配置文件。
QueryRow / QueryRowPartial
整行扫描 / 按列名部分扫描。
Transact
事务回调,返回 error 自动回滚。
与 goctl model
生成的 model 底层就是它,业务可直接拿 conn。
core/stores/cache 缓存封装
干啥:把「查缓存 → 没命中查库 → 回填」封装成 Take;内置 singleflight + 本地锁防缓存击穿,再上层可选 bloom 防穿透。
c := cache.New (cacheNodeConf) // 含 Redis 节点配置
var u User
err := c.Take (&u, "user:1" , func (v any ) error {
return conn.QueryRow (v, "select * from user where id=1" )
})
c.Del ("user:1" ) // 写后删缓存
Take(v, key, queryFn) 是缓存层的灵魂:命中直接返回;未命中并发请求只放一个去查库并回填,其余共享结果 —— 这就是 singleflight 的价值。
core/stores/redis Redis 操作
干啥:对 go-redis/redigo 的封装(red 包),统一连接池、超时、错误处理。缓存层与限流底层都靠它。
r := redis.MustNewRedis (redis.RedisConf{
Host: "127.0.0.1:6379" , Type: redis.NodeType, Pass: "" ,
})
r.Set ("k" , "v" )
r.Setex ("k" , "v" , 60 )
v, _ := r.Get ("k" )
n, _ := r.Incr ("cnt" )
r.Hget ("h" , "f" )
MustNewRedis
配置失败 panic;Type 区分 Node / Cluster。
Setex / Incr / Hget
常用单值/计数/哈希操作一应俱全。
red.Redis 接口
可 mock 做测试,不绑死实现。
集群
RedisConf.Type=ClusterType 即走集群模式。
core/stores/mon MongoDB & core/bloom 布隆过滤器
mon:对 MongoDB 的封装(Collection 操作)。bloom:基于 redis 的布隆过滤器,挡掉「不存在的 key」打到数据库(缓存穿透)。
// 布隆:先问是否存在,不存在直接返回,避免透传到 DB
b := bloom.New (bloomConf)
if !b.Exists ("user:999" ) {
return nil , errNotFound // 一定不存在
}
b.Add ("user:1" ) // 写入时同步加
缓存三连击防护:击穿用 singleflight(cache.Take 内置)、穿透用 bloom、雪崩用随机过期时间。go-zero 全部备齐。
LAYER 4 · 基础
贯穿所有层的「横切」能力。最常用的是 conf、logx,其次是 syncx/mr 这类并发利器。
core/conf 配置加载
干啥:读 yaml/json 配置到结构体(注意:go-zero 用 json tag 映射字段)。支持环境变量覆盖、监听变更。
var c struct {
Name string `json:"name"`
Port int `json:"port"`
}
conf.MustLoad ("etc/config.yaml" , &c) // 失败 panic
conf.Load ("etc/config.yaml" , &c, conf.UseEnv ()) // 允许环境变量覆盖
坑:go-zero 配置文件字段映射用 json tag 不是 yaml tag 。改了配置名记得同步改 struct 的 json tag,否则读到零值。
core/logx 日志
干啥:结构化日志(json 格式),分 Info/Error/Slow/Stat/Fatal 等级,自动带时间戳、调用方、traceId。替代自带 log。
logx.Info ("server start" )
logx.Errorf ("query failed: %v" , err)
logx.Slowf ("slow sql: %s" , sql) // 慢日志单独通道
logx.WithContext (ctx).Infof ("req done" ) // 带上 traceId
logx.SetUp (logx.LogConf{ServiceName: "user" , Mode: "file" })
业务日志一律用 logx,别用 fmt.Println —— 这样日志能被采集、带 traceId、且框架的耗时/错误统计才接得上。
core/syncx 并发原语
干啥:一组「微信团队」打磨的并发工具。最神的是 SingleFlight(合并并发相同请求),还有 Pool、ResourceGroup、AtomicBool。
var sf syncx.SingleFlight
val, err := sf.Do ("key" , func () (any , error) {
return fetchExpensive() // 并发 100 次只真查 1 次
})
pool := syncx.NewPool (func () any { return new (bytes.Buffer) })
buf := pool.Take ().(*bytes.Buffer)
buf.Reset (); pool.Put (buf) // 用完归还
SingleFlight.Do
同 key 并发合并为一次执行,缓存击穿神器。
NewPool
对象池,类似 sync.Pool 但语义更清晰。
AtomicBool
无锁布尔,替代 bool+mutex。
ResourceGroup
一组资源共享生命周期,优雅释放。
core/mr MapReduce 并发聚合
干啥:把「扇出取数 + 聚合」写成 MapReduce,自动并发、自动 cancel、首个 error 即短路。批查多源数据的利器。
vals, err := mr.MapReduce (
func (src chan <- int ) { // generate
for i := 0; i < 10; i++ { src <- i }
},
func (item int , w mr.Writer[int ], cancel func (error)) {
v, e := fetch(item) // mapper(可并发)
if e != nil { cancel(e); return }
w.Write (v)
},
func (pipe <-chan int , w mr.Writer[int ], _ func (error)) {
sum := 0 // reducer
for v := range pipe { sum += v }
w.Write (sum)
},
)
mr.Finish (fn1, fn2, fn3) // 并发跑多个 fn,等全部,首个错返回
需要「并发拉 N 个数据源再汇总」时别手写 WaitGroup + 锁,用 mr.MapReduce / mr.Finish 更安全优雅。
core/proc 优雅退出 & core/collection 时间轮
proc:注册关闭钩子,收到信号时按序清理(关连接、flush 日志)。collection:TimingWheel(延迟任务)/ RollingWindow(滑动窗口指标)/ Cache(带 TTL 的内存缓存)。
proc.AddShutdownHandler (func () {
db.Close (); logx.Close () // 进程退出前清理
})
// 时间轮:5s 一格、共 360 格
tw, _ := collection.NewTimingWheel (5*time.Second, 360 , onExpire)
go-zero 框架已内置优雅退出(监听 SIGTERM)。只有当你自己 hold 了非托管资源(自连的 DB/自定义 goroutine)才需手动 AddShutdownHandler。
core/stat · core/prometheus · core/trace 可观测三件套
stat:自定义业务指标。prometheus:暴露 QPS/耗时到 /metrics。trace:基于 OpenTelemetry 的链路追踪,串起一次请求的上下游。
m := stat.NewMetrics ("order_count" )
m.Add (1) // 业务计数
prometheus.Add ("path" , "/pay" ) // 打标签累加
trace.StartAgent (trace.Config{...}) // 启动 trace 上报
defer trace.StopAgent ()
rest/zrpc 默认已接好 prometheus 与 tracing 拦截器。你要做的通常只是 stat.NewMetrics 加自己的业务埋点,再配好上报地址。
BONUS
goctl · 代码生成 CLI
不是 runtime 库,但它是让上面所有库「自动用起来」的发动机。
干啥:goctl 读 .api / .proto / .sql,生成带 rest+zrpc+model+配置+中间件 的整套骨架。生成的代码内部就调用了本页所有库(conf.MustLoad、sqlx、cache、limit 拦截器等)。
goctl api go -api user.api -dir . # 生成 HTTP 服务
goctl rpc protoc user.proto --zrpc_out=. -m # 生成 RPC 服务
goctl model mysql ddl -src user.sql -dir . -c # 生成带缓存 model
想记住完整命令?本目录已有 go-zero-commands.html 专门速查 goctl。本文聚焦「库本身」,命令请移步那篇。