Go Zero源码阅读1

文章目录

  • [一、go-zero 前置基础与源码环境](#一、go-zero 前置基础与源码环境)
    • [1. go-zero 设计思想与极简微服务理念](#1. go-zero 设计思想与极简微服务理念)
      • [1.1 设计哲学](#1.1 设计哲学)
      • [1.2 核心特性](#1.2 核心特性)
    • [2. 微服务基础概念](#2. 微服务基础概念)
      • [2.1 HTTP 与 RPC](#2.1 HTTP 与 RPC)
      • [2.2 服务注册与发现](#2.2 服务注册与发现)
      • [2.3 熔断与限流](#2.3 熔断与限流)
      • [2.4 链路追踪](#2.4 链路追踪)
      • [2.5 分布式缓存](#2.5 分布式缓存)
    • [3. go-zero 整体分层架构](#3. go-zero 整体分层架构)
      • [3.1 分层说明](#3.1 分层说明)
    • [4. 仓库目录结构详解](#4. 仓库目录结构详解)
      • [4.1 核心包详解](#4.1 核心包详解)
        • [core/breaker - 熔断器](#core/breaker - 熔断器)
        • [core/discov - 服务发现](#core/discov - 服务发现)
        • [rest/server.go - HTTP 服务](#rest/server.go - HTTP 服务)
        • [zrpc/server.go - gRPC 服务](#zrpc/server.go - gRPC 服务)
    • [5. 本地源码编译与调试](#5. 本地源码编译与调试)
      • [5.1 环境要求](#5.1 环境要求)
      • [5.2 编译源码](#5.2 编译源码)
      • [5.3 安装 goctl](#5.3 安装 goctl)
      • [5.4 调试技巧](#5.4 调试技巧)
    • [6. goctl 工具原理](#6. goctl 工具原理)
      • [6.1 架构概览](#6.1 架构概览)
      • [6.2 核心流程](#6.2 核心流程)
      • [6.3 模板机制](#6.3 模板机制)
      • [6.4 API 定义示例](#6.4 API 定义示例)
    • [7. 快速开始示例](#7. 快速开始示例)
      • [7.1 创建 API 服务](#7.1 创建 API 服务)
      • [7.2 创建 RPC 服务](#7.2 创建 RPC 服务)
      • [7.3 测试服务](#7.3 测试服务)
  • 二、框架核心底层基础组件
    • [1. 配置管理 (conf)](#1. 配置管理 (conf))
      • [1.1 功能概述](#1.1 功能概述)
      • [1.2 核心源码解析](#1.2 核心源码解析)
      • [1.3 使用示例](#1.3 使用示例)
      • [1.4 架构设计](#1.4 架构设计)
    • [2. 日志系统 (logx)](#2. 日志系统 (logx))
      • [2.1 功能概述](#2.1 功能概述)
      • [2.2 Logger 接口](#2.2 Logger 接口)
      • [2.3 日志配置](#2.3 日志配置)
  • logx
    • [logx 配置](#logx 配置)
    • 打印日志方法
    • 与第三方日志库集成
    • 将日志写到指定的存储
    • 过滤敏感字段
    • 更多示例
    • [3. 进程管理与信号处理 (proc)](#3. 进程管理与信号处理 (proc))
      • [3.1 平滑关闭机制](#3.1 平滑关闭机制)
      • [3.2 信号监听](#3.2 信号监听)
      • [3.3 使用示例](#3.3 使用示例)
    • [4. 并发原语 (syncx)](#4. 并发原语 (syncx))
      • [4.1 资源池 (Pool)](#4.1 资源池 (Pool))
      • [4.2 Pool 设计特点](#4.2 Pool 设计特点)
    • [5. 滑动窗口 (collection/RollingWindow)](#5. 滑动窗口 (collection/RollingWindow))
      • [5.1 核心实现](#5.1 核心实现)
      • [5.2 滑动窗口应用场景](#5.2 滑动窗口应用场景)
    • [6. 限流器 (limit)](#6. 限流器 (limit))
      • [6.1 令牌桶限流器](#6.1 令牌桶限流器)
      • [6.2 降级策略设计](#6.2 降级策略设计)
    • [7. 基础组件架构全景](#7. 基础组件架构全景)
    • [8. 最佳实践](#8. 最佳实践)
      • [8.1 配置管理最佳实践](#8.1 配置管理最佳实践)
      • [8.2 平滑关闭最佳实践](#8.2 平滑关闭最佳实践)
  • [三、HTTP 网关 & API 服务底层实现](#三、HTTP 网关 & API 服务底层实现)
    • [1. 整体架构](#1. 整体架构)
      • [1.1 核心组件](#1.1 核心组件)
      • [1.2 Server 结构体](#1.2 Server 结构体)
    • [2. 路由实现](#2. 路由实现)
      • [2.1 路由树结构](#2.1 路由树结构)
      • [2.2 路径匹配](#2.2 路径匹配)
    • [3. 中间件链](#3. 中间件链)
      • [3.1 链设计模式](#3.1 链设计模式)
      • [3.2 中间件执行顺序](#3.2 中间件执行顺序)
      • [3.3 内置中间件](#3.3 内置中间件)
    • [4. 请求处理流程](#4. 请求处理流程)
      • [4.1 完整流程图](#4.1 完整流程图)
      • [4.2 Engine 启动](#4.2 Engine 启动)
    • [5. 核心中间件解析](#5. 核心中间件解析)
      • [5.1 熔断中间件](#5.1 熔断中间件)
      • [5.2 超时中间件](#5.2 超时中间件)
      • [5.3 降载中间件](#5.3 降载中间件)
    • [6. JWT 认证中间件](#6. JWT 认证中间件)
      • [6.1 使用方式](#6.1 使用方式)
      • [6.2 实现原理](#6.2 实现原理)
    • [7. HTTP 响应处理](#7. HTTP 响应处理)
      • [7.1 响应包装器](#7.1 响应包装器)
      • [7.2 统一响应格式](#7.2 统一响应格式)
    • [8. CORS 跨域支持](#8. CORS 跨域支持)
    • [9. SSE 服务端事件支持](#9. SSE 服务端事件支持)
    • [10. 实际使用示例](#10. 实际使用示例)
      • [10.1 基本 HTTP 服务](#10.1 基本 HTTP 服务)
      • [10.2 路由分组与中间件](#10.2 路由分组与中间件)
  • [四、gRPC RPC 服务底层全解析](#四、gRPC RPC 服务底层全解析)
    • [1. 整体架构](#1. 整体架构)
      • [1.1 服务端架构](#1.1 服务端架构)
      • [1.2 客户端架构](#1.2 客户端架构)
    • [2. 服务端实现](#2. 服务端实现)
      • [2.1 RpcServer 结构体](#2.1 RpcServer 结构体)
      • [2.2 服务端拦截器设置](#2.2 服务端拦截器设置)
      • [2.3 核心服务实现](#2.3 核心服务实现)
    • [3. 客户端实现](#3. 客户端实现)
      • [3.1 RpcClient 结构体](#3.1 RpcClient 结构体)
      • [3.2 客户端连接建立](#3.2 客户端连接建立)
    • [4. 负载均衡](#4. 负载均衡)
      • [4.1 P2C (Pick 2 Choices) 负载均衡](#4.1 P2C (Pick 2 Choices) 负载均衡)
      • [4.2 一致性哈希负载均衡](#4.2 一致性哈希负载均衡)
    • [5. 核心拦截器](#5. 核心拦截器)
      • [5.1 客户端熔断拦截器](#5.1 客户端熔断拦截器)
      • [5.2 客户端超时拦截器](#5.2 客户端超时拦截器)
      • [5.3 服务端降载拦截器](#5.3 服务端降载拦截器)
    • [6. 使用示例](#6. 使用示例)
      • [6.1 服务端使用](#6.1 服务端使用)
      • [6.2 客户端使用](#6.2 客户端使用)
  • 五、服务注册与发现 (etcd)
    • [1. 整体架构](#1. 整体架构)
    • [2. 服务注册实现](#2. 服务注册实现)
      • [2.1 Publisher 接口](#2.1 Publisher 接口)
      • [2.2 Etcd 键格式](#2.2 Etcd 键格式)
    • [3. 服务发现实现](#3. 服务发现实现)
      • [3.1 EtcdResolver](#3.1 EtcdResolver)
      • [3.2 节点监听](#3.2 节点监听)
      • [3.3 服务发现核心](#3.3 服务发现核心)
    • [4. 服务键格式](#4. 服务键格式)
    • [5. 负载均衡与服务发现集成](#5. 负载均衡与服务发现集成)
    • [6. 使用示例](#6. 使用示例)
      • [6.1 服务端配置](#6.1 服务端配置)
      • [6.2 客户端配置](#6.2 客户端配置)
      • [6.3 直接使用服务发现](#6.3 直接使用服务发现)
  • 六、服务治理核心:熔断/限流/降级
    • [1. 服务治理全景](#1. 服务治理全景)
    • [2. 熔断器 (Breaker)](#2. 熔断器 (Breaker))
      • [2.1 熔断器原理](#2.1 熔断器原理)
      • [2.2 核心实现](#2.2 核心实现)
      • [2.3 Google SRE 熔断器算法](#2.3 Google SRE 熔断器算法)
      • [2.4 熔断器使用](#2.4 熔断器使用)
    • [3. 滑动窗口 (RollingWindow)](#3. 滑动窗口 (RollingWindow))
      • [3.1 核心实现](#3.1 核心实现)
      • [3.2 滑动窗口结构](#3.2 滑动窗口结构)
    • [4. 限流器 (Limiter)](#4. 限流器 (Limiter))
      • [4.1 令牌桶限流器](#4.1 令牌桶限流器)
      • [4.2 Redis Lua 脚本](#4.2 Redis Lua 脚本)
    • [5. 自适应降载器 (AdaptiveShedder)](#5. 自适应降载器 (AdaptiveShedder))
      • [5.1 降载原理](#5.1 降载原理)
      • [5.2 过载判断指标](#5.2 过载判断指标)
    • [6. 实际应用示例](#6. 实际应用示例)
      • [6.1 HTTP 服务配置](#6.1 HTTP 服务配置)
      • [6.2 RPC 服务配置](#6.2 RPC 服务配置)
      • [6.3 自定义熔断器](#6.3 自定义熔断器)

Go Zero源码阅读2:
https://blog.csdn.net/qq_41822345/article/details/162694317

一、go-zero 前置基础与源码环境

1. go-zero 设计思想与极简微服务理念

1.1 设计哲学

go-zero 是一个集成了各种工程实践的 Web 和 RPC 框架,其核心设计思想包括:

  • 极简理念:约定优于配置,减少样板代码
  • 工程化导向:内置最佳实践,提高开发效率
  • 微服务治理:熔断、限流、降级、链路追踪等开箱即用
  • 高可用:自动注册发现、负载均衡、容错设计
  • 可观测性:日志、指标、追踪全链路可观测

1.2 核心特性

  • 强大的服务发现和负载均衡
  • 自动熔断和自适应降载
  • 链路追踪集成 (OpenTelemetry)
  • Prometheus 指标导出
  • 高并发控制与限流
  • 代码生成工具 goctl

2. 微服务基础概念

2.1 HTTP 与 RPC

  • HTTP (REST):轻量级、跨语言、易于调试,适合对外提供 API
  • RPC (Remote Procedure Call):高性能、强类型、支持流式传输,适合内部服务间通信

go-zero 同时支持 HTTP API 和 gRPC 两种通信方式。

2.2 服务注册与发现

服务注册发现是微服务架构的核心组件,用于解决"服务在哪里"的问题:

  • 服务提供者:启动时向注册中心注册自己的地址
  • 服务消费者:从注册中心获取可用服务列表,通过负载均衡选择节点
  • 健康检查:注册中心监控服务健康状态,剔除故障节点

go-zero 内置 etcd 作为注册中心,也支持 Kubernetes DNS 发现。

2.3 熔断与限流

  • 熔断器 (Circuit Breaker):保护系统不被雪崩拖垮,类似电路保险丝
  • 限流器 (Rate Limiter):控制请求速率,防止系统过载
  • 降载 (Load Shedding):系统高负载时主动丢弃部分请求

2.4 链路追踪

通过 trace-id 串联整个请求链路,记录每个服务的耗时、状态等信息,便于问题定位和性能分析。

2.5 分布式缓存

  • Redis 作为缓存层,减轻数据库压力
  • 缓存预热、过期策略
  • 缓存击穿、雪崩、穿透防护

3. go-zero 整体分层架构

#mermaid-svg-8n2poboib8WeOvnz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8n2poboib8WeOvnz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8n2poboib8WeOvnz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8n2poboib8WeOvnz .error-icon{fill:#552222;}#mermaid-svg-8n2poboib8WeOvnz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8n2poboib8WeOvnz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8n2poboib8WeOvnz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8n2poboib8WeOvnz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8n2poboib8WeOvnz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8n2poboib8WeOvnz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8n2poboib8WeOvnz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8n2poboib8WeOvnz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8n2poboib8WeOvnz .marker.cross{stroke:#333333;}#mermaid-svg-8n2poboib8WeOvnz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8n2poboib8WeOvnz p{margin:0;}#mermaid-svg-8n2poboib8WeOvnz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8n2poboib8WeOvnz .cluster-label text{fill:#333;}#mermaid-svg-8n2poboib8WeOvnz .cluster-label span{color:#333;}#mermaid-svg-8n2poboib8WeOvnz .cluster-label span p{background-color:transparent;}#mermaid-svg-8n2poboib8WeOvnz .label text,#mermaid-svg-8n2poboib8WeOvnz span{fill:#333;color:#333;}#mermaid-svg-8n2poboib8WeOvnz .node rect,#mermaid-svg-8n2poboib8WeOvnz .node circle,#mermaid-svg-8n2poboib8WeOvnz .node ellipse,#mermaid-svg-8n2poboib8WeOvnz .node polygon,#mermaid-svg-8n2poboib8WeOvnz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8n2poboib8WeOvnz .rough-node .label text,#mermaid-svg-8n2poboib8WeOvnz .node .label text,#mermaid-svg-8n2poboib8WeOvnz .image-shape .label,#mermaid-svg-8n2poboib8WeOvnz .icon-shape .label{text-anchor:middle;}#mermaid-svg-8n2poboib8WeOvnz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8n2poboib8WeOvnz .rough-node .label,#mermaid-svg-8n2poboib8WeOvnz .node .label,#mermaid-svg-8n2poboib8WeOvnz .image-shape .label,#mermaid-svg-8n2poboib8WeOvnz .icon-shape .label{text-align:center;}#mermaid-svg-8n2poboib8WeOvnz .node.clickable{cursor:pointer;}#mermaid-svg-8n2poboib8WeOvnz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8n2poboib8WeOvnz .arrowheadPath{fill:#333333;}#mermaid-svg-8n2poboib8WeOvnz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8n2poboib8WeOvnz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8n2poboib8WeOvnz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8n2poboib8WeOvnz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8n2poboib8WeOvnz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8n2poboib8WeOvnz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8n2poboib8WeOvnz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8n2poboib8WeOvnz .cluster text{fill:#333;}#mermaid-svg-8n2poboib8WeOvnz .cluster span{color:#333;}#mermaid-svg-8n2poboib8WeOvnz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8n2poboib8WeOvnz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8n2poboib8WeOvnz rect.text{fill:none;stroke-width:0;}#mermaid-svg-8n2poboib8WeOvnz .icon-shape,#mermaid-svg-8n2poboib8WeOvnz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8n2poboib8WeOvnz .icon-shape p,#mermaid-svg-8n2poboib8WeOvnz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8n2poboib8WeOvnz .icon-shape .label rect,#mermaid-svg-8n2poboib8WeOvnz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8n2poboib8WeOvnz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8n2poboib8WeOvnz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8n2poboib8WeOvnz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础组件层 (core)
RPC层 (zrpc)
网关层 (rest)
工具层
goctl 代码生成
API 定义解析
模板引擎
HTTP Server
路由管理
中间件链
gRPC Server
gRPC Client
拦截器链
日志 logx
熔断 breaker
限流 limit
服务发现 discov
存储 stores
并发原语 syncx

3.1 分层说明

层级 包名 职责
工具层 tools/goctl 代码生成、API 解析、模板渲染
网关层 rest HTTP 服务、路由、中间件
RPC层 zrpc gRPC 服务、客户端、拦截器
基础组件层 core 日志、熔断、限流、服务发现等

4. 仓库目录结构详解

复制代码
go-zero/
├── core/                    # 核心基础组件
│   ├── bloom/              # 布隆过滤器
│   ├── breaker/            # 熔断器
│   ├── collection/         # 集合工具类 (滑动窗口等)
│   ├── conf/               # 配置管理
│   ├── discov/             # 服务发现
│   ├── errorx/             # 错误处理
│   ├── executors/          # 执行器 (批量、延迟等)
│   ├── fx/                 # 函数式工具
│   ├── lang/               # 语言扩展
│   ├── load/               # 负载降载
│   ├── logx/               # 日志系统
│   ├── mapping/            # 结构体映射
│   ├── mathx/              # 数学工具
│   ├── proc/               # 进程管理 (平滑退出等)
│   ├── prometheus/         # Prometheus 指标
│   ├── rescue/             # panic 恢复
│   ├── stat/               # 统计指标
│   ├── stores/             # 存储 (Redis, SQL等)
│   ├── stringx/            # 字符串工具
│   ├── syncx/              # 并发原语
│   ├── threadx/            # 线程工具
│   ├── timex/              # 时间工具
│   └── trace/              # 链路追踪
├── rest/                   # HTTP 服务框架
│   ├── handler/            # 内置中间件
│   ├── httpc/              # HTTP 客户端
│   ├── httpx/              # HTTP 工具
│   ├── pathvar/            # 路径变量
│   └── router/             # 路由实现
├── zrpc/                   # gRPC 服务框架
│   ├── internal/           # 内部实现
│   ├── resolver/           # 服务发现解析器
│   └── resolver/...        # etcd, k8s 等解析器
└── tools/
    └── goctl/              # 代码生成工具
        ├── api/            # API 定义处理
        ├── docker/         # Docker 生成
        ├── kube/           # Kubernetes 生成
        ├── model/          # 模型代码生成
        ├── rpc/            # RPC 代码生成
        └── template/       # 模板管理

4.1 核心包详解

core/breaker - 熔断器
go 复制代码
// 文件路径: core/breaker/breaker.go
type Breaker interface {
    Name() string
    Allow() (Promise, error)           // 检查是否允许请求
    Do(req func() error) error         // 执行请求,自动处理熔断
    DoWithFallback(req func() error, fallback Fallback) error
}

type Promise interface {
    Accept()                           // 标记请求成功
    Reject(reason string)              // 标记请求失败
}
core/discov - 服务发现
go 复制代码
// 文件路径: core/discov/clients.go
const TimeToLive int64 = 10  // etcd 租约 TTL 10秒

// 服务注册键格式: key/id
func makeEtcdKey(key string, id int64) string {
    return fmt.Sprintf("%s%c%d", key, internal.Delimiter, id)
}
rest/server.go - HTTP 服务
go 复制代码
// 文件路径: rest/server.go
type Server struct {
    ngin   *engine
    router httpx.Router
}

func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
    // 初始化 server,配置路由、中间件等
}

func (s *Server) Start() {
    // 启动 HTTP 服务,支持平滑关闭
}
zrpc/server.go - gRPC 服务
go 复制代码
// 文件路径: zrpc/server.go
type RpcServer struct {
    server   internal.Server
    register internal.RegisterFn
}

func NewServer(c RpcServerConf, register internal.RegisterFn) (*RpcServer, error) {
    // 初始化 RPC server,设置拦截器、注册服务等
}

func (rs *RpcServer) Start() {
    // 启动 RPC 服务,支持平滑关闭
}

5. 本地源码编译与调试

5.1 环境要求

  • Go 1.24+
  • etcd 3.5+ (可选,用于服务发现)
  • Redis 6+ (可选,用于缓存、分布式锁)

5.2 编译源码

bash 复制代码
# 克隆仓库
git clone https://github.com/zeromicro/go-zero.git
cd go-zero

# 安装依赖
go mod download

# 运行测试
go test -v ./core/...
go test -v ./rest/...
go test -v ./zrpc/...

5.3 安装 goctl

bash 复制代码
# 从源码安装
cd tools/goctl
go install

# 验证安装
goctl --version

5.4 调试技巧

使用 Delve 调试
bash 复制代码
# 安装 Delve
go install github.com/go-delve/delve/cmd/dlv@latest

# 调试测试
dlv test ./core/breaker

# 调试程序
dlv debug ./your-app
日志调试

go-zero 使用 logx 包,可通过环境变量控制日志级别:

bash 复制代码
# 开发环境:显示所有日志
export LOG_LEVEL=debug

# 生产环境:只显示错误日志
export LOG_LEVEL=error

6. goctl 工具原理

6.1 架构概览

#mermaid-svg-tM0wlbl0Rv86bdpn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tM0wlbl0Rv86bdpn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tM0wlbl0Rv86bdpn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tM0wlbl0Rv86bdpn .error-icon{fill:#552222;}#mermaid-svg-tM0wlbl0Rv86bdpn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tM0wlbl0Rv86bdpn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tM0wlbl0Rv86bdpn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tM0wlbl0Rv86bdpn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tM0wlbl0Rv86bdpn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tM0wlbl0Rv86bdpn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tM0wlbl0Rv86bdpn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tM0wlbl0Rv86bdpn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tM0wlbl0Rv86bdpn .marker.cross{stroke:#333333;}#mermaid-svg-tM0wlbl0Rv86bdpn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tM0wlbl0Rv86bdpn p{margin:0;}#mermaid-svg-tM0wlbl0Rv86bdpn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tM0wlbl0Rv86bdpn .cluster-label text{fill:#333;}#mermaid-svg-tM0wlbl0Rv86bdpn .cluster-label span{color:#333;}#mermaid-svg-tM0wlbl0Rv86bdpn .cluster-label span p{background-color:transparent;}#mermaid-svg-tM0wlbl0Rv86bdpn .label text,#mermaid-svg-tM0wlbl0Rv86bdpn span{fill:#333;color:#333;}#mermaid-svg-tM0wlbl0Rv86bdpn .node rect,#mermaid-svg-tM0wlbl0Rv86bdpn .node circle,#mermaid-svg-tM0wlbl0Rv86bdpn .node ellipse,#mermaid-svg-tM0wlbl0Rv86bdpn .node polygon,#mermaid-svg-tM0wlbl0Rv86bdpn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tM0wlbl0Rv86bdpn .rough-node .label text,#mermaid-svg-tM0wlbl0Rv86bdpn .node .label text,#mermaid-svg-tM0wlbl0Rv86bdpn .image-shape .label,#mermaid-svg-tM0wlbl0Rv86bdpn .icon-shape .label{text-anchor:middle;}#mermaid-svg-tM0wlbl0Rv86bdpn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tM0wlbl0Rv86bdpn .rough-node .label,#mermaid-svg-tM0wlbl0Rv86bdpn .node .label,#mermaid-svg-tM0wlbl0Rv86bdpn .image-shape .label,#mermaid-svg-tM0wlbl0Rv86bdpn .icon-shape .label{text-align:center;}#mermaid-svg-tM0wlbl0Rv86bdpn .node.clickable{cursor:pointer;}#mermaid-svg-tM0wlbl0Rv86bdpn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tM0wlbl0Rv86bdpn .arrowheadPath{fill:#333333;}#mermaid-svg-tM0wlbl0Rv86bdpn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tM0wlbl0Rv86bdpn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tM0wlbl0Rv86bdpn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tM0wlbl0Rv86bdpn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tM0wlbl0Rv86bdpn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tM0wlbl0Rv86bdpn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tM0wlbl0Rv86bdpn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tM0wlbl0Rv86bdpn .cluster text{fill:#333;}#mermaid-svg-tM0wlbl0Rv86bdpn .cluster span{color:#333;}#mermaid-svg-tM0wlbl0Rv86bdpn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tM0wlbl0Rv86bdpn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tM0wlbl0Rv86bdpn rect.text{fill:none;stroke-width:0;}#mermaid-svg-tM0wlbl0Rv86bdpn .icon-shape,#mermaid-svg-tM0wlbl0Rv86bdpn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tM0wlbl0Rv86bdpn .icon-shape p,#mermaid-svg-tM0wlbl0Rv86bdpn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tM0wlbl0Rv86bdpn .icon-shape .label rect,#mermaid-svg-tM0wlbl0Rv86bdpn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tM0wlbl0Rv86bdpn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tM0wlbl0Rv86bdpn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tM0wlbl0Rv86bdpn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} API 文件定义
词法解析器
语法解析器
AST 抽象语法树
模板渲染引擎
生成 Go 代码
生成 Dockerfile
生成 K8s YAML

6.2 核心流程

goctl 使用 ANTLR4 进行语法解析,将 .api 文件解析为 AST,然后通过模板引擎生成各种代码。

复制代码
goctl 命令结构:
├── api      # API 相关命令
│   ├── new   # 创建新 API 服务
│   ├── go    # 生成 Go 代码
│   ├── doc   # 生成文档
│   └── ...
├── rpc      # RPC 相关命令
│   ├── new   # 创建新 RPC 服务
│   └── ...
├── model    # 模型代码生成
│   ├── mysql # 从 MySQL 生成
│   └── ...
└── docker   # Docker 相关

6.3 模板机制

goctl 支持自定义模板,可以通过环境变量指定模板目录:

bash 复制代码
export GOCTL_TEMPLATE=/path/to/your/templates
goctl api go -api your.api -dir .

内置模板位于 tools/goctl/<api|rpc|model>/ 目录下。

6.4 API 定义示例

api 复制代码
// example.api
syntax = "v1"

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

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

service user-api {
    @handler login
    post /user/login (LoginRequest) returns (LoginResponse)
}

通过 goctl api go -api example.api -dir . 即可生成完整的 HTTP 服务代码。

7. 快速开始示例

7.1 创建 API 服务

bash 复制代码
goctl api new greet
cd greet
go mod tidy
go run greet.go -f etc/greet-api.yaml

7.2 创建 RPC 服务

bash 复制代码
goctl rpc new greet
cd greet
go mod tidy
go run greet.go -f etc/greet.yaml

7.3 测试服务

bash 复制代码
# API 服务
curl http://localhost:8888/from/me

# RPC 服务(使用 grpcurl)
grpcurl -plaintext localhost:8080 greet.Greet/Hello

二、框架核心底层基础组件

go-zero 的核心组件位于 core/ 目录下,这是整个框架的基石。本章将深入解析这些组件的实现原理。

1. 配置管理 (conf)

1.1 功能概述

配置管理模块支持多格式配置文件(JSON、YAML、TOML、JSON5),支持环境变量替换,提供自动验证功能。

1.2 核心源码解析

go 复制代码
// 文件路径: core/conf/config.go

// 支持的配置文件格式
var loaders = map[string]func([]byte, any) error{
    ".json":  LoadFromJsonBytes,
    ".json5": LoadFromJson5Bytes,
    ".toml":  LoadFromTomlBytes,
    ".yaml":  LoadFromYamlBytes,
    ".yml":   LoadFromYamlBytes,
}

// Load 从文件加载配置
func Load(file string, v any, opts ...Option) error {
    content, err := os.ReadFile(file)
    if err != nil {
        return err
    }

    loader, ok := loaders[strings.ToLower(path.Ext(file))]
    if !ok {
        return fmt.Errorf("unrecognized file type: %s", file)
    }

    var opt options
    for _, o := range opts {
        o(&opt)
    }

    // 如果启用了环境变量,替换配置中的 ${VAR}
    if opt.env {
        return loader([]byte(os.ExpandEnv(string(content))), v)
    }

    return loader(content, v)
}

// MustLoad 加载配置,出错则退出程序
func MustLoad(path string, v any, opts ...Option) {
    if err := Load(path, v, opts...); err != nil {
        log.Fatalf("error: config file %s, %s", path, err.Error())
    }
}

1.3 使用示例

go 复制代码
type Config struct {
    Name string `json:",default=go-zero"`
    Host string `json:",default=0.0.0.0"`
    Port int    `json:",default=8888"`
}

var c Config
conf.MustLoad("config.yaml", &c, conf.UseEnv())

1.4 架构设计

#mermaid-svg-ixPG1nkGdUsdIWwQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ixPG1nkGdUsdIWwQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ixPG1nkGdUsdIWwQ .error-icon{fill:#552222;}#mermaid-svg-ixPG1nkGdUsdIWwQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ixPG1nkGdUsdIWwQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ixPG1nkGdUsdIWwQ .marker.cross{stroke:#333333;}#mermaid-svg-ixPG1nkGdUsdIWwQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ixPG1nkGdUsdIWwQ p{margin:0;}#mermaid-svg-ixPG1nkGdUsdIWwQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ixPG1nkGdUsdIWwQ .cluster-label text{fill:#333;}#mermaid-svg-ixPG1nkGdUsdIWwQ .cluster-label span{color:#333;}#mermaid-svg-ixPG1nkGdUsdIWwQ .cluster-label span p{background-color:transparent;}#mermaid-svg-ixPG1nkGdUsdIWwQ .label text,#mermaid-svg-ixPG1nkGdUsdIWwQ span{fill:#333;color:#333;}#mermaid-svg-ixPG1nkGdUsdIWwQ .node rect,#mermaid-svg-ixPG1nkGdUsdIWwQ .node circle,#mermaid-svg-ixPG1nkGdUsdIWwQ .node ellipse,#mermaid-svg-ixPG1nkGdUsdIWwQ .node polygon,#mermaid-svg-ixPG1nkGdUsdIWwQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ixPG1nkGdUsdIWwQ .rough-node .label text,#mermaid-svg-ixPG1nkGdUsdIWwQ .node .label text,#mermaid-svg-ixPG1nkGdUsdIWwQ .image-shape .label,#mermaid-svg-ixPG1nkGdUsdIWwQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-ixPG1nkGdUsdIWwQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ixPG1nkGdUsdIWwQ .rough-node .label,#mermaid-svg-ixPG1nkGdUsdIWwQ .node .label,#mermaid-svg-ixPG1nkGdUsdIWwQ .image-shape .label,#mermaid-svg-ixPG1nkGdUsdIWwQ .icon-shape .label{text-align:center;}#mermaid-svg-ixPG1nkGdUsdIWwQ .node.clickable{cursor:pointer;}#mermaid-svg-ixPG1nkGdUsdIWwQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ixPG1nkGdUsdIWwQ .arrowheadPath{fill:#333333;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ixPG1nkGdUsdIWwQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ixPG1nkGdUsdIWwQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ixPG1nkGdUsdIWwQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ixPG1nkGdUsdIWwQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ixPG1nkGdUsdIWwQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ixPG1nkGdUsdIWwQ .cluster text{fill:#333;}#mermaid-svg-ixPG1nkGdUsdIWwQ .cluster span{color:#333;}#mermaid-svg-ixPG1nkGdUsdIWwQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ixPG1nkGdUsdIWwQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ixPG1nkGdUsdIWwQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-ixPG1nkGdUsdIWwQ .icon-shape,#mermaid-svg-ixPG1nkGdUsdIWwQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ixPG1nkGdUsdIWwQ .icon-shape p,#mermaid-svg-ixPG1nkGdUsdIWwQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ixPG1nkGdUsdIWwQ .icon-shape .label rect,#mermaid-svg-ixPG1nkGdUsdIWwQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ixPG1nkGdUsdIWwQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ixPG1nkGdUsdIWwQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ixPG1nkGdUsdIWwQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Load
.json
.yaml
.toml
配置文件
文件扩展名
LoadFromJsonBytes
LoadFromYamlBytes
LoadFromTomlBytes
转统一JSON
反射映射结构体
validate验证

2. 日志系统 (logx)

2.1 功能概述

logx 提供分级日志(Debug、Info、Slow、Error)、结构化字段、链路追踪集成、文件自动切割等功能。

2.2 Logger 接口

go 复制代码
// 文件路径: core/logx/logger.go

type Logger interface {
    // Debug 级别
    Debug(...any)
    Debugf(string, ...any)
    Debugv(any)
    Debugw(string, ...LogField)
    
    // Info 级别
    Info(...any)
    Infof(string, ...any)
    Infov(any)
    Infow(string, ...LogField)
    
    // Slow 级别 - 用于慢查询日志
    Slow(...any)
    Slowf(string, ...any)
    Slowv(any)
    Sloww(string, ...LogField)
    
    // Error 级别
    Error(...any)
    Errorf(string, ...any)
    Errorv(any)
    Errorw(string, ...LogField)
    
    // 上下文关联
    WithContext(ctx context.Context) Logger
    WithFields(fields ...LogField) Logger
}

2.3 日志配置

yaml 复制代码
Log:
  Mode: file           # console | file | volume
  Path: logs
  Level: info          # debug | info | error | severe
  Compress: true       # 是否压缩旧日志
  KeepDays: 7          # 保留天数
  StackCoolDownMillis: 100

logx

English | 简体中文

logx 配置

go 复制代码
type LogConf struct {
	ServiceName         string              `json:",optional"`
	Mode                string              `json:",default=console,options=[console,file,volume]"`
	Encoding            string              `json:",default=json,options=[json,plain]"`
	TimeFormat          string              `json:",optional"`
	Path                string              `json:",default=logs"`
	Level               string              `json:",default=info,options=[info,error,severe]"`
	Compress            bool                `json:",optional"`
	KeepDays            int                 `json:",optional"`
	StackCooldownMillis int                 `json:",default=100"`
	MaxBackups          int                 `json:",default=0"`
	MaxSize             int                 `json:",default=0"`
	Rotation            string              `json:",default=daily,options=[daily,size]"`
}
  • ServiceName:设置服务名称,可选。在 volume 模式下,该名称用于生成日志文件。在 rest/zrpc 服务中,名称将被自动设置为 restzrpc 的名称。
  • Mode:输出日志的模式,默认是 console
    • console 模式将日志写到 stdout/stderr
    • file 模式将日志写到 Path 指定目录的文件中
    • volume 模式在 docker 中使用,将日志写入挂载的卷中
  • Encoding: 指示如何对日志进行编码,默认是 json
    • json模式以 json 格式写日志
    • plain模式用纯文本写日志,并带有终端颜色显示
  • TimeFormat:自定义时间格式,可选。默认是 2006-01-02T15:04:05.000Z07:00
  • Path:设置日志路径,默认为 logs
  • Level: 用于过滤日志的日志级别。默认为 info
    • info,所有日志都被写入
    • error, info 的日志被丢弃
    • severe, infoerror 日志被丢弃,只有 severe 日志被写入
  • Compress: 是否压缩日志文件,只在 file 模式下工作
  • KeepDays:日志文件被保留多少天,在给定的天数之后,过期的文件将被自动删除。对 console 模式没有影响
  • StackCooldownMillis:多少毫秒后再次写入堆栈跟踪。用来避免堆栈跟踪日志过多
  • MaxBackups: 多少个日志文件备份将被保存。0代表所有备份都被保存。当Rotation被设置为size时才会起作用。注意:KeepDays选项的优先级会比MaxBackups高,即使MaxBackups被设置为0,当达到KeepDays上限时备份文件同样会被删除。
  • MaxSize: 当前被写入的日志文件最大可占用多少空间。0代表没有上限。单位为MB。当Rotation被设置为size时才会起作用。
  • Rotation: 日志轮转策略类型。默认为daily(按天轮转)。
    • daily 按天轮转。
    • size 按日志大小轮转。

打印日志方法

go 复制代码
type Logger interface {
	// Error logs a message at error level.
	Error(...any)
	// Errorf logs a message at error level.
	Errorf(string, ...any)
	// Errorv logs a message at error level.
	Errorv(any)
	// Errorw logs a message at error level.
	Errorw(string, ...LogField)
	// Info logs a message at info level.
	Info(...any)
	// Infof logs a message at info level.
	Infof(string, ...any)
	// Infov logs a message at info level.
	Infov(any)
	// Infow logs a message at info level.
	Infow(string, ...LogField)
	// Slow logs a message at slow level.
	Slow(...any)
	// Slowf logs a message at slow level.
	Slowf(string, ...any)
	// Slowv logs a message at slow level.
	Slowv(any)
	// Sloww logs a message at slow level.
	Sloww(string, ...LogField)
	// WithContext returns a new logger with the given context.
	WithContext(context.Context) Logger
	// WithDuration returns a new logger with the given duration.
	WithDuration(time.Duration) Logger
}
  • Error, Info, Slow: 将任何类型的信息写进日志,使用 fmt.Sprint(...) 来转换为 string
  • Errorf, Infof, Slowf: 将指定格式的信息写入日志
  • Errorv, Infov, Slowv: 将任何类型的信息写入日志,用 json marshal 编码
  • Errorw, Infow, Sloww: 写日志,并带上给定的 key:value 字段
  • WithContext:将给定的 ctx 注入日志信息,例如用于记录 trace-idspan-id
  • WithDuration: 将指定的时间写入日志信息中,字段名为 duration

与第三方日志库集成

对于其它的日志库,请参考上面示例实现,并欢迎提交 PRhttps://github.com/zeromicro/zero-contrib

将日志写到指定的存储

logx定义了两个接口,方便自定义 logx,将日志写入任何存储。

  • logx.NewWriter(w io.Writer)
  • logx.SetWriter(write logx.Writer)

例如,如果我们想把日志写进kafka,而不是控制台或文件,我们可以像下面这样做。

go 复制代码
type KafkaWriter struct {
	Pusher *kq.Pusher
}

func NewKafkaWriter(pusher *kq.Pusher) *KafkaWriter {
	return &KafkaWriter{
		Pusher: pusher,
	}
}

func (w *KafkaWriter) Write(p []byte) (n int, err error) {
	// writing log with newlines, trim them.
	if err := w.Pusher.Push(strings.TrimSpace(string(p))); err != nil {
		return 0, err
	}

	return len(p), nil
}

func main() {
	pusher := kq.NewPusher([]string{"localhost:9092"}, "go-zero")
	defer pusher.Close()

	writer := logx.NewWriter(NewKafkaWriter(pusher))
	logx.SetWriter(writer)
  
	// more code
}

完整代码:https://github.com/zeromicro/zero-examples/blob/main/logx/tokafka/main.go

过滤敏感字段

如果我们需要防止 password 字段被记录下来,我们可以像下面这样实现。

go 复制代码
type (
	Message struct {
		Name     string
		Password string
		Message  string
	}

	SensitiveLogger struct {
		logx.Writer
	}
)

func NewSensitiveLogger(writer logx.Writer) *SensitiveLogger {
	return &SensitiveLogger{
		Writer: writer,
	}
}

func (l *SensitiveLogger) Info(msg any, fields ...logx.LogField) {
	if m, ok := msg.(Message); ok {
		l.Writer.Info(Message{
			Name:     m.Name,
			Password: "******",
			Message:  m.Message,
		}, fields...)
	} else {
		l.Writer.Info(msg, fields...)
	}
}

func main() {
	// setup logx to make sure originalWriter not nil,
	// the injected writer is only for filtering, like a middleware.

	originalWriter := logx.Reset()
	writer := NewSensitiveLogger(originalWriter)
	logx.SetWriter(writer)

	logx.Infov(Message{
		Name:     "foo",
		Password: "shouldNotAppear",
		Message:  "bar",
	})
  
	// more code
}

完整代码:https://github.com/zeromicro/zero-examples/blob/main/logx/filterfields/main.go

更多示例

https://github.com/zeromicro/zero-examples/tree/main/logx

3. 进程管理与信号处理 (proc)

3.1 平滑关闭机制

go 复制代码
// 文件路径: core/proc/shutdown.go

const (
    defaultWrapUpTime = time.Second       // 收尾等待时间
    defaultWaitTime   = 5500 * time.Millisecond  // 强制退出等待时间
)

var (
    wrapUpListeners   = new(listenerManager)   // 收尾监听器
    shutdownListeners = new(listenerManager)   // 关闭监听器
)

// AddShutdownListener 添加关闭监听器
func AddShutdownListener(fn func()) (waitForCalled func()) {
    return shutdownListeners.addListener(fn)
}

// AddWrapUpListener 添加收尾监听器
func AddWrapUpListener(fn func()) (waitForCalled func()) {
    return wrapUpListeners.addListener(fn)
}

// gracefulStop 处理优雅关闭
func gracefulStop(signals chan os.Signal, sig syscall.Signal) {
    signal.Stop(signals)
    
    logx.Infof("Got signal %d, shutting down...", sig)
    go wrapUpListeners.notifyListeners()  // 先执行收尾
    
    time.Sleep(wrapUpTime)
    go shutdownListeners.notifyListeners()  // 再执行关闭
    
    time.Sleep(waitTime - wrapUpTime)
    logx.Infof("Still alive after %v, going to force kill...", waitTime)
    _ = syscall.Kill(syscall.Getpid(), sig)
}

3.2 信号监听

go 复制代码
// 文件路径: core/proc/signals.go

func init() {
    go func() {
        signals := make(chan os.Signal, 1)
        signal.Notify(signals, syscall.SIGUSR1, syscall.SIGUSR2, 
                      syscall.SIGTERM, syscall.SIGINT)
        
        for {
            v := <-signals
            switch v {
            case syscall.SIGUSR1:
                dumpGoroutines(fileCreator{})  // 打印 goroutine 堆栈
            case syscall.SIGUSR2:
                profiler := StartProfile()      // 启动性能分析
                time.AfterFunc(profileDuration, profiler.Stop)
            case syscall.SIGTERM, syscall.SIGINT:
                stopOnSignal()
                gracefulStop(signals, v)        // 优雅关闭
            }
        }
    }()
}

3.3 使用示例

go 复制代码
func main() {
    // 注册收尾回调
    proc.AddWrapUpListener(func() {
        logx.Info("Closing database connections...")
        db.Close()
    })
    
    // 注册关闭回调
    proc.AddShutdownListener(func() {
        logx.Info("Shutting down server...")
        server.Stop()
    })
    
    // 启动服务
    server.Start()
}

4. 并发原语 (syncx)

4.1 资源池 (Pool)

go 复制代码
// 文件路径: core/syncx/pool.go

type Pool struct {
    limit   int                // 最大资源数
    created int                // 已创建资源数
    maxAge  time.Duration      // 资源最大存活时间
    lock    sync.Locker
    cond    *sync.Cond         // 条件变量
    head    *node              // 空闲链表头
    create  func() any         // 创建资源函数
    destroy func(any)          // 销毁资源函数
}

// NewPool 创建资源池
func NewPool(n int, create func() any, destroy func(any), opts ...PoolOption) *Pool {
    lock := new(sync.Mutex)
    pool := &Pool{
        limit:   n,
        lock:    lock,
        cond:    sync.NewCond(lock),
        create:  create,
        destroy: destroy,
    }
    return pool
}

// Get 获取资源
func (p *Pool) Get() any {
    p.lock.Lock()
    defer p.lock.Unlock()
    
    for {
        // 先尝试从链表获取
        if p.head != nil {
            head := p.head
            p.head = head.next
            // 检查资源是否过期
            if p.maxAge > 0 && head.lastUsed+p.maxAge < timex.Now() {
                p.created--
                p.destroy(head.item)
                continue  // 过期了,继续循环
            }
            return head.item
        }
        
        // 如果未达上限,创建新资源
        if p.created < p.limit {
            p.created++
            return p.create()
        }
        
        // 等待资源释放
        p.cond.Wait()
    }
}

// Put 归还资源
func (p *Pool) Put(x any) {
    if x == nil {
        return
    }
    
    p.lock.Lock()
    defer p.lock.Unlock()
    
    p.head = &node{
        item:     x,
        next:     p.head,
        lastUsed: timex.Now(),
    }
    p.cond.Signal()  // 唤醒等待者
}

4.2 Pool 设计特点

  1. 有界资源池:通过 limit 控制最大资源数
  2. 资源过期:支持 maxAge 设置资源存活时间
  3. 条件变量:使用 sync.Cond 实现等待唤醒
  4. 销毁钩子:资源销毁时可执行自定义清理逻辑

Factory LinkedList Pool Caller Factory LinkedList Pool Caller #mermaid-svg-Ytq7mWI5ueaCsexA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ytq7mWI5ueaCsexA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ytq7mWI5ueaCsexA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ytq7mWI5ueaCsexA .error-icon{fill:#552222;}#mermaid-svg-Ytq7mWI5ueaCsexA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ytq7mWI5ueaCsexA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ytq7mWI5ueaCsexA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ytq7mWI5ueaCsexA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ytq7mWI5ueaCsexA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ytq7mWI5ueaCsexA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ytq7mWI5ueaCsexA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ytq7mWI5ueaCsexA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ytq7mWI5ueaCsexA .marker.cross{stroke:#333333;}#mermaid-svg-Ytq7mWI5ueaCsexA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ytq7mWI5ueaCsexA p{margin:0;}#mermaid-svg-Ytq7mWI5ueaCsexA .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Ytq7mWI5ueaCsexA text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Ytq7mWI5ueaCsexA .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Ytq7mWI5ueaCsexA .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Ytq7mWI5ueaCsexA .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Ytq7mWI5ueaCsexA .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Ytq7mWI5ueaCsexA #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Ytq7mWI5ueaCsexA .sequenceNumber{fill:white;}#mermaid-svg-Ytq7mWI5ueaCsexA #sequencenumber{fill:#333;}#mermaid-svg-Ytq7mWI5ueaCsexA #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Ytq7mWI5ueaCsexA .messageText{fill:#333;stroke:none;}#mermaid-svg-Ytq7mWI5ueaCsexA .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Ytq7mWI5ueaCsexA .labelText,#mermaid-svg-Ytq7mWI5ueaCsexA .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Ytq7mWI5ueaCsexA .loopText,#mermaid-svg-Ytq7mWI5ueaCsexA .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Ytq7mWI5ueaCsexA .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Ytq7mWI5ueaCsexA .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Ytq7mWI5ueaCsexA .noteText,#mermaid-svg-Ytq7mWI5ueaCsexA .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Ytq7mWI5ueaCsexA .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Ytq7mWI5ueaCsexA .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Ytq7mWI5ueaCsexA .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Ytq7mWI5ueaCsexA .actorPopupMenu{position:absolute;}#mermaid-svg-Ytq7mWI5ueaCsexA .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Ytq7mWI5ueaCsexA .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Ytq7mWI5ueaCsexA .actor-man circle,#mermaid-svg-Ytq7mWI5ueaCsexA line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Ytq7mWI5ueaCsexA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt Resource expired alt created \< limit alt List not empty List empty Get() Lock Pop head Return expired destroy() Continue loop Return resource create() Return new resource cond.Wait() Put(x) Push to head cond.Signal()

5. 滑动窗口 (collection/RollingWindow)

5.1 核心实现

go 复制代码
// 文件路径: core/collection/rollingwindow.go

type RollingWindow[T Numerical, B BucketInterface[T]] struct {
    lock          sync.RWMutex
    size          int                // 桶数量
    win           *window[T, B]      // 窗口
    interval      time.Duration      // 每个桶的时间间隔
    offset        int                // 当前桶偏移量
    ignoreCurrent bool               // 是否忽略当前桶
    lastTime      time.Duration      // 最后更新时间
}

// Add 添加值到当前桶
func (rw *RollingWindow[T, B]) Add(v T) {
    rw.lock.Lock()
    defer rw.lock.Unlock()
    rw.updateOffset()
    rw.win.add(rw.offset, v)
}

// Reduce 聚合所有桶数据
func (rw *RollingWindow[T, B]) Reduce(fn func(b B)) {
    rw.lock.RLock()
    defer rw.lock.RUnlock()
    
    var diff int
    span := rw.span()
    // 是否忽略当前桶(因为数据可能不完整)
    if span == 0 && rw.ignoreCurrent {
        diff = rw.size - 1
    } else {
        diff = rw.size - span
    }
    if diff > 0 {
        offset := (rw.offset + span + 1) % rw.size
        rw.win.reduce(offset, diff, fn)
    }
}

// updateOffset 更新桶偏移量,重置过期桶
func (rw *RollingWindow[T, B]) updateOffset() {
    span := rw.span()
    if span <= 0 {
        return
    }
    
    offset := rw.offset
    // 重置过期的桶
    for i := 0; i < span; i++ {
        rw.win.resetBucket((offset + i + 1) % rw.size)
    }
    
    rw.offset = (offset + span) % rw.size
    now := timex.Now()
    // 对齐到时间边界
    rw.lastTime = now - (now-rw.lastTime)%rw.interval
}

// Bucket 定义桶结构
type Bucket[T Numerical] struct {
    Sum   T     // 总和
    Count int64 // 计数
}

5.2 滑动窗口应用场景

  1. 熔断器:统计窗口内的失败率
  2. 限流器:统计窗口内的请求数
  3. 指标统计:QPS、延迟分布等

#mermaid-svg-KZCt5Xj65Hwaj1UT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KZCt5Xj65Hwaj1UT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KZCt5Xj65Hwaj1UT .error-icon{fill:#552222;}#mermaid-svg-KZCt5Xj65Hwaj1UT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KZCt5Xj65Hwaj1UT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KZCt5Xj65Hwaj1UT .marker.cross{stroke:#333333;}#mermaid-svg-KZCt5Xj65Hwaj1UT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KZCt5Xj65Hwaj1UT p{margin:0;}#mermaid-svg-KZCt5Xj65Hwaj1UT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KZCt5Xj65Hwaj1UT .cluster-label text{fill:#333;}#mermaid-svg-KZCt5Xj65Hwaj1UT .cluster-label span{color:#333;}#mermaid-svg-KZCt5Xj65Hwaj1UT .cluster-label span p{background-color:transparent;}#mermaid-svg-KZCt5Xj65Hwaj1UT .label text,#mermaid-svg-KZCt5Xj65Hwaj1UT span{fill:#333;color:#333;}#mermaid-svg-KZCt5Xj65Hwaj1UT .node rect,#mermaid-svg-KZCt5Xj65Hwaj1UT .node circle,#mermaid-svg-KZCt5Xj65Hwaj1UT .node ellipse,#mermaid-svg-KZCt5Xj65Hwaj1UT .node polygon,#mermaid-svg-KZCt5Xj65Hwaj1UT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KZCt5Xj65Hwaj1UT .rough-node .label text,#mermaid-svg-KZCt5Xj65Hwaj1UT .node .label text,#mermaid-svg-KZCt5Xj65Hwaj1UT .image-shape .label,#mermaid-svg-KZCt5Xj65Hwaj1UT .icon-shape .label{text-anchor:middle;}#mermaid-svg-KZCt5Xj65Hwaj1UT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KZCt5Xj65Hwaj1UT .rough-node .label,#mermaid-svg-KZCt5Xj65Hwaj1UT .node .label,#mermaid-svg-KZCt5Xj65Hwaj1UT .image-shape .label,#mermaid-svg-KZCt5Xj65Hwaj1UT .icon-shape .label{text-align:center;}#mermaid-svg-KZCt5Xj65Hwaj1UT .node.clickable{cursor:pointer;}#mermaid-svg-KZCt5Xj65Hwaj1UT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KZCt5Xj65Hwaj1UT .arrowheadPath{fill:#333333;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KZCt5Xj65Hwaj1UT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KZCt5Xj65Hwaj1UT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KZCt5Xj65Hwaj1UT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KZCt5Xj65Hwaj1UT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KZCt5Xj65Hwaj1UT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KZCt5Xj65Hwaj1UT .cluster text{fill:#333;}#mermaid-svg-KZCt5Xj65Hwaj1UT .cluster span{color:#333;}#mermaid-svg-KZCt5Xj65Hwaj1UT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KZCt5Xj65Hwaj1UT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KZCt5Xj65Hwaj1UT rect.text{fill:none;stroke-width:0;}#mermaid-svg-KZCt5Xj65Hwaj1UT .icon-shape,#mermaid-svg-KZCt5Xj65Hwaj1UT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KZCt5Xj65Hwaj1UT .icon-shape p,#mermaid-svg-KZCt5Xj65Hwaj1UT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KZCt5Xj65Hwaj1UT .icon-shape .label rect,#mermaid-svg-KZCt5Xj65Hwaj1UT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KZCt5Xj65Hwaj1UT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KZCt5Xj65Hwaj1UT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KZCt5Xj65Hwaj1UT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 时间线 (秒)
过期, 重置
Add()
Reduce()
第1秒

10, 15, 20

第2秒

12, 18

第3秒

14 <-- 当前
第4秒

(待填充)
第5秒

(待填充)
NewRequest
QPS = 1

Avg = 14

6. 限流器 (limit)

6.1 令牌桶限流器

go 复制代码
// 文件路径: core/limit/tokenlimit.go

type TokenLimiter struct {
    rate           int              // 每秒生成令牌数
    burst          int              // 桶容量
    store          *redis.Redis     // Redis 存储
    tokenKey       string
    timestampKey   string
    rescueLock     sync.Mutex
    redisAlive     uint32           // Redis 是否存活
    monitorStarted bool
    rescueLimiter  *xrate.Limiter   // 本地降级限流器
}

// AllowN 检查是否允许 n 个请求
func (lim *TokenLimiter) AllowN(now time.Time, n int) bool {
    return lim.reserveN(context.Background(), now, n)
}

func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) bool {
    // 如果 Redis 挂了,降级到本地限流器
    if atomic.LoadUint32(&lim.redisAlive) == 0 {
        return lim.rescueLimiter.AllowN(now, n)
    }
    
    // 使用 Lua 脚本原子执行令牌生成和消耗
    resp, err := lim.store.ScriptRunCtx(ctx,
        tokenScript,
        []string{lim.tokenKey, lim.timestampKey},
        []string{
            strconv.Itoa(lim.rate),
            strconv.Itoa(lim.burst),
            strconv.FormatInt(now.Unix(), 10),
            strconv.Itoa(n),
        })
    
    if err != nil {
        logx.Errorf("fail to use rate limiter: %s, use in-process limiter for rescue", err)
        lim.startMonitor()
        return lim.rescueLimiter.AllowN(now, n)
    }
    
    code, ok := resp.(int64)
    return code == 1
}

6.2 降级策略设计

#mermaid-svg-0ZYdijJ0yWUteb0i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0ZYdijJ0yWUteb0i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0ZYdijJ0yWUteb0i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0ZYdijJ0yWUteb0i .error-icon{fill:#552222;}#mermaid-svg-0ZYdijJ0yWUteb0i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0ZYdijJ0yWUteb0i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0ZYdijJ0yWUteb0i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0ZYdijJ0yWUteb0i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0ZYdijJ0yWUteb0i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0ZYdijJ0yWUteb0i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0ZYdijJ0yWUteb0i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0ZYdijJ0yWUteb0i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0ZYdijJ0yWUteb0i .marker.cross{stroke:#333333;}#mermaid-svg-0ZYdijJ0yWUteb0i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0ZYdijJ0yWUteb0i p{margin:0;}#mermaid-svg-0ZYdijJ0yWUteb0i .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0ZYdijJ0yWUteb0i .cluster-label text{fill:#333;}#mermaid-svg-0ZYdijJ0yWUteb0i .cluster-label span{color:#333;}#mermaid-svg-0ZYdijJ0yWUteb0i .cluster-label span p{background-color:transparent;}#mermaid-svg-0ZYdijJ0yWUteb0i .label text,#mermaid-svg-0ZYdijJ0yWUteb0i span{fill:#333;color:#333;}#mermaid-svg-0ZYdijJ0yWUteb0i .node rect,#mermaid-svg-0ZYdijJ0yWUteb0i .node circle,#mermaid-svg-0ZYdijJ0yWUteb0i .node ellipse,#mermaid-svg-0ZYdijJ0yWUteb0i .node polygon,#mermaid-svg-0ZYdijJ0yWUteb0i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0ZYdijJ0yWUteb0i .rough-node .label text,#mermaid-svg-0ZYdijJ0yWUteb0i .node .label text,#mermaid-svg-0ZYdijJ0yWUteb0i .image-shape .label,#mermaid-svg-0ZYdijJ0yWUteb0i .icon-shape .label{text-anchor:middle;}#mermaid-svg-0ZYdijJ0yWUteb0i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0ZYdijJ0yWUteb0i .rough-node .label,#mermaid-svg-0ZYdijJ0yWUteb0i .node .label,#mermaid-svg-0ZYdijJ0yWUteb0i .image-shape .label,#mermaid-svg-0ZYdijJ0yWUteb0i .icon-shape .label{text-align:center;}#mermaid-svg-0ZYdijJ0yWUteb0i .node.clickable{cursor:pointer;}#mermaid-svg-0ZYdijJ0yWUteb0i .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0ZYdijJ0yWUteb0i .arrowheadPath{fill:#333333;}#mermaid-svg-0ZYdijJ0yWUteb0i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0ZYdijJ0yWUteb0i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0ZYdijJ0yWUteb0i .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0ZYdijJ0yWUteb0i .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0ZYdijJ0yWUteb0i .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0ZYdijJ0yWUteb0i .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0ZYdijJ0yWUteb0i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0ZYdijJ0yWUteb0i .cluster text{fill:#333;}#mermaid-svg-0ZYdijJ0yWUteb0i .cluster span{color:#333;}#mermaid-svg-0ZYdijJ0yWUteb0i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0ZYdijJ0yWUteb0i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0ZYdijJ0yWUteb0i rect.text{fill:none;stroke-width:0;}#mermaid-svg-0ZYdijJ0yWUteb0i .icon-shape,#mermaid-svg-0ZYdijJ0yWUteb0i .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0ZYdijJ0yWUteb0i .icon-shape p,#mermaid-svg-0ZYdijJ0yWUteb0i .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0ZYdijJ0yWUteb0i .icon-shape .label rect,#mermaid-svg-0ZYdijJ0yWUteb0i .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0ZYdijJ0yWUteb0i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0ZYdijJ0yWUteb0i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0ZYdijJ0yWUteb0i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
Yes
No
恢复
请求
RedisAlive?
Lua脚本原子执行
本地限流器
成功?
允许请求
启动Monitor
定期Ping Redis
重置redisAlive=1

7. 基础组件架构全景

#mermaid-svg-LiSLS7EpZmF1jPj4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LiSLS7EpZmF1jPj4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LiSLS7EpZmF1jPj4 .error-icon{fill:#552222;}#mermaid-svg-LiSLS7EpZmF1jPj4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LiSLS7EpZmF1jPj4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LiSLS7EpZmF1jPj4 .marker.cross{stroke:#333333;}#mermaid-svg-LiSLS7EpZmF1jPj4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LiSLS7EpZmF1jPj4 p{margin:0;}#mermaid-svg-LiSLS7EpZmF1jPj4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LiSLS7EpZmF1jPj4 .cluster-label text{fill:#333;}#mermaid-svg-LiSLS7EpZmF1jPj4 .cluster-label span{color:#333;}#mermaid-svg-LiSLS7EpZmF1jPj4 .cluster-label span p{background-color:transparent;}#mermaid-svg-LiSLS7EpZmF1jPj4 .label text,#mermaid-svg-LiSLS7EpZmF1jPj4 span{fill:#333;color:#333;}#mermaid-svg-LiSLS7EpZmF1jPj4 .node rect,#mermaid-svg-LiSLS7EpZmF1jPj4 .node circle,#mermaid-svg-LiSLS7EpZmF1jPj4 .node ellipse,#mermaid-svg-LiSLS7EpZmF1jPj4 .node polygon,#mermaid-svg-LiSLS7EpZmF1jPj4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LiSLS7EpZmF1jPj4 .rough-node .label text,#mermaid-svg-LiSLS7EpZmF1jPj4 .node .label text,#mermaid-svg-LiSLS7EpZmF1jPj4 .image-shape .label,#mermaid-svg-LiSLS7EpZmF1jPj4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-LiSLS7EpZmF1jPj4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LiSLS7EpZmF1jPj4 .rough-node .label,#mermaid-svg-LiSLS7EpZmF1jPj4 .node .label,#mermaid-svg-LiSLS7EpZmF1jPj4 .image-shape .label,#mermaid-svg-LiSLS7EpZmF1jPj4 .icon-shape .label{text-align:center;}#mermaid-svg-LiSLS7EpZmF1jPj4 .node.clickable{cursor:pointer;}#mermaid-svg-LiSLS7EpZmF1jPj4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LiSLS7EpZmF1jPj4 .arrowheadPath{fill:#333333;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LiSLS7EpZmF1jPj4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LiSLS7EpZmF1jPj4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LiSLS7EpZmF1jPj4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LiSLS7EpZmF1jPj4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LiSLS7EpZmF1jPj4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LiSLS7EpZmF1jPj4 .cluster text{fill:#333;}#mermaid-svg-LiSLS7EpZmF1jPj4 .cluster span{color:#333;}#mermaid-svg-LiSLS7EpZmF1jPj4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LiSLS7EpZmF1jPj4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LiSLS7EpZmF1jPj4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-LiSLS7EpZmF1jPj4 .icon-shape,#mermaid-svg-LiSLS7EpZmF1jPj4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LiSLS7EpZmF1jPj4 .icon-shape p,#mermaid-svg-LiSLS7EpZmF1jPj4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LiSLS7EpZmF1jPj4 .icon-shape .label rect,#mermaid-svg-LiSLS7EpZmF1jPj4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LiSLS7EpZmF1jPj4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LiSLS7EpZmF1jPj4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LiSLS7EpZmF1jPj4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 服务层
功能组件层
工具层
syncx

(并发原语)
timex

(时间工具)
stringx

(字符串)
mathx

(数学)
conf

(配置管理)
logx

(日志系统)
proc

(进程管理)
collection

(集合/滑动窗口)
limit

(限流)
breaker

(熔断)
rest

(HTTP服务)
zrpc

(RPC服务)

8. 最佳实践

8.1 配置管理最佳实践

go 复制代码
// 推荐使用结构体标签定义默认值
type Config struct {
    Log     logx.LogConf
    Rest    rest.RestConf
    Rpc     zrpc.RpcServerConf
    DB      mysqlx.SqlConfig
    Redis   redis.RedisConfig
}

var c Config
conf.MustLoad("etc/config.yaml", &c, conf.UseEnv())

8.2 平滑关闭最佳实践

go 复制代码
func main() {
    // 1. 先启动资源,后注册关闭回调
    db := initDB()
    redis := initRedis()
    server := initServer()
    
    // 2. 注册收尾回调(先停止接收请求)
    proc.AddWrapUpListener(func() {
        logx.Info("Stopping server accept...")
        server.PauseAccept()
    })
    
    // 3. 注册关闭回调(后释放资源)
    proc.AddShutdownListener(func() {
        logx.Info("Closing resources...")
        server.Stop()
        redis.Close()
        db.Close()
    })
    
    // 4. 启动服务
    server.Start()
}

三、HTTP 网关 & API 服务底层实现

本章深入解析 go-zero 的 HTTP 服务实现,包括路由系统、中间件链、请求处理流程等。

1. 整体架构

1.1 核心组件

#mermaid-svg-tqpx1KLawp3BeVFF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tqpx1KLawp3BeVFF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tqpx1KLawp3BeVFF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tqpx1KLawp3BeVFF .error-icon{fill:#552222;}#mermaid-svg-tqpx1KLawp3BeVFF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tqpx1KLawp3BeVFF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tqpx1KLawp3BeVFF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tqpx1KLawp3BeVFF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tqpx1KLawp3BeVFF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tqpx1KLawp3BeVFF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tqpx1KLawp3BeVFF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tqpx1KLawp3BeVFF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tqpx1KLawp3BeVFF .marker.cross{stroke:#333333;}#mermaid-svg-tqpx1KLawp3BeVFF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tqpx1KLawp3BeVFF p{margin:0;}#mermaid-svg-tqpx1KLawp3BeVFF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tqpx1KLawp3BeVFF .cluster-label text{fill:#333;}#mermaid-svg-tqpx1KLawp3BeVFF .cluster-label span{color:#333;}#mermaid-svg-tqpx1KLawp3BeVFF .cluster-label span p{background-color:transparent;}#mermaid-svg-tqpx1KLawp3BeVFF .label text,#mermaid-svg-tqpx1KLawp3BeVFF span{fill:#333;color:#333;}#mermaid-svg-tqpx1KLawp3BeVFF .node rect,#mermaid-svg-tqpx1KLawp3BeVFF .node circle,#mermaid-svg-tqpx1KLawp3BeVFF .node ellipse,#mermaid-svg-tqpx1KLawp3BeVFF .node polygon,#mermaid-svg-tqpx1KLawp3BeVFF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tqpx1KLawp3BeVFF .rough-node .label text,#mermaid-svg-tqpx1KLawp3BeVFF .node .label text,#mermaid-svg-tqpx1KLawp3BeVFF .image-shape .label,#mermaid-svg-tqpx1KLawp3BeVFF .icon-shape .label{text-anchor:middle;}#mermaid-svg-tqpx1KLawp3BeVFF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tqpx1KLawp3BeVFF .rough-node .label,#mermaid-svg-tqpx1KLawp3BeVFF .node .label,#mermaid-svg-tqpx1KLawp3BeVFF .image-shape .label,#mermaid-svg-tqpx1KLawp3BeVFF .icon-shape .label{text-align:center;}#mermaid-svg-tqpx1KLawp3BeVFF .node.clickable{cursor:pointer;}#mermaid-svg-tqpx1KLawp3BeVFF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tqpx1KLawp3BeVFF .arrowheadPath{fill:#333333;}#mermaid-svg-tqpx1KLawp3BeVFF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tqpx1KLawp3BeVFF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tqpx1KLawp3BeVFF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tqpx1KLawp3BeVFF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tqpx1KLawp3BeVFF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tqpx1KLawp3BeVFF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tqpx1KLawp3BeVFF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tqpx1KLawp3BeVFF .cluster text{fill:#333;}#mermaid-svg-tqpx1KLawp3BeVFF .cluster span{color:#333;}#mermaid-svg-tqpx1KLawp3BeVFF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tqpx1KLawp3BeVFF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tqpx1KLawp3BeVFF rect.text{fill:none;stroke-width:0;}#mermaid-svg-tqpx1KLawp3BeVFF .icon-shape,#mermaid-svg-tqpx1KLawp3BeVFF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tqpx1KLawp3BeVFF .icon-shape p,#mermaid-svg-tqpx1KLawp3BeVFF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tqpx1KLawp3BeVFF .icon-shape .label rect,#mermaid-svg-tqpx1KLawp3BeVFF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tqpx1KLawp3BeVFF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tqpx1KLawp3BeVFF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tqpx1KLawp3BeVFF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 中间件层
路由层
入口层
拥有
绑定路由
存储路径
构建中间件
串联
串联
串联
串联
串联
串联
Server
engine
patRouter
search.Tree
chain.Chain
TraceHandler
LogHandler
BreakerHandler
SheddingHandler
TimeoutHandler
RecoverHandler

1.2 Server 结构体

go 复制代码
// 文件路径: rest/server.go

type Server struct {
    ngin   *engine          // 核心引擎
    router httpx.Router     // 路由实现
}

// NewServer 创建 HTTP 服务器
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
    if err := c.SetUp(); err != nil {
        return nil, err
    }

    server := &Server{
        ngin:   newEngine(c),
        router: router.NewRouter(),
    }

    opts = append([]RunOption{WithNotFoundHandler(nil)}, opts...)
    for _, opt := range opts {
        opt(server)
    }

    return server, nil
}

// Start 启动服务器
func (s *Server) Start() {
    handleError(s.ngin.start(s.router))
}

// AddRoute 添加路由
func (s *Server) AddRoute(r Route, opts ...RouteOption) {
    s.AddRoutes([]Route{r}, opts...)
}

2. 路由实现

2.1 路由树结构

go 复制代码
// 文件路径: rest/router/patrouter.go

type patRouter struct {
    trees      map[string]*search.Tree  // 每个 HTTP 方法对应一棵树
    notFound   http.Handler
    notAllowed http.Handler
}

// Handle 注册路由
func (pr *patRouter) Handle(method, reqPath string, handler http.Handler) error {
    if !validMethod(method) {
        return ErrInvalidMethod
    }

    if len(reqPath) == 0 || reqPath[0] != '/' {
        return ErrInvalidPath
    }

    cleanPath := path.Clean(reqPath)
    tree, ok := pr.trees[method]
    if ok {
        return tree.Add(cleanPath, handler)
    }

    tree = search.NewTree()
    pr.trees[method] = tree
    return tree.Add(cleanPath, handler)
}

// ServeHTTP 处理请求
func (pr *patRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    reqPath := path.Clean(r.URL.Path)
    if tree, ok := pr.trees[r.Method]; ok {
        if result, ok := tree.Search(reqPath); ok {
            if len(result.Params) > 0 {
                r = pathvar.WithVars(r, result.Params)
            }
            result.Item.(http.Handler).ServeHTTP(w, r)
            return
        }
    }

    // 处理 404 或 405
    allows, ok := pr.methodsAllowed(r.Method, reqPath)
    if !ok {
        pr.handleNotFound(w, r)
        return
    }

    if pr.notAllowed != nil {
        pr.notAllowed.ServeHTTP(w, r)
    } else {
        w.Header().Set(allowHeader, allows)
        w.WriteHeader(http.StatusMethodNotAllowed)
    }
}

2.2 路径匹配

路由树使用前缀树 (Trie) 实现,支持路径参数:

  • /user/:id - 匹配 /user/123
  • /user/*path - 匹配 /user/a/b/c
  • /user/me - 精确匹配,优先级高于参数匹配

#mermaid-svg-LsOdJ5GK6SCNJwgy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LsOdJ5GK6SCNJwgy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LsOdJ5GK6SCNJwgy .error-icon{fill:#552222;}#mermaid-svg-LsOdJ5GK6SCNJwgy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LsOdJ5GK6SCNJwgy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LsOdJ5GK6SCNJwgy .marker.cross{stroke:#333333;}#mermaid-svg-LsOdJ5GK6SCNJwgy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LsOdJ5GK6SCNJwgy p{margin:0;}#mermaid-svg-LsOdJ5GK6SCNJwgy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LsOdJ5GK6SCNJwgy .cluster-label text{fill:#333;}#mermaid-svg-LsOdJ5GK6SCNJwgy .cluster-label span{color:#333;}#mermaid-svg-LsOdJ5GK6SCNJwgy .cluster-label span p{background-color:transparent;}#mermaid-svg-LsOdJ5GK6SCNJwgy .label text,#mermaid-svg-LsOdJ5GK6SCNJwgy span{fill:#333;color:#333;}#mermaid-svg-LsOdJ5GK6SCNJwgy .node rect,#mermaid-svg-LsOdJ5GK6SCNJwgy .node circle,#mermaid-svg-LsOdJ5GK6SCNJwgy .node ellipse,#mermaid-svg-LsOdJ5GK6SCNJwgy .node polygon,#mermaid-svg-LsOdJ5GK6SCNJwgy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LsOdJ5GK6SCNJwgy .rough-node .label text,#mermaid-svg-LsOdJ5GK6SCNJwgy .node .label text,#mermaid-svg-LsOdJ5GK6SCNJwgy .image-shape .label,#mermaid-svg-LsOdJ5GK6SCNJwgy .icon-shape .label{text-anchor:middle;}#mermaid-svg-LsOdJ5GK6SCNJwgy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LsOdJ5GK6SCNJwgy .rough-node .label,#mermaid-svg-LsOdJ5GK6SCNJwgy .node .label,#mermaid-svg-LsOdJ5GK6SCNJwgy .image-shape .label,#mermaid-svg-LsOdJ5GK6SCNJwgy .icon-shape .label{text-align:center;}#mermaid-svg-LsOdJ5GK6SCNJwgy .node.clickable{cursor:pointer;}#mermaid-svg-LsOdJ5GK6SCNJwgy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LsOdJ5GK6SCNJwgy .arrowheadPath{fill:#333333;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LsOdJ5GK6SCNJwgy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LsOdJ5GK6SCNJwgy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LsOdJ5GK6SCNJwgy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LsOdJ5GK6SCNJwgy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LsOdJ5GK6SCNJwgy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LsOdJ5GK6SCNJwgy .cluster text{fill:#333;}#mermaid-svg-LsOdJ5GK6SCNJwgy .cluster span{color:#333;}#mermaid-svg-LsOdJ5GK6SCNJwgy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LsOdJ5GK6SCNJwgy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LsOdJ5GK6SCNJwgy rect.text{fill:none;stroke-width:0;}#mermaid-svg-LsOdJ5GK6SCNJwgy .icon-shape,#mermaid-svg-LsOdJ5GK6SCNJwgy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LsOdJ5GK6SCNJwgy .icon-shape p,#mermaid-svg-LsOdJ5GK6SCNJwgy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LsOdJ5GK6SCNJwgy .icon-shape .label rect,#mermaid-svg-LsOdJ5GK6SCNJwgy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LsOdJ5GK6SCNJwgy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LsOdJ5GK6SCNJwgy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LsOdJ5GK6SCNJwgy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} /
user/
me (精确)
:id (参数)
*path (通配)
/user/me
/user/123
/user/a/b

3. 中间件链

3.1 链设计模式

go 复制代码
// 文件路径: rest/chain/chain.go

type Chain interface {
    Append(handlers ...func(http.Handler) http.Handler) Chain
    Then(handler http.Handler) http.Handler
    ThenFunc(fn http.HandlerFunc) http.Handler
}

type chain struct {
    handlers []func(http.Handler) http.Handler
}

// Append 添加中间件
func (c chain) Append(handlers ...func(http.Handler) http.Handler) Chain {
    size := len(c.handlers) + len(handlers)
    newHandlers := make([]func(http.Handler) http.Handler, size)
    copy(newHandlers, c.handlers)
    copy(newHandlers[len(c.handlers):], handlers)
    return chain{handlers: newHandlers}
}

// Then 构建最终 handler
func (c chain) Then(handler http.Handler) http.Handler {
    for i := len(c.handlers) - 1; i >= 0; i-- {
        handler = c.handlers[i](handler)
    }
    return handler
}

3.2 中间件执行顺序

BusinessHandler RecoverHandler TimeoutHandler SheddingHandler BreakerHandler LogHandler TraceHandler Client BusinessHandler RecoverHandler TimeoutHandler SheddingHandler BreakerHandler LogHandler TraceHandler Client #mermaid-svg-Lo82R19L8xqZtDS1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Lo82R19L8xqZtDS1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Lo82R19L8xqZtDS1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Lo82R19L8xqZtDS1 .error-icon{fill:#552222;}#mermaid-svg-Lo82R19L8xqZtDS1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Lo82R19L8xqZtDS1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Lo82R19L8xqZtDS1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Lo82R19L8xqZtDS1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Lo82R19L8xqZtDS1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Lo82R19L8xqZtDS1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Lo82R19L8xqZtDS1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Lo82R19L8xqZtDS1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Lo82R19L8xqZtDS1 .marker.cross{stroke:#333333;}#mermaid-svg-Lo82R19L8xqZtDS1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Lo82R19L8xqZtDS1 p{margin:0;}#mermaid-svg-Lo82R19L8xqZtDS1 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Lo82R19L8xqZtDS1 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Lo82R19L8xqZtDS1 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Lo82R19L8xqZtDS1 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Lo82R19L8xqZtDS1 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Lo82R19L8xqZtDS1 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Lo82R19L8xqZtDS1 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Lo82R19L8xqZtDS1 .sequenceNumber{fill:white;}#mermaid-svg-Lo82R19L8xqZtDS1 #sequencenumber{fill:#333;}#mermaid-svg-Lo82R19L8xqZtDS1 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Lo82R19L8xqZtDS1 .messageText{fill:#333;stroke:none;}#mermaid-svg-Lo82R19L8xqZtDS1 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Lo82R19L8xqZtDS1 .labelText,#mermaid-svg-Lo82R19L8xqZtDS1 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Lo82R19L8xqZtDS1 .loopText,#mermaid-svg-Lo82R19L8xqZtDS1 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Lo82R19L8xqZtDS1 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Lo82R19L8xqZtDS1 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Lo82R19L8xqZtDS1 .noteText,#mermaid-svg-Lo82R19L8xqZtDS1 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Lo82R19L8xqZtDS1 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Lo82R19L8xqZtDS1 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Lo82R19L8xqZtDS1 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Lo82R19L8xqZtDS1 .actorPopupMenu{position:absolute;}#mermaid-svg-Lo82R19L8xqZtDS1 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Lo82R19L8xqZtDS1 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Lo82R19L8xqZtDS1 .actor-man circle,#mermaid-svg-Lo82R19L8xqZtDS1 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Lo82R19L8xqZtDS1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Request Next Next Next Next Next Execute Response Response Response Response Response Response Response

3.3 内置中间件

go 复制代码
// 文件路径: rest/engine.go

func (ng *engine) buildChainWithNativeMiddlewares(fr featuredRoutes, route Route,
    metrics *stat.Metrics) chain.Chain {
    chn := chain.New()

    if ng.conf.Middlewares.Trace {
        chn = chn.Append(handler.TraceHandler(ng.conf.Name, route.Path))
    }
    if ng.conf.Middlewares.Log {
        chn = chn.Append(ng.getLogHandler())
    }
    if ng.conf.Middlewares.Prometheus {
        chn = chn.Append(handler.PrometheusHandler(route.Path, route.Method))
    }
    if ng.conf.Middlewares.MaxConns {
        chn = chn.Append(handler.MaxConnsHandler(ng.conf.MaxConns))
    }
    if ng.conf.Middlewares.Breaker {
        chn = chn.Append(handler.BreakerHandler(route.Method, route.Path, metrics))
    }
    if ng.conf.Middlewares.Shedding {
        chn = chn.Append(handler.SheddingHandler(ng.getShedder(fr.priority), metrics))
    }
    if ng.conf.Middlewares.Timeout {
        chn = chn.Append(handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)))
    }
    if ng.conf.Middlewares.Recover {
        chn = chn.Append(handler.RecoverHandler)
    }
    if ng.conf.Middlewares.Metrics {
        chn = chn.Append(handler.MetricHandler(metrics))
    }

    return chn
}

4. 请求处理流程

4.1 完整流程图

#mermaid-svg-1nOgEo07j1dZYjZi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1nOgEo07j1dZYjZi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1nOgEo07j1dZYjZi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1nOgEo07j1dZYjZi .error-icon{fill:#552222;}#mermaid-svg-1nOgEo07j1dZYjZi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1nOgEo07j1dZYjZi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1nOgEo07j1dZYjZi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1nOgEo07j1dZYjZi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1nOgEo07j1dZYjZi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1nOgEo07j1dZYjZi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1nOgEo07j1dZYjZi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1nOgEo07j1dZYjZi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1nOgEo07j1dZYjZi .marker.cross{stroke:#333333;}#mermaid-svg-1nOgEo07j1dZYjZi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1nOgEo07j1dZYjZi p{margin:0;}#mermaid-svg-1nOgEo07j1dZYjZi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1nOgEo07j1dZYjZi .cluster-label text{fill:#333;}#mermaid-svg-1nOgEo07j1dZYjZi .cluster-label span{color:#333;}#mermaid-svg-1nOgEo07j1dZYjZi .cluster-label span p{background-color:transparent;}#mermaid-svg-1nOgEo07j1dZYjZi .label text,#mermaid-svg-1nOgEo07j1dZYjZi span{fill:#333;color:#333;}#mermaid-svg-1nOgEo07j1dZYjZi .node rect,#mermaid-svg-1nOgEo07j1dZYjZi .node circle,#mermaid-svg-1nOgEo07j1dZYjZi .node ellipse,#mermaid-svg-1nOgEo07j1dZYjZi .node polygon,#mermaid-svg-1nOgEo07j1dZYjZi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1nOgEo07j1dZYjZi .rough-node .label text,#mermaid-svg-1nOgEo07j1dZYjZi .node .label text,#mermaid-svg-1nOgEo07j1dZYjZi .image-shape .label,#mermaid-svg-1nOgEo07j1dZYjZi .icon-shape .label{text-anchor:middle;}#mermaid-svg-1nOgEo07j1dZYjZi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1nOgEo07j1dZYjZi .rough-node .label,#mermaid-svg-1nOgEo07j1dZYjZi .node .label,#mermaid-svg-1nOgEo07j1dZYjZi .image-shape .label,#mermaid-svg-1nOgEo07j1dZYjZi .icon-shape .label{text-align:center;}#mermaid-svg-1nOgEo07j1dZYjZi .node.clickable{cursor:pointer;}#mermaid-svg-1nOgEo07j1dZYjZi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1nOgEo07j1dZYjZi .arrowheadPath{fill:#333333;}#mermaid-svg-1nOgEo07j1dZYjZi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1nOgEo07j1dZYjZi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1nOgEo07j1dZYjZi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1nOgEo07j1dZYjZi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1nOgEo07j1dZYjZi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1nOgEo07j1dZYjZi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1nOgEo07j1dZYjZi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1nOgEo07j1dZYjZi .cluster text{fill:#333;}#mermaid-svg-1nOgEo07j1dZYjZi .cluster span{color:#333;}#mermaid-svg-1nOgEo07j1dZYjZi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1nOgEo07j1dZYjZi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1nOgEo07j1dZYjZi rect.text{fill:none;stroke-width:0;}#mermaid-svg-1nOgEo07j1dZYjZi .icon-shape,#mermaid-svg-1nOgEo07j1dZYjZi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1nOgEo07j1dZYjZi .icon-shape p,#mermaid-svg-1nOgEo07j1dZYjZi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1nOgEo07j1dZYjZi .icon-shape .label rect,#mermaid-svg-1nOgEo07j1dZYjZi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1nOgEo07j1dZYjZi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1nOgEo07j1dZYjZi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1nOgEo07j1dZYjZi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} No
Yes
接收请求
路由匹配?
404 Not Found
提取路径参数
执行中间件链
Trace 链路追踪
Log 日志记录
Prometheus 指标
MaxConns 连接数限制
Breaker 熔断检查
Shedding 负载降载
Timeout 超时控制
Recover Panic恢复
Metric 指标统计
业务Handler
返回响应

4.2 Engine 启动

go 复制代码
// 文件路径: rest/engine.go

func (ng *engine) start(router httpx.Router, opts ...StartOption) error {
    // 1. 绑定路由
    if err := ng.bindRoutes(router); err != nil {
        return err
    }

    // 2. 配置网络超时
    opts = append([]StartOption{ng.withNetworkTimeout()}, opts...)

    // 3. 启动 HTTP 或 HTTPS
    if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {
        return internal.StartHttp(ng.conf.Host, ng.conf.Port, router, opts...)
    }

    return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
        ng.conf.KeyFile, router, opts...)
}

5. 核心中间件解析

5.1 熔断中间件

go 复制代码
// 文件路径: rest/handler/breakerhandler.go

func BreakerHandler(method, path string, metrics *stat.Metrics) func(http.Handler) http.Handler {
    brk := breaker.NewBreaker(breaker.WithName(getBreakerName(method, path)))

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            cw := response.NewWithCodeResponseWriter(w)
            err := brk.Do(func() error {
                next.ServeHTTP(cw, r)
                if cw.Code < http.StatusInternalServerError {
                    return nil
                }
                return errServiceUnavailable
            })

            if err != nil {
                metrics.AddDrop()
                w.WriteHeader(http.StatusServiceUnavailable)
            }
        })
    }
}

5.2 超时中间件

go 复制代码
// 文件路径: rest/handler/timeouthandler.go

func TimeoutHandler(timeout time.Duration) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx, cancel := context.WithTimeout(r.Context(), timeout)
            defer cancel()

            done := make(chan struct{})
            panicChan := make(chan any, 1)
            tw := response.NewWithCodeResponseWriter(w)

            go func() {
                defer func() {
                    if p := recover(); p != nil {
                        panicChan <- p
                    }
                }()
                next.ServeHTTP(tw, r.WithContext(ctx))
                close(done)
            }()

            select {
            case <-done:
                if tw.Code > 0 {
                    w.WriteHeader(tw.Code)
                }
            case p := <-panicChan:
                panic(p)
            case <-ctx.Done():
                w.WriteHeader(http.StatusRequestTimeout)
            }
        })
    }
}

5.3 降载中间件

go 复制代码
// 文件路径: rest/handler/sheddinghandler.go

func SheddingHandler(shedder load.Shedder, metrics *stat.Metrics) func(http.Handler) http.Handler {
    if shedder == nil {
        return func(next http.Handler) http.Handler {
            return next
        }
    }

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            promise, err := shedder.Allow()
            if err != nil {
                metrics.AddDrop()
                w.WriteHeader(http.StatusServiceUnavailable)
                return
            }

            cw := response.NewWithCodeResponseWriter(w)
            next.ServeHTTP(cw, r)
            promise(cw.Code)
        })
    }
}

6. JWT 认证中间件

6.1 使用方式

go 复制代码
server.AddRoutes(
    []rest.Route{
        {
            Method:  http.MethodGet,
            Path:    "/user/info",
            Handler: UserInfoHandler,
        },
    },
    rest.WithJwt("my-secret-key"),  // 启用 JWT 认证
)

6.2 实现原理

go 复制代码
// 文件路径: rest/handler/authhandler.go

func Authorize(secret string, opts ...AuthorizeOption) func(http.Handler) http.Handler {
    var authOpts authorizeOptions
    for _, opt := range opts {
        opt(&authOpts)
    }

    validator := auth.NewTokenValidator(func(secret string) (any, error) {
        return []byte(secret), nil
    }, secret, authOpts.prevSecret)

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 从 header 或 query 提取 token
            token, err := tokenParser.ParseToken(r)
            if err != nil {
                unauthorized(w, r, authOpts.callback, err)
                return
            }

            // 验证 token
            claims, err := validator.Validate(token)
            if err != nil {
                unauthorized(w, r, authOpts.callback, err)
                return
            }

            // 将 claims 注入 context
            newCtx := context.WithValue(r.Context(), claimsKey, claims)
            next.ServeHTTP(w, r.WithContext(newCtx))
        })
    }
}

7. HTTP 响应处理

7.1 响应包装器

go 复制代码
// 文件路径: rest/internal/response/withcoderesponsewriter.go

type WithCodeResponseWriter struct {
    http.ResponseWriter
    Code int
}

func NewWithCodeResponseWriter(w http.ResponseWriter) *WithCodeResponseWriter {
    return &WithCodeResponseWriter{
        ResponseWriter: w,
    }
}

func (w *WithCodeResponseWriter) WriteHeader(code int) {
    w.Code = code
    w.ResponseWriter.WriteHeader(code)
}

7.2 统一响应格式

go-zero 推荐使用 httpx 工具进行响应处理:

go 复制代码
// 成功响应
httpx.Ok(w)
httpx.OkJson(w, data)

// 错误响应
httpx.Error(w, err)
httpx.ErrorCtx(ctx, w, err)
httpx.WriteJson(w, http.StatusBadRequest, err)

8. CORS 跨域支持

go 复制代码
// 文件路径: rest/server.go

func WithCors(origin ...string) RunOption {
    return func(server *Server) {
        server.router.SetNotAllowedHandler(cors.NotAllowedHandler(nil, origin...))
        server.router = newCorsRouter(server.router, nil, origin...)
    }
}

type corsRouter struct {
    httpx.Router
    middleware Middleware
}

func (c *corsRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    c.middleware(c.Router.ServeHTTP)(w, r)
}

9. SSE 服务端事件支持

go 复制代码
// 文件路径: rest/engine.go

func buildSSERoutes(routes []Route) []Route {
    for i, route := range routes {
        h := route.Handler
        routes[i].Handler = func(w http.ResponseWriter, r *http.Request) {
            // 清除默认写入超时,SSE 需要长连接
            rc := http.NewResponseController(w)
            _ = rc.SetWriteDeadline(time.Time{})

            w.Header().Set(header.ContentType, header.ContentTypeEventStream)
            w.Header().Set(header.CacheControl, header.CacheControlNoCache)
            w.Header().Set(header.Connection, header.ConnectionKeepAlive)
            h(w, r)
        }
    }
    return routes
}

10. 实际使用示例

10.1 基本 HTTP 服务

go 复制代码
func main() {
    c := rest.RestConf{
        Host: "0.0.0.0",
        Port: 8888,
        Name: "my-api",
        Middlewares: rest.MiddlewaresConf{
            Trace:    true,
            Log:      true,
            Recover:  true,
            Breaker:  true,
            Shedding: true,
            Timeout:  true,
        },
    }

    server := rest.MustNewServer(c)
    defer server.Stop()

    // 添加路由
    server.AddRoute(rest.Route{
        Method:  http.MethodGet,
        Path:    "/hello",
        Handler: func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("Hello go-zero!"))
        },
    })

    // 打印路由
    server.PrintRoutes()
    server.Start()
}

10.2 路由分组与中间件

go 复制代码
func main() {
    server := rest.MustNewServer(c)
    defer server.Stop()

    // 添加全局中间件
    server.Use(func(next http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            logx.Info("Global middleware")
            next(w, r)
        }
    })

    // 公开路由 - 不需要认证
    server.AddRoutes([]rest.Route{
        {Method: http.MethodPost, Path: "/user/login", Handler: LoginHandler},
    })

    // 私有路由 - 需要 JWT 认证
    server.AddRoutes([]rest.Route{
        {Method: http.MethodGet, Path: "/user/info", Handler: UserInfoHandler},
        {Method: http.MethodPost, Path: "/user/logout", Handler: LogoutHandler},
    }, rest.WithJwt("my-secret"))

    // 带超时的路由
    server.AddRoutes([]rest.Route{
        {Method: http.MethodGet, Path: "/slow/api", Handler: SlowHandler},
    }, rest.WithTimeout(30*time.Second))

    server.Start()
}

四、gRPC RPC 服务底层全解析

本章深入解析 go-zero 的 gRPC 服务实现,包括服务端、客户端、拦截器链、负载均衡等。

1. 整体架构

1.1 服务端架构

#mermaid-svg-n1yI8wYy8Hjo87KI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-n1yI8wYy8Hjo87KI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-n1yI8wYy8Hjo87KI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-n1yI8wYy8Hjo87KI .error-icon{fill:#552222;}#mermaid-svg-n1yI8wYy8Hjo87KI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-n1yI8wYy8Hjo87KI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-n1yI8wYy8Hjo87KI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-n1yI8wYy8Hjo87KI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-n1yI8wYy8Hjo87KI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-n1yI8wYy8Hjo87KI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-n1yI8wYy8Hjo87KI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-n1yI8wYy8Hjo87KI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-n1yI8wYy8Hjo87KI .marker.cross{stroke:#333333;}#mermaid-svg-n1yI8wYy8Hjo87KI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-n1yI8wYy8Hjo87KI p{margin:0;}#mermaid-svg-n1yI8wYy8Hjo87KI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-n1yI8wYy8Hjo87KI .cluster-label text{fill:#333;}#mermaid-svg-n1yI8wYy8Hjo87KI .cluster-label span{color:#333;}#mermaid-svg-n1yI8wYy8Hjo87KI .cluster-label span p{background-color:transparent;}#mermaid-svg-n1yI8wYy8Hjo87KI .label text,#mermaid-svg-n1yI8wYy8Hjo87KI span{fill:#333;color:#333;}#mermaid-svg-n1yI8wYy8Hjo87KI .node rect,#mermaid-svg-n1yI8wYy8Hjo87KI .node circle,#mermaid-svg-n1yI8wYy8Hjo87KI .node ellipse,#mermaid-svg-n1yI8wYy8Hjo87KI .node polygon,#mermaid-svg-n1yI8wYy8Hjo87KI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-n1yI8wYy8Hjo87KI .rough-node .label text,#mermaid-svg-n1yI8wYy8Hjo87KI .node .label text,#mermaid-svg-n1yI8wYy8Hjo87KI .image-shape .label,#mermaid-svg-n1yI8wYy8Hjo87KI .icon-shape .label{text-anchor:middle;}#mermaid-svg-n1yI8wYy8Hjo87KI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-n1yI8wYy8Hjo87KI .rough-node .label,#mermaid-svg-n1yI8wYy8Hjo87KI .node .label,#mermaid-svg-n1yI8wYy8Hjo87KI .image-shape .label,#mermaid-svg-n1yI8wYy8Hjo87KI .icon-shape .label{text-align:center;}#mermaid-svg-n1yI8wYy8Hjo87KI .node.clickable{cursor:pointer;}#mermaid-svg-n1yI8wYy8Hjo87KI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-n1yI8wYy8Hjo87KI .arrowheadPath{fill:#333333;}#mermaid-svg-n1yI8wYy8Hjo87KI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-n1yI8wYy8Hjo87KI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-n1yI8wYy8Hjo87KI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n1yI8wYy8Hjo87KI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-n1yI8wYy8Hjo87KI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n1yI8wYy8Hjo87KI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-n1yI8wYy8Hjo87KI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-n1yI8wYy8Hjo87KI .cluster text{fill:#333;}#mermaid-svg-n1yI8wYy8Hjo87KI .cluster span{color:#333;}#mermaid-svg-n1yI8wYy8Hjo87KI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-n1yI8wYy8Hjo87KI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-n1yI8wYy8Hjo87KI rect.text{fill:none;stroke-width:0;}#mermaid-svg-n1yI8wYy8Hjo87KI .icon-shape,#mermaid-svg-n1yI8wYy8Hjo87KI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n1yI8wYy8Hjo87KI .icon-shape p,#mermaid-svg-n1yI8wYy8Hjo87KI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-n1yI8wYy8Hjo87KI .icon-shape .label rect,#mermaid-svg-n1yI8wYy8Hjo87KI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n1yI8wYy8Hjo87KI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-n1yI8wYy8Hjo87KI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-n1yI8wYy8Hjo87KI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 拦截器层
服务端
RpcServer
internal/rpcPubServer

(带服务注册)
internal/rpcServer

(纯服务)
gRPC Server
TracingInterceptor
RecoverInterceptor
StatInterceptor
BreakerInterceptor
SheddingInterceptor
TimeoutInterceptor

1.2 客户端架构

#mermaid-svg-LHXKWkena4w0Yaap{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LHXKWkena4w0Yaap .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LHXKWkena4w0Yaap .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LHXKWkena4w0Yaap .error-icon{fill:#552222;}#mermaid-svg-LHXKWkena4w0Yaap .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LHXKWkena4w0Yaap .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LHXKWkena4w0Yaap .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LHXKWkena4w0Yaap .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LHXKWkena4w0Yaap .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LHXKWkena4w0Yaap .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LHXKWkena4w0Yaap .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LHXKWkena4w0Yaap .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LHXKWkena4w0Yaap .marker.cross{stroke:#333333;}#mermaid-svg-LHXKWkena4w0Yaap svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LHXKWkena4w0Yaap p{margin:0;}#mermaid-svg-LHXKWkena4w0Yaap .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LHXKWkena4w0Yaap .cluster-label text{fill:#333;}#mermaid-svg-LHXKWkena4w0Yaap .cluster-label span{color:#333;}#mermaid-svg-LHXKWkena4w0Yaap .cluster-label span p{background-color:transparent;}#mermaid-svg-LHXKWkena4w0Yaap .label text,#mermaid-svg-LHXKWkena4w0Yaap span{fill:#333;color:#333;}#mermaid-svg-LHXKWkena4w0Yaap .node rect,#mermaid-svg-LHXKWkena4w0Yaap .node circle,#mermaid-svg-LHXKWkena4w0Yaap .node ellipse,#mermaid-svg-LHXKWkena4w0Yaap .node polygon,#mermaid-svg-LHXKWkena4w0Yaap .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LHXKWkena4w0Yaap .rough-node .label text,#mermaid-svg-LHXKWkena4w0Yaap .node .label text,#mermaid-svg-LHXKWkena4w0Yaap .image-shape .label,#mermaid-svg-LHXKWkena4w0Yaap .icon-shape .label{text-anchor:middle;}#mermaid-svg-LHXKWkena4w0Yaap .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LHXKWkena4w0Yaap .rough-node .label,#mermaid-svg-LHXKWkena4w0Yaap .node .label,#mermaid-svg-LHXKWkena4w0Yaap .image-shape .label,#mermaid-svg-LHXKWkena4w0Yaap .icon-shape .label{text-align:center;}#mermaid-svg-LHXKWkena4w0Yaap .node.clickable{cursor:pointer;}#mermaid-svg-LHXKWkena4w0Yaap .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LHXKWkena4w0Yaap .arrowheadPath{fill:#333333;}#mermaid-svg-LHXKWkena4w0Yaap .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LHXKWkena4w0Yaap .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LHXKWkena4w0Yaap .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LHXKWkena4w0Yaap .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LHXKWkena4w0Yaap .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LHXKWkena4w0Yaap .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LHXKWkena4w0Yaap .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LHXKWkena4w0Yaap .cluster text{fill:#333;}#mermaid-svg-LHXKWkena4w0Yaap .cluster span{color:#333;}#mermaid-svg-LHXKWkena4w0Yaap div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LHXKWkena4w0Yaap .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LHXKWkena4w0Yaap rect.text{fill:none;stroke-width:0;}#mermaid-svg-LHXKWkena4w0Yaap .icon-shape,#mermaid-svg-LHXKWkena4w0Yaap .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LHXKWkena4w0Yaap .icon-shape p,#mermaid-svg-LHXKWkena4w0Yaap .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LHXKWkena4w0Yaap .icon-shape .label rect,#mermaid-svg-LHXKWkena4w0Yaap .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LHXKWkena4w0Yaap .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LHXKWkena4w0Yaap .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LHXKWkena4w0Yaap :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 拦截器层
负载均衡层
解析器层
客户端
RpcClient
internal/client
gRPC ClientConn
resolver
etcdBuilder
directBuilder
p2cBalancer
consistentHash
TracingInterceptor
DurationInterceptor
PrometheusInterceptor
BreakerInterceptor
TimeoutInterceptor

2. 服务端实现

2.1 RpcServer 结构体

go 复制代码
// 文件路径: zrpc/server.go

type RpcServer struct {
    server   internal.Server
    register internal.RegisterFn
}

// NewServer 创建 RPC 服务
func NewServer(c RpcServerConf, register internal.RegisterFn) (*RpcServer, error) {
    var err error
    if err = c.Validate(); err != nil {
        return nil, err
    }

    var server internal.Server
    metrics := stat.NewMetrics(c.ListenOn)
    serverOptions := []internal.ServerOption{
        internal.WithRpcHealth(c.Health),
    }

    // 根据是否有 etcd 配置选择不同的 server 实现
    if c.HasEtcd() {
        server, err = internal.NewRpcPubServer(c.Etcd, c.ListenOn, serverOptions...)
        if err != nil {
            return nil, err
        }
    } else {
        server = internal.NewRpcServer(c.ListenOn, serverOptions...)
    }

    server.SetName(c.Name)
    metrics.SetName(c.Name)
    setupStreamInterceptors(server, c)
    setupUnaryInterceptors(server, c, metrics)
    if err = setupAuthInterceptors(server, c); err != nil {
        return nil, err
    }

    rpcServer := &RpcServer{
        server:   server,
        register: register,
    }
    if err = c.SetUp(); err != nil {
        return nil, err
    }

    return rpcServer, nil
}

// Start 启动服务
func (rs *RpcServer) Start() {
    if err := rs.server.Start(rs.register); err != nil {
        logx.Error(err)
        panic(err)
    }
}

2.2 服务端拦截器设置

go 复制代码
// 文件路径: zrpc/server.go

func setupUnaryInterceptors(svr internal.Server, c RpcServerConf, metrics *stat.Metrics) {
    if c.Middlewares.Trace {
        svr.AddUnaryInterceptors(serverinterceptors.UnaryTracingInterceptor)
    }
    if c.Middlewares.Recover {
        svr.AddUnaryInterceptors(serverinterceptors.UnaryRecoverInterceptor)
    }
    if c.Middlewares.Stat {
        svr.AddUnaryInterceptors(serverinterceptors.UnaryStatInterceptor(metrics, c.Middlewares.StatConf))
    }
    if c.Middlewares.Prometheus {
        svr.AddUnaryInterceptors(serverinterceptors.UnaryPrometheusInterceptor)
    }
    if c.Middlewares.Breaker {
        svr.AddUnaryInterceptors(serverinterceptors.UnaryBreakerInterceptor)
    }
    if c.CpuThreshold > 0 {
        shedder := load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
        svr.AddUnaryInterceptors(serverinterceptors.UnarySheddingInterceptor(shedder, metrics))
    }
    if c.Timeout > 0 {
        svr.AddUnaryInterceptors(serverinterceptors.UnaryTimeoutInterceptor(
            time.Duration(c.Timeout)*time.Millisecond, c.MethodTimeouts...))
    }
}

func setupStreamInterceptors(svr internal.Server, c RpcServerConf) {
    if c.Middlewares.Trace {
        svr.AddStreamInterceptors(serverinterceptors.StreamTracingInterceptor)
    }
    if c.Middlewares.Recover {
        svr.AddStreamInterceptors(serverinterceptors.StreamRecoverInterceptor)
    }
    if c.Middlewares.Breaker {
        svr.AddStreamInterceptors(serverinterceptors.StreamBreakerInterceptor)
    }
}

2.3 核心服务实现

go 复制代码
// 文件路径: zrpc/internal/server.go

type baseRpcServer struct {
    address            string
    health             *health.Server
    options            []grpc.ServerOption
    streamInterceptors []grpc.StreamServerInterceptor
    unaryInterceptors  []grpc.UnaryServerInterceptor
}

func (s *baseRpcServer) Start(register RegisterFn) error {
    lis, err := net.Listen("tcp", s.address)
    if err != nil {
        return err
    }

    var options []grpc.ServerOption
    if len(s.unaryInterceptors) > 0 {
        options = append(options, grpc.ChainUnaryInterceptor(s.unaryInterceptors...))
    }
    if len(s.streamInterceptors) > 0 {
        options = append(options, grpc.ChainStreamInterceptor(s.streamInterceptors...))
    }
    options = append(options, s.options...)

    grpcServer := grpc.NewServer(options...)
    if s.health != nil {
        health.RegisterHealthServer(grpcServer, s.health)
    }
    register(grpcServer)

    proc.AddWrapUpListener(func() {
        s.health.Shutdown()
        grpcServer.GracefulStop()
    })

    return grpcServer.Serve(lis)
}

3. 客户端实现

3.1 RpcClient 结构体

go 复制代码
// 文件路径: zrpc/client.go

type RpcClient struct {
    client Client
}

// NewClient 创建 RPC 客户端
func NewClient(c RpcClientConf, options ...ClientOption) (Client, error) {
    var opts []ClientOption
    if c.HasCredential() {
        opts = append(opts, WithDialOption(grpc.WithPerRPCCredentials(&auth.Credential{
            App:   c.App,
            Token: c.Token,
        })))
    }
    if c.NonBlock {
        opts = append(opts, WithNonBlock())
    } else {
        opts = append(opts, WithBlock())
    }
    if c.Timeout > 0 {
        opts = append(opts, WithTimeout(time.Duration(c.Timeout)*time.Millisecond))
    }
    if c.KeepaliveTime > 0 {
        opts = append(opts, WithDialOption(grpc.WithKeepaliveParams(keepalive.ClientParameters{
            Time: c.KeepaliveTime,
        })))
    }

    svcCfg := makeLBServiceConfig(c.BalancerName)
    opts = append(opts, WithDialOption(grpc.WithDefaultServiceConfig(svcCfg)))

    opts = append(opts, options...)

    target, err := c.BuildTarget()
    if err != nil {
        return nil, err
    }

    client, err := internal.NewClient(target, c.Middlewares, opts...)
    if err != nil {
        return nil, err
    }

    return &RpcClient{
        client: client,
    }, nil
}

// Conn 获取底层 gRPC 连接
func (rc *RpcClient) Conn() *grpc.ClientConn {
    return rc.client.Conn()
}

3.2 客户端连接建立

go 复制代码
// 文件路径: zrpc/internal/client.go

func (c *client) dial(server string, opts ...ClientOption) error {
    options := c.buildDialOptions(opts...)
    timeCtx, cancel := context.WithTimeout(context.Background(), dialTimeout)
    defer cancel()
    conn, err := grpc.DialContext(timeCtx, server, options...)
    if err != nil {
        service := server
        if errors.Is(err, context.DeadlineExceeded) {
            pos := strings.LastIndexByte(server, separator)
            if 0 < pos && pos < len(server)-1 {
                service = server[pos+1:]
            }
        }
        return fmt.Errorf("rpc dial: %s, error: %s, make sure rpc service %q is already started",
            server, err.Error(), service)
    }

    c.conn = conn
    return nil
}

func (c *client) buildUnaryInterceptors(timeout time.Duration) []grpc.UnaryClientInterceptor {
    var interceptors []grpc.UnaryClientInterceptor

    if c.middlewares.Trace {
        interceptors = append(interceptors, clientinterceptors.UnaryTracingInterceptor)
    }
    if c.middlewares.Duration {
        interceptors = append(interceptors, clientinterceptors.DurationInterceptor)
    }
    if c.middlewares.Prometheus {
        interceptors = append(interceptors, clientinterceptors.PrometheusInterceptor)
    }
    if c.middlewares.Breaker {
        interceptors = append(interceptors, clientinterceptors.BreakerInterceptor)
    }
    if c.middlewares.Timeout {
        interceptors = append(interceptors, clientinterceptors.TimeoutInterceptor(timeout))
    }

    return interceptors
}

4. 负载均衡

4.1 P2C (Pick 2 Choices) 负载均衡

go 复制代码
// 文件路径: zrpc/internal/balancer/p2c/p2c.go

type p2cPicker struct {
    subConns []connInfo
    stamp    *syncx.AtomicDuration
    rand     *rand.Rand
    randLock sync.Mutex
}

func (p *p2cPicker) Pick(ctx context.Context, info balancer.PickInfo) (
    balancer.SubConn, balancer.DoneInfo, error) {
    p.stamp.Set(timex.Now())
    if len(p.subConns) == 0 {
        return nil, balancer.DoneInfo{}, balancer.ErrNoSubConnAvailable
    }
    if len(p.subConns) == 1 {
        return p.subConns[0].subConn, p.buildDoneInfo(p.subConns[0]), nil
    }

    p.randLock.Lock()
    x := p.rand.Intn(len(p.subConns))
    y := p.rand.Intn(len(p.subConns) - 1)
    if y >= x {
        y++
    }
    p.randLock.Unlock()

    pcx, pcy := p.subConns[x], p.subConns[y]
    pcx.load, pcy.load = pcx.load.Load(), pcy.load.Load()
    if pcx.load > pcy.load {
        x, y = y, x
        pcx = pcy
    }

    pcx.inflight.Inc()
    return pcx.subConn, p.buildDoneInfo(pcx), nil
}

4.2 一致性哈希负载均衡

go 复制代码
// 文件路径: zrpc/internal/balancer/consistenthash/consistenthash.go

type picker struct {
    hashFunc consistenthash.HashFunc
    ring     *hash.ConsistentHash
    conns    map[string]balancer.SubConn
    key      string
}

func (p *picker) Pick(ctx context.Context, info balancer.PickInfo) (
    balancer.SubConn, balancer.DoneInfo, error) {
    val, ok := ctx.Value(hashKey).(string)
    if !ok {
        return nil, balancer.DoneInfo{}, errMissingHashKey
    }

    node, ok := p.ring.Get(val)
    if !ok {
        return nil, balancer.DoneInfo{}, errNoSubConnAvailable
    }

    return p.conns[node], balancer.DoneInfo{}, nil
}

5. 核心拦截器

5.1 客户端熔断拦截器

go 复制代码
// 文件路径: zrpc/internal/clientinterceptors/breakerinterceptor.go

func BreakerInterceptor(ctx context.Context, method string, req, reply any,
    cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    breakerName := path.Join(cc.Target(), method)
    brk := breaker.NewBreaker(breaker.WithName(breakerName))

    return brk.DoCtx(ctx, func() error {
        return invoker(ctx, method, req, reply, cc, opts...)
    })
}

5.2 客户端超时拦截器

go 复制代码
// 文件路径: zrpc/internal/clientinterceptors/timeoutinterceptor.go

func TimeoutInterceptor(timeout time.Duration) grpc.UnaryClientInterceptor {
    return func(ctx context.Context, method string, req, reply any,
        cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
        if timeout <= 0 {
            return invoker(ctx, method, req, reply, cc, opts...)
        }

        ctx, cancel := context.WithTimeout(ctx, timeout)
        defer cancel()

        return invoker(ctx, method, req, reply, cc, opts...)
    }
}

5.3 服务端降载拦截器

go 复制代码
// 文件路径: zrpc/internal/serverinterceptors/sheddinginterceptor.go

func UnarySheddingInterceptor(shedder load.Shedder, metrics *stat.Metrics) grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req any, info *grpc.UnaryServerInfo,
        handler grpc.UnaryHandler) (resp any, err error) {
        if shedder == nil {
            return handler(ctx, req)
        }

        promise, err := shedder.Allow()
        if err != nil {
            metrics.AddDrop()
            return nil, err
        }

        resp, err = handler(ctx, req)
        promise(err)
        return
    }
}

6. 使用示例

6.1 服务端使用

go 复制代码
type Config struct {
    zrpc.RpcServerConf
}

func main() {
    var c Config
    conf.MustLoad("etc/config.yaml", &c)

    c.MustSetUp()

    ctx := context.Background()
    svcCtx := svc.NewServiceContext(c)

    s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
        greeter.RegisterGreeterServer(grpcServer, server.NewGreeterServer(svcCtx))
    })
    defer s.Stop()

    s.Start()
}

6.2 客户端使用

go 复制代码
type Config struct {
    zrpc.RpcClientConf
}

func main() {
    var c Config
    conf.MustLoad("etc/config.yaml", &c)

    client := zrpc.MustNewClient(c)
    defer client.Conn().Close()

    greeterClient := greeter.NewGreeterClient(client.Conn())
    resp, err := greeterClient.SayHello(context.Background(), &greeter.HelloRequest{
        Name: "go-zero",
    })
    if err != nil {
        logx.Errorf("Error: %v", err)
        return
    }

    logx.Infof("Response: %s", resp.GetMessage())
}

五、服务注册与发现 (etcd)

本章深入解析 go-zero 的服务注册发现机制,包括 etcd 注册、节点监听、负载均衡集成等。

1. 整体架构

#mermaid-svg-VPwLGkP7ZaNr3bvs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VPwLGkP7ZaNr3bvs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VPwLGkP7ZaNr3bvs .error-icon{fill:#552222;}#mermaid-svg-VPwLGkP7ZaNr3bvs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VPwLGkP7ZaNr3bvs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VPwLGkP7ZaNr3bvs .marker.cross{stroke:#333333;}#mermaid-svg-VPwLGkP7ZaNr3bvs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VPwLGkP7ZaNr3bvs p{margin:0;}#mermaid-svg-VPwLGkP7ZaNr3bvs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VPwLGkP7ZaNr3bvs .cluster-label text{fill:#333;}#mermaid-svg-VPwLGkP7ZaNr3bvs .cluster-label span{color:#333;}#mermaid-svg-VPwLGkP7ZaNr3bvs .cluster-label span p{background-color:transparent;}#mermaid-svg-VPwLGkP7ZaNr3bvs .label text,#mermaid-svg-VPwLGkP7ZaNr3bvs span{fill:#333;color:#333;}#mermaid-svg-VPwLGkP7ZaNr3bvs .node rect,#mermaid-svg-VPwLGkP7ZaNr3bvs .node circle,#mermaid-svg-VPwLGkP7ZaNr3bvs .node ellipse,#mermaid-svg-VPwLGkP7ZaNr3bvs .node polygon,#mermaid-svg-VPwLGkP7ZaNr3bvs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VPwLGkP7ZaNr3bvs .rough-node .label text,#mermaid-svg-VPwLGkP7ZaNr3bvs .node .label text,#mermaid-svg-VPwLGkP7ZaNr3bvs .image-shape .label,#mermaid-svg-VPwLGkP7ZaNr3bvs .icon-shape .label{text-anchor:middle;}#mermaid-svg-VPwLGkP7ZaNr3bvs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VPwLGkP7ZaNr3bvs .rough-node .label,#mermaid-svg-VPwLGkP7ZaNr3bvs .node .label,#mermaid-svg-VPwLGkP7ZaNr3bvs .image-shape .label,#mermaid-svg-VPwLGkP7ZaNr3bvs .icon-shape .label{text-align:center;}#mermaid-svg-VPwLGkP7ZaNr3bvs .node.clickable{cursor:pointer;}#mermaid-svg-VPwLGkP7ZaNr3bvs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VPwLGkP7ZaNr3bvs .arrowheadPath{fill:#333333;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VPwLGkP7ZaNr3bvs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VPwLGkP7ZaNr3bvs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VPwLGkP7ZaNr3bvs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VPwLGkP7ZaNr3bvs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VPwLGkP7ZaNr3bvs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VPwLGkP7ZaNr3bvs .cluster text{fill:#333;}#mermaid-svg-VPwLGkP7ZaNr3bvs .cluster span{color:#333;}#mermaid-svg-VPwLGkP7ZaNr3bvs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VPwLGkP7ZaNr3bvs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VPwLGkP7ZaNr3bvs rect.text{fill:none;stroke-width:0;}#mermaid-svg-VPwLGkP7ZaNr3bvs .icon-shape,#mermaid-svg-VPwLGkP7ZaNr3bvs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VPwLGkP7ZaNr3bvs .icon-shape p,#mermaid-svg-VPwLGkP7ZaNr3bvs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VPwLGkP7ZaNr3bvs .icon-shape .label rect,#mermaid-svg-VPwLGkP7ZaNr3bvs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VPwLGkP7ZaNr3bvs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VPwLGkP7ZaNr3bvs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VPwLGkP7ZaNr3bvs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} etcd集群
客户端
服务端
Put Lease KeepAlive
Get Watch
Change Event
Update Local Cache
RpcServer
Publisher

服务注册
EtcdClient
RpcClient
Resolver

服务发现
Watcher

节点监听
Etcd Key

/service/name/id

2. 服务注册实现

2.1 Publisher 接口

go 复制代码
// 文件路径: core/discov/publisher.go

type Publisher struct {
    etcd  EtcdClient
    key   string
    id    int64
    full  string
    lease clientv3.LeaseID
    quit  chan struct{}
}

// NewPublisher 创建服务发布者
func NewPublisher(etcdConf EtcdConf, key string, opts ...PublisherOption) (*Publisher, error) {
    cli, err := NewClient(etcdConf)
    if err != nil {
        return nil, err
    }

    id := time.Now().UnixNano()
    p := &Publisher{
        etcd: cli,
        key:  key,
        id:   id,
        full: makeEtcdKey(key, id),
        quit: make(chan struct{}),
    }

    for _, opt := range opts {
        opt(p)
    }

    return p, nil
}

// KeepAlive 保持服务注册
func (p *Publisher) KeepAlive() error {
    resp, err := p.etcd.Grant(p.etcd.Ctx(), TimeToLive)
    if err != nil {
        return err
    }

    if _, err := p.etcd.Put(p.etcd.Ctx(), p.full, "",
        clientv3.WithLease(resp.ID)); err != nil {
        return err
    }

    p.lease = resp.ID
    ka, err := p.etcd.KeepAlive(p.etcd.Ctx(), resp.ID)
    if err != nil {
        return err
    }

    go func() {
        for {
            select {
            case _, ok := <-ka:
                if !ok {
                    p.renew()
                    return
                }
            case <-p.quit:
                return
            }
        }
    }()

    return nil
}

func (p *Publisher) renew() {
    for {
        select {
        case <-p.quit:
            return
        default:
        }

        time.Sleep(backoffStrategy * time.Second)
        if err := p.KeepAlive(); err == nil {
            return
        }
    }
}

// Stop 停止服务注册
func (p *Publisher) Stop() {
    close(p.quit)
    if p.lease != 0 {
        _, _ = p.etcd.Revoke(context.Background(), p.lease)
    }
    p.etcd.Close()
}

2.2 Etcd 键格式

go 复制代码
// 文件路径: core/discov/clients.go

const timeToLive int64 = 10

func makeEtcdKey(key string, id int64) string {
    return fmt.Sprintf("%s%c%d", key, internal.Delimiter, id)
}

// 键格式: /service/name/1234567890
// 其中 1234567890 是时间戳 id

3. 服务发现实现

3.1 EtcdResolver

go 复制代码
// 文件路径: zrpc/resolver/internal/etcdbuilder.go

type etcdBuilder struct {
    discovBuilder
}

func (b *etcdBuilder) Scheme() string {
    return EtcdScheme
}

func (b *etcdBuilder) Build(target resolver.Target, cc resolver.ClientConn,
    opts resolver.BuildOptions) (resolver.Resolver, error) {
    r, err := b.discovBuilder.Build(target, cc, opts)
    if err != nil {
        return nil, err
    }

    dr := r.(*discovResolver)
    if err = dr.watchEtcd(b.newUpdateStream); err != nil {
        return nil, err
    }

    return r, nil
}

3.2 节点监听

go 复制代码
// 文件路径: core/discov/internal/updatelistener.go

type UpdateListener interface {
    OnAdd(node Node)
    OnDelete(node Node)
}

type updateListener struct {
    updates   chan []Node
    newAdds   chan Node
    newDeletes chan Node
}

func NewUpdateListener() *updateListener {
    return &updateListener{
        updates:   make(chan []Node),
        newAdds:   make(chan Node),
        newDeletes: make(chan Node),
    }
}

func (ul *updateListener) OnAdd(node Node) {
    ul.newAdds <- node
}

func (ul *updateListener) OnDelete(node Node) {
    ul.newDeletes <- node
}

func (ul *updateListener) Updates() <-chan []Node {
    return ul.updates
}

3.3 服务发现核心

go 复制代码
// 文件路径: zrpc/resolver/internal/discovbuilder.go

type discovResolver struct {
    cc     resolver.ClientConn
    key    string
    sub    discov.Subscriber
    targets []target.Endpoint
    update chan struct{}
    quit   chan struct{}
}

func (dr *discovResolver) resolve() error {
    vals, err := dr.sub.Values()
    if err != nil {
        return err
    }

    var endpoints []target.Endpoint
    for _, val := range vals {
        endpoints = append(endpoints, target.Endpoint{Addr: val})
    }

    dr.targets = endpoints
    select {
    case dr.update <- struct{}{}:
    default:
    }

    return nil
}

func (dr *discovResolver) watchEtcd(updateStream func() (UpdateStream, error)) error {
    stream, err := updateStream()
    if err != nil {
        return err
    }

    go func() {
        for {
            select {
            case <-dr.quit:
                return
            case <-stream.Chan():
                _ = dr.resolve()
            }
        }
    }()

    return nil
}

4. 服务键格式

#mermaid-svg-VEYhiIXKeoKdN8Uk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VEYhiIXKeoKdN8Uk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VEYhiIXKeoKdN8Uk .error-icon{fill:#552222;}#mermaid-svg-VEYhiIXKeoKdN8Uk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VEYhiIXKeoKdN8Uk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VEYhiIXKeoKdN8Uk .marker.cross{stroke:#333333;}#mermaid-svg-VEYhiIXKeoKdN8Uk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VEYhiIXKeoKdN8Uk p{margin:0;}#mermaid-svg-VEYhiIXKeoKdN8Uk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VEYhiIXKeoKdN8Uk .cluster-label text{fill:#333;}#mermaid-svg-VEYhiIXKeoKdN8Uk .cluster-label span{color:#333;}#mermaid-svg-VEYhiIXKeoKdN8Uk .cluster-label span p{background-color:transparent;}#mermaid-svg-VEYhiIXKeoKdN8Uk .label text,#mermaid-svg-VEYhiIXKeoKdN8Uk span{fill:#333;color:#333;}#mermaid-svg-VEYhiIXKeoKdN8Uk .node rect,#mermaid-svg-VEYhiIXKeoKdN8Uk .node circle,#mermaid-svg-VEYhiIXKeoKdN8Uk .node ellipse,#mermaid-svg-VEYhiIXKeoKdN8Uk .node polygon,#mermaid-svg-VEYhiIXKeoKdN8Uk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VEYhiIXKeoKdN8Uk .rough-node .label text,#mermaid-svg-VEYhiIXKeoKdN8Uk .node .label text,#mermaid-svg-VEYhiIXKeoKdN8Uk .image-shape .label,#mermaid-svg-VEYhiIXKeoKdN8Uk .icon-shape .label{text-anchor:middle;}#mermaid-svg-VEYhiIXKeoKdN8Uk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VEYhiIXKeoKdN8Uk .rough-node .label,#mermaid-svg-VEYhiIXKeoKdN8Uk .node .label,#mermaid-svg-VEYhiIXKeoKdN8Uk .image-shape .label,#mermaid-svg-VEYhiIXKeoKdN8Uk .icon-shape .label{text-align:center;}#mermaid-svg-VEYhiIXKeoKdN8Uk .node.clickable{cursor:pointer;}#mermaid-svg-VEYhiIXKeoKdN8Uk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VEYhiIXKeoKdN8Uk .arrowheadPath{fill:#333333;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VEYhiIXKeoKdN8Uk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VEYhiIXKeoKdN8Uk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VEYhiIXKeoKdN8Uk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VEYhiIXKeoKdN8Uk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VEYhiIXKeoKdN8Uk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VEYhiIXKeoKdN8Uk .cluster text{fill:#333;}#mermaid-svg-VEYhiIXKeoKdN8Uk .cluster span{color:#333;}#mermaid-svg-VEYhiIXKeoKdN8Uk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VEYhiIXKeoKdN8Uk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VEYhiIXKeoKdN8Uk rect.text{fill:none;stroke-width:0;}#mermaid-svg-VEYhiIXKeoKdN8Uk .icon-shape,#mermaid-svg-VEYhiIXKeoKdN8Uk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VEYhiIXKeoKdN8Uk .icon-shape p,#mermaid-svg-VEYhiIXKeoKdN8Uk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VEYhiIXKeoKdN8Uk .icon-shape .label rect,#mermaid-svg-VEYhiIXKeoKdN8Uk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VEYhiIXKeoKdN8Uk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VEYhiIXKeoKdN8Uk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VEYhiIXKeoKdN8Uk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 服务键
etcd路径前缀

/service/
服务名

user-api
分隔符

/
服务节点ID

时间戳
完整示例: /service/user-api/1234567890

5. 负载均衡与服务发现集成

go 复制代码
// 文件路径: zrpc/client.go

func makeLBServiceConfig(balancerName string) string {
    if len(balancerName) == 0 {
        balancerName = p2c.Name
    }

    return fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, balancerName)
}

// 使用方式
func NewClient(c RpcClientConf, options ...ClientOption) (Client, error) {
    // ...
    svcCfg := makeLBServiceConfig(c.BalancerName)
    opts = append(opts, WithDialOption(grpc.WithDefaultServiceConfig(svcCfg)))
    // ...
}

6. 使用示例

6.1 服务端配置

yaml 复制代码
# etc/greet.yaml
Name: greet.rpc
ListenOn: 0.0.0.0:8080
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: greet.rpc

6.2 客户端配置

yaml 复制代码
# etc/client.yaml
Greet:
  Etcd:
    Hosts:
    - 127.0.0.1:2379
    Key: greet.rpc
  BalancerName: p2c
  NonBlock: true

6.3 直接使用服务发现

go 复制代码
// 服务注册
publisher, err := discov.NewPublisher(
    discov.EtcdConf{
        Hosts: []string{"127.0.0.1:2379"},
        Key:   "my.service",
    },
    "127.0.0.1:8080",
)
if err != nil {
    panic(err)
}
defer publisher.Stop()

if err := publisher.KeepAlive(); err != nil {
    panic(err)
}

// 服务发现
subscriber, err := discov.NewSubscriber(
    discov.EtcdConf{
        Hosts: []string{"127.0.0.1:2379"},
        Key:   "my.service",
    },
)
if err != nil {
    panic(err)
}
defer subscriber.Stop()

for _, val := range subscriber.Values() {
    fmt.Println("Service endpoint:", val)
}

六、服务治理核心:熔断/限流/降级

本章深入解析 go-zero 的服务治理核心组件,包括熔断器、限流器、降载器等。

1. 服务治理全景

#mermaid-svg-jddUyJnUYYRStllh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jddUyJnUYYRStllh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jddUyJnUYYRStllh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jddUyJnUYYRStllh .error-icon{fill:#552222;}#mermaid-svg-jddUyJnUYYRStllh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jddUyJnUYYRStllh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jddUyJnUYYRStllh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jddUyJnUYYRStllh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jddUyJnUYYRStllh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jddUyJnUYYRStllh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jddUyJnUYYRStllh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jddUyJnUYYRStllh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jddUyJnUYYRStllh .marker.cross{stroke:#333333;}#mermaid-svg-jddUyJnUYYRStllh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jddUyJnUYYRStllh p{margin:0;}#mermaid-svg-jddUyJnUYYRStllh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jddUyJnUYYRStllh .cluster-label text{fill:#333;}#mermaid-svg-jddUyJnUYYRStllh .cluster-label span{color:#333;}#mermaid-svg-jddUyJnUYYRStllh .cluster-label span p{background-color:transparent;}#mermaid-svg-jddUyJnUYYRStllh .label text,#mermaid-svg-jddUyJnUYYRStllh span{fill:#333;color:#333;}#mermaid-svg-jddUyJnUYYRStllh .node rect,#mermaid-svg-jddUyJnUYYRStllh .node circle,#mermaid-svg-jddUyJnUYYRStllh .node ellipse,#mermaid-svg-jddUyJnUYYRStllh .node polygon,#mermaid-svg-jddUyJnUYYRStllh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jddUyJnUYYRStllh .rough-node .label text,#mermaid-svg-jddUyJnUYYRStllh .node .label text,#mermaid-svg-jddUyJnUYYRStllh .image-shape .label,#mermaid-svg-jddUyJnUYYRStllh .icon-shape .label{text-anchor:middle;}#mermaid-svg-jddUyJnUYYRStllh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jddUyJnUYYRStllh .rough-node .label,#mermaid-svg-jddUyJnUYYRStllh .node .label,#mermaid-svg-jddUyJnUYYRStllh .image-shape .label,#mermaid-svg-jddUyJnUYYRStllh .icon-shape .label{text-align:center;}#mermaid-svg-jddUyJnUYYRStllh .node.clickable{cursor:pointer;}#mermaid-svg-jddUyJnUYYRStllh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jddUyJnUYYRStllh .arrowheadPath{fill:#333333;}#mermaid-svg-jddUyJnUYYRStllh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jddUyJnUYYRStllh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jddUyJnUYYRStllh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jddUyJnUYYRStllh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jddUyJnUYYRStllh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jddUyJnUYYRStllh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jddUyJnUYYRStllh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jddUyJnUYYRStllh .cluster text{fill:#333;}#mermaid-svg-jddUyJnUYYRStllh .cluster span{color:#333;}#mermaid-svg-jddUyJnUYYRStllh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jddUyJnUYYRStllh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jddUyJnUYYRStllh rect.text{fill:none;stroke-width:0;}#mermaid-svg-jddUyJnUYYRStllh .icon-shape,#mermaid-svg-jddUyJnUYYRStllh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jddUyJnUYYRStllh .icon-shape p,#mermaid-svg-jddUyJnUYYRStllh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jddUyJnUYYRStllh .icon-shape .label rect,#mermaid-svg-jddUyJnUYYRStllh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jddUyJnUYYRStllh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jddUyJnUYYRStllh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jddUyJnUYYRStllh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 指标统计
服务治理
熔断器

Breaker
限流器

Limiter
降载器

Shedder
自适应降载

AdaptiveShedder
滑动窗口

RollingWindow
指标采集

Metrics

2. 熔断器 (Breaker)

2.1 熔断器原理

#mermaid-svg-p6R8nyIOo3Xhv4Fk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .error-icon{fill:#552222;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .marker.cross{stroke:#333333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk p{margin:0;}#mermaid-svg-p6R8nyIOo3Xhv4Fk defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-p6R8nyIOo3Xhv4Fk g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-p6R8nyIOo3Xhv4Fk g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-p6R8nyIOo3Xhv4Fk .edgeLabel .label text{fill:#333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .label div .edgeLabel{color:#333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk #statediagram-barbEnd{fill:#333333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .cluster-label,#mermaid-svg-p6R8nyIOo3Xhv4Fk .nodeLabel{color:#131300;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .note-edge{stroke-dasharray:5;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-note text{fill:black;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram-note .nodeLabel{color:black;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagram .edgeLabel{color:red;}#mermaid-svg-p6R8nyIOo3Xhv4Fk #dependencyStart,#mermaid-svg-p6R8nyIOo3Xhv4Fk #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-p6R8nyIOo3Xhv4Fk .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-p6R8nyIOo3Xhv4Fk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 正常状态
错误率超过阈值
冷却时间过后
少量请求成功
少量请求失败
Closed
Open
HalfOpen

2.2 核心实现

go 复制代码
// 文件路径: core/breaker/breaker.go

type Breaker interface {
    Name() string
    Allow() (Promise, error)
    AllowCtx(ctx context.Context) (Promise, error)
    Do(req func() error) error
    DoCtx(ctx context.Context, req func() error) error
    DoWithAcceptable(req func() error, acceptable Acceptable) error
    DoWithFallback(req func() error, fallback Fallback) error
}

type Promise interface {
    Accept()
    Reject(reason string)
}

type circuitBreaker struct {
    name   string
    throttle
}

func NewBreaker(opts ...Option) Breaker {
    var b circuitBreaker
    for _, opt := range opts {
        opt(&b)
    }
    if len(b.name) == 0 {
        b.name = stringx.Rand()
    }
    b.throttle = newLoggedThrottle(b.name, newGoogleBreaker())

    return &b
}

2.3 Google SRE 熔断器算法

go 复制代码
// 文件路径: core/breaker/googlebreaker.go

type googleBreaker struct {
    k     float64
    stat  *collection.RollingWindow
    proba *mathx.Proba
}

func newGoogleBreaker() *googleBreaker {
    bucketSize := time.Millisecond * 100
    winSize := 10
    stat := collection.NewRollingWindow(winSize, bucketSize)
    return &googleBreaker{
        k:     1.5,
        stat:  stat,
        proba: mathx.NewProba(),
    }
}

func (b *googleBreaker) accept() error {
    accepts, total := b.history()
    weightedAccepts := b.k * float64(accepts)
    // Google SRE 算法: drops = (requests - k * accepts) / (requests + 1)
    dropRatio := math.Max(0, (float64(total)-weightedAccepts)/float64(total+1))
    if dropRatio <= 0 {
        return nil
    }

    if b.proba.TrueOnProba(dropRatio) {
        return ErrServiceUnavailable
    }

    return nil
}

2.4 熔断器使用

go 复制代码
// 方式1: 手动 Allow/Accept/Reject
breaker := breaker.NewBreaker(breaker.WithName("my-service"))
promise, err := breaker.Allow()
if err != nil {
    // 熔断中,直接返回
    return err
}

defer func() {
    if e := recover(); e != nil {
        promise.Reject(fmt.Sprintf("panic: %v", e))
        panic(e)
    }
}()

if err := doSomething(); err != nil {
    promise.Reject(err.Error())
    return err
}

promise.Accept()

// 方式2: 使用 Do
err := breaker.Do(func() error {
    return doSomething()
})

// 方式3: 使用 DoWithFallback
err := breaker.DoWithFallback(func() error {
    return doSomething()
}, func(err error) error {
    return fallbackLogic()
})

3. 滑动窗口 (RollingWindow)

3.1 核心实现

go 复制代码
// 文件路径: core/collection/rollingwindow.go

type RollingWindow[T Numerical, B BucketInterface[T]] struct {
    lock          sync.RWMutex
    size          int
    win           *window[T, B]
    interval      time.Duration
    offset        int
    ignoreCurrent bool
    lastTime      time.Duration
}

func NewRollingWindow[T Numerical, B BucketInterface[T]](newBucket func() B,
    size int, interval time.Duration, opts ...RollingWindowOption[T, B]) *RollingWindow[T, B] {
    w := &RollingWindow[T, B]{
        size:     size,
        win:      newWindow(newBucket, size),
        interval: interval,
        lastTime: timex.Now(),
    }
    for _, opt := range opts {
        opt(w)
    }
    return w
}

func (rw *RollingWindow[T, B]) Add(v T) {
    rw.lock.Lock()
    defer rw.lock.Unlock()
    rw.updateOffset()
    rw.win.add(rw.offset, v)
}

func (rw *RollingWindow[T, B]) Reduce(fn func(b B)) {
    rw.lock.RLock()
    defer rw.lock.RUnlock()

    var diff int
    span := rw.span()
    if span == 0 && rw.ignoreCurrent {
        diff = rw.size - 1
    } else {
        diff = rw.size - span
    }
    if diff > 0 {
        offset := (rw.offset + span + 1) % rw.size
        rw.win.reduce(offset, diff, fn)
    }
}

3.2 滑动窗口结构

#mermaid-svg-JK6adIjowNMJJHhE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JK6adIjowNMJJHhE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JK6adIjowNMJJHhE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JK6adIjowNMJJHhE .error-icon{fill:#552222;}#mermaid-svg-JK6adIjowNMJJHhE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JK6adIjowNMJJHhE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JK6adIjowNMJJHhE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JK6adIjowNMJJHhE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JK6adIjowNMJJHhE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JK6adIjowNMJJHhE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JK6adIjowNMJJHhE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JK6adIjowNMJJHhE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JK6adIjowNMJJHhE .marker.cross{stroke:#333333;}#mermaid-svg-JK6adIjowNMJJHhE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JK6adIjowNMJJHhE p{margin:0;}#mermaid-svg-JK6adIjowNMJJHhE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JK6adIjowNMJJHhE .cluster-label text{fill:#333;}#mermaid-svg-JK6adIjowNMJJHhE .cluster-label span{color:#333;}#mermaid-svg-JK6adIjowNMJJHhE .cluster-label span p{background-color:transparent;}#mermaid-svg-JK6adIjowNMJJHhE .label text,#mermaid-svg-JK6adIjowNMJJHhE span{fill:#333;color:#333;}#mermaid-svg-JK6adIjowNMJJHhE .node rect,#mermaid-svg-JK6adIjowNMJJHhE .node circle,#mermaid-svg-JK6adIjowNMJJHhE .node ellipse,#mermaid-svg-JK6adIjowNMJJHhE .node polygon,#mermaid-svg-JK6adIjowNMJJHhE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JK6adIjowNMJJHhE .rough-node .label text,#mermaid-svg-JK6adIjowNMJJHhE .node .label text,#mermaid-svg-JK6adIjowNMJJHhE .image-shape .label,#mermaid-svg-JK6adIjowNMJJHhE .icon-shape .label{text-anchor:middle;}#mermaid-svg-JK6adIjowNMJJHhE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JK6adIjowNMJJHhE .rough-node .label,#mermaid-svg-JK6adIjowNMJJHhE .node .label,#mermaid-svg-JK6adIjowNMJJHhE .image-shape .label,#mermaid-svg-JK6adIjowNMJJHhE .icon-shape .label{text-align:center;}#mermaid-svg-JK6adIjowNMJJHhE .node.clickable{cursor:pointer;}#mermaid-svg-JK6adIjowNMJJHhE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JK6adIjowNMJJHhE .arrowheadPath{fill:#333333;}#mermaid-svg-JK6adIjowNMJJHhE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JK6adIjowNMJJHhE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JK6adIjowNMJJHhE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JK6adIjowNMJJHhE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JK6adIjowNMJJHhE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JK6adIjowNMJJHhE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JK6adIjowNMJJHhE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JK6adIjowNMJJHhE .cluster text{fill:#333;}#mermaid-svg-JK6adIjowNMJJHhE .cluster span{color:#333;}#mermaid-svg-JK6adIjowNMJJHhE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JK6adIjowNMJJHhE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JK6adIjowNMJJHhE rect.text{fill:none;stroke-width:0;}#mermaid-svg-JK6adIjowNMJJHhE .icon-shape,#mermaid-svg-JK6adIjowNMJJHhE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JK6adIjowNMJJHhE .icon-shape p,#mermaid-svg-JK6adIjowNMJJHhE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JK6adIjowNMJJHhE .icon-shape .label rect,#mermaid-svg-JK6adIjowNMJJHhE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JK6adIjowNMJJHhE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JK6adIjowNMJJHhE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JK6adIjowNMJJHhE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 时间桶
过期重置
写入
桶1

(100ms)
桶2

(100ms)
桶3

(100ms)
...

(共10桶)
当前时间

4. 限流器 (Limiter)

4.1 令牌桶限流器

go 复制代码
// 文件路径: core/limit/tokenlimit.go

type TokenLimiter struct {
    rate           int
    burst          int
    store          *redis.Redis
    tokenKey       string
    timestampKey   string
    rescueLock     sync.Mutex
    redisAlive     uint32
    monitorStarted bool
    rescueLimiter  *xrate.Limiter
}

func NewTokenLimiter(rate, burst int, store *redis.Redis, key string) *TokenLimiter {
    tokenKey := fmt.Sprintf(tokenFormat, key)
    timestampKey := fmt.Sprintf(timestampFormat, key)

    return &TokenLimiter{
        rate:         rate,
        burst:        burst,
        store:        store,
        tokenKey:     tokenKey,
        timestampKey: timestampKey,
        redisAlive:   1,
        rescueLimiter: xrate.NewLimiter(xrate.Every(time.Second/time.Duration(rate)), burst),
    }
}

func (lim *TokenLimiter) Allow() bool {
    return lim.AllowN(time.Now(), 1)
}

func (lim *TokenLimiter) AllowN(now time.Time, n int) bool {
    return lim.reserveN(context.Background(), now, n)
}

func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) bool {
    if atomic.LoadUint32(&lim.redisAlive) == 0 {
        return lim.rescueLimiter.AllowN(now, n)
    }

    resp, err := lim.store.ScriptRunCtx(ctx, tokenScript,
        []string{lim.tokenKey, lim.timestampKey},
        []string{
            strconv.Itoa(lim.rate),
            strconv.Itoa(lim.burst),
            strconv.FormatInt(now.Unix(), 10),
            strconv.Itoa(n),
        })

    if err != nil {
        logx.Errorf("fail to use rate limiter: %s, use in-process limiter for rescue", err)
        lim.startMonitor()
        return lim.rescueLimiter.AllowN(now, n)
    }

    code, ok := resp.(int64)
    return code == 1
}

4.2 Redis Lua 脚本

lua 复制代码
-- tokenscript.lua
-- 令牌桶算法实现
local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)

local last_tokens = tonumber(redis.call("get", KEYS[1]))
if last_tokens == nil then
    last_tokens = capacity
end

local last_refreshed = tonumber(redis.call("get", KEYS[2]))
if last_refreshed == nil then
    last_refreshed = 0
end

local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens + (delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
    new_tokens = filled_tokens - requested
end

redis.call("setex", KEYS[1], ttl, new_tokens)
redis.call("setex", KEYS[2], ttl, now)

return allowed and 1 or 0

5. 自适应降载器 (AdaptiveShedder)

5.1 降载原理

go 复制代码
// 文件路径: core/load/adaptiveshedder.go

type AdaptiveShedder struct {
    windows      *collection.RollingWindow
    inflight     *atomicx.AtomicInt64
    maxPass      *atomicx.AtomicInt64
    minRt        *atomicx.AtomicInt64
    windows      *collection.RollingWindow
    coolingTime  *xrate.Limiter
    cpuThreshold int64
}

func (as *AdaptiveShedder) Allow() (Promise, error) {
    if as.shouldDrop() {
        stat.Report("drop")
        return nil, ErrServiceOverloaded
    }

    as.inflight.Inc()
    start := timex.Now()

    return promiseFunc(func(errCode int) {
        as.inflight.Dec()
        rt := timex.Since(start)
        as.addStat(float64(rt))
    }), nil
}

func (as *AdaptiveShedder) shouldDrop() bool {
    if as.systemOverloaded() {
        if as.coolingTime.Allow() {
            return true
        }
    }
    return false
}

func (as *AdaptiveShedder) systemOverloaded() bool {
    return stat.CpuUsage() > as.cpuThreshold
}

5.2 过载判断指标

#mermaid-svg-EGQHOAhNbSyXo2IP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EGQHOAhNbSyXo2IP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EGQHOAhNbSyXo2IP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EGQHOAhNbSyXo2IP .error-icon{fill:#552222;}#mermaid-svg-EGQHOAhNbSyXo2IP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EGQHOAhNbSyXo2IP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EGQHOAhNbSyXo2IP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EGQHOAhNbSyXo2IP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EGQHOAhNbSyXo2IP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EGQHOAhNbSyXo2IP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EGQHOAhNbSyXo2IP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EGQHOAhNbSyXo2IP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EGQHOAhNbSyXo2IP .marker.cross{stroke:#333333;}#mermaid-svg-EGQHOAhNbSyXo2IP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EGQHOAhNbSyXo2IP p{margin:0;}#mermaid-svg-EGQHOAhNbSyXo2IP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EGQHOAhNbSyXo2IP .cluster-label text{fill:#333;}#mermaid-svg-EGQHOAhNbSyXo2IP .cluster-label span{color:#333;}#mermaid-svg-EGQHOAhNbSyXo2IP .cluster-label span p{background-color:transparent;}#mermaid-svg-EGQHOAhNbSyXo2IP .label text,#mermaid-svg-EGQHOAhNbSyXo2IP span{fill:#333;color:#333;}#mermaid-svg-EGQHOAhNbSyXo2IP .node rect,#mermaid-svg-EGQHOAhNbSyXo2IP .node circle,#mermaid-svg-EGQHOAhNbSyXo2IP .node ellipse,#mermaid-svg-EGQHOAhNbSyXo2IP .node polygon,#mermaid-svg-EGQHOAhNbSyXo2IP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EGQHOAhNbSyXo2IP .rough-node .label text,#mermaid-svg-EGQHOAhNbSyXo2IP .node .label text,#mermaid-svg-EGQHOAhNbSyXo2IP .image-shape .label,#mermaid-svg-EGQHOAhNbSyXo2IP .icon-shape .label{text-anchor:middle;}#mermaid-svg-EGQHOAhNbSyXo2IP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EGQHOAhNbSyXo2IP .rough-node .label,#mermaid-svg-EGQHOAhNbSyXo2IP .node .label,#mermaid-svg-EGQHOAhNbSyXo2IP .image-shape .label,#mermaid-svg-EGQHOAhNbSyXo2IP .icon-shape .label{text-align:center;}#mermaid-svg-EGQHOAhNbSyXo2IP .node.clickable{cursor:pointer;}#mermaid-svg-EGQHOAhNbSyXo2IP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EGQHOAhNbSyXo2IP .arrowheadPath{fill:#333333;}#mermaid-svg-EGQHOAhNbSyXo2IP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EGQHOAhNbSyXo2IP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EGQHOAhNbSyXo2IP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EGQHOAhNbSyXo2IP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EGQHOAhNbSyXo2IP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EGQHOAhNbSyXo2IP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EGQHOAhNbSyXo2IP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EGQHOAhNbSyXo2IP .cluster text{fill:#333;}#mermaid-svg-EGQHOAhNbSyXo2IP .cluster span{color:#333;}#mermaid-svg-EGQHOAhNbSyXo2IP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EGQHOAhNbSyXo2IP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EGQHOAhNbSyXo2IP rect.text{fill:none;stroke-width:0;}#mermaid-svg-EGQHOAhNbSyXo2IP .icon-shape,#mermaid-svg-EGQHOAhNbSyXo2IP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EGQHOAhNbSyXo2IP .icon-shape p,#mermaid-svg-EGQHOAhNbSyXo2IP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EGQHOAhNbSyXo2IP .icon-shape .label rect,#mermaid-svg-EGQHOAhNbSyXo2IP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EGQHOAhNbSyXo2IP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EGQHOAhNbSyXo2IP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EGQHOAhNbSyXo2IP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



系统过载?
CPU使用率 > 阈值?
正在冷却期?
允许请求
丢弃请求

6. 实际应用示例

6.1 HTTP 服务配置

go 复制代码
server := rest.MustNewServer(rest.RestConf{
    Name: "api",
    Host: "0.0.0.0",
    Port: 8888,
    Middlewares: rest.MiddlewaresConf{
        Trace:    true,
        Log:      true,
        Recover:  true,
        Breaker:  true,  // 启用熔断
        Shedding: true,  // 启用降载
        Timeout:  true,
    },
    CpuThreshold: 900,  // CPU 阈值 (0-1000)
})

6.2 RPC 服务配置

go 复制代码
server := zrpc.MustNewServer(zrpc.RpcServerConf{
    Name: "rpc",
    ListenOn: "0.0.0.0:8080",
    Middlewares: zrpc.ServerMiddlewaresConf{
        Trace:    true,
        Log:      true,
        Recover:  true,
        Breaker:  true,
    },
    CpuThreshold: 900,
})

6.3 自定义熔断器

go 复制代码
myBreaker := breaker.NewBreaker(
    breaker.WithName("custom-breaker"),
)

err := myBreaker.Do(func() error {
    return externalCall()
})

if err == breaker.ErrServiceUnavailable {
    logx.Info("Circuit breaker open, using fallback")
    return fallbackResponse()
}

相关推荐
2601_961593422 小时前
Rust 开发环境配置繁琐?RustRover 开箱即用搞定编码调试
开发语言·后端·macos·rust
犀利豆4 小时前
AI in Harness(三)
人工智能·后端
IT_陈寒5 小时前
Java的Stream.parallel()把我CPU跑爆了,这种优化要谨慎
前端·人工智能·后端
子兮曰6 小时前
Bun 重写为 Rust:11天、64个Claude、16.5万美元,一次改变行业认知的激进实验
前端·后端·bun
子兮曰6 小时前
Node.js v26.5.0 深度解读:import Text、流式 ReadableStreamTee、以及隐藏的安全加固浪潮
前端·后端·node.js
子兮曰6 小时前
TypeScript 7.0 RC 深度解读:Go 重写完成,10 倍提速,编译器的「奇点时刻」
前端·后端·typescript
kosan6 小时前
.NET/C# 实战:我如何把登录验证码从“裸奔”改到“铜墙铁壁”
后端
王中阳Go7 小时前
AI编程实践:规则 + 技能 + 子代理 + 钩子,拆解Trae AI 开发规范的完整落地范式
后端·agent·ai编程
码事漫谈7 小时前
放下代码洁癖,享受挖坑人生
后端