Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

expvar - 运行时导出变量

概述

expvar 包提供了一个标准化的方式,用于在运行中的 Go 程序中导出变量。

expvar 包是什么:

  • 📦 变量导出:将程序运行时变量导出为 JSON
  • 🔧 监控指标:用于监控应用程序的性能指标
  • 📋 HTTP 服务:通过 HTTP 端点自动暴露变量
  • 🛠️ 线程安全:所有操作都是并发安全的
  • 📊 标准格式:使用标准 JSON 格式导出数据
  • 🌐 Web 界面:可与监控工具集成

主要用途:

  • 🌐 性能监控:导出计数器、延迟、错误率等指标
  • 📧 调试工具:运行时查看程序状态
  • 🔐 健康检查:服务健康状态监控
  • 📊 指标收集:与监控系统集成(如 Prometheus)
  • 🖼️ 运行时统计:内存使用、Goroutine 数量等
  • 🔑 自定义指标:业务相关的性能指标

重要说明:

  • ⚠️ 线程安全:所有类型的方法都是并发安全的
  • ⚠️ 自动注册:某些类型在创建时自动注册
  • ⚠️ HTTP 端点:默认在 /debug/vars 暴露
  • ⚠️ JSON 格式:导出的数据是标准 JSON 格式
  • ✅ 标准库:Go 标准库提供完整支持
  • ✅ 内置类型:提供 Int、Float、String、Map、List 等类型
  • ✅ 自定义类型:可以实现 Var 接口创建自定义类型

基本使用示例:

package main

import (
    "expvar"
    "net/http"
)

// 创建并注册变量
var (
    requests  = expvar.NewInt("requests")
    errors    = expvar.NewInt("errors")
    version   = expvar.NewString("version")
    stats     = expvar.NewMap("stats")
)

func main() {
    // 设置初始值
    version.Set("1.0.0")
    
    // 启动 HTTP 服务
    http.ListenAndServe(":8080", nil)
}

访问变量:

# 访问 HTTP 端点
curl http://localhost:8080/debug/vars

# JSON 响应
{
    "cmdline": ["./myapp"],
    "requests": 1000,
    "errors": 5,
    "version": "1.0.0",
    "stats": {
        "latency": 50,
        "memory": 1024
    }
}

核心类型

Var 接口

Var 是所有导出变量的接口:

type Var interface {
    String() string
}

说明:

  • Var 是 expvar 包的核心接口
  • 只有一个方法 String() 返回 JSON 编码的字符串
  • 所有导出变量类型都必须实现此接口
  • String() 方法返回的应该是 JSON 格式

实现 Var 接口的类型:

  • *Int - 整数类型
  • *Float - 浮点数类型
  • *String - 字符串类型
  • *Func - 函数类型
  • *Map - 映射类型
  • *List - 列表类型

自定义 Var 示例:

type Counter struct {
    value int64
}

func (c *Counter) String() string {
    return fmt.Sprintf("%d", c.value)
}

// 注册自定义变量
expvar.Publish("custom_counter", &Counter{})

Int 类型

Int 是一个原子整数变量:

type Int struct {
    // 包含过滤或未导出的字段
}

创建方法:

// 创建并注册新的 Int 变量
func NewInt(name string) *Int

// 创建未注册的 Int 变量
func NewIntValue(value int64) *Int

方法:

// Add 将 delta 加到值上(原子操作)
func (i *Int) Add(delta int64)

// Set 设置值(原子操作)
func (i *Int) Set(value int64)

// String 返回 JSON 字符串
func (i *Int) String() string

// Value 获取当前值
func (i *Int) Value() int64

完整示例:

package main

import (
    "expvar"
    "fmt"
)

func main() {
    // 创建并注册
    requests := expvar.NewInt("api_requests")
    
    // 设置初始值
    requests.Set(0)
    
    // 增加计数
    requests.Add(1)
    requests.Add(5)
    
    // 获取值
    value := requests.Value()
    fmt.Printf("Requests: %d\n", value) // Requests: 6
    
    // 转换为 JSON
    json := requests.String()
    fmt.Printf("JSON: %s\n", json) // JSON: 6
    
    // 创建未注册的变量
    temp := expvar.NewIntValue(100)
    fmt.Printf("Temp: %s\n", temp.String()) // Temp: 100
}

使用场景:

  • 请求计数器
  • 错误计数器
  • 处理项目数
  • 任何需要原子操作的整数指标

Float 类型

Float 是一个原子浮点数变量:

type Float struct {
    // 包含过滤或未导出的字段
}

创建方法:

// 创建并注册新的 Float 变量
func NewFloat(name string) *Float

// 创建未注册的 Float 变量
func NewFloatValue(value float64) *Float

方法:

// Add 将 delta 加到值上(原子操作)
func (f *Float) Add(delta float64)

// Set 设置值(原子操作)
func (f *Float) Set(value float64)

// String 返回 JSON 字符串
func (f *Float) String() string

// Value 获取当前值
func (f *Float) Value() float64

完整示例:

package main

import (
    "expvar"
    "fmt"
)

func main() {
    // 创建并注册
    temperature := expvar.NewFloat("temperature")
    
    // 设置值
    temperature.Set(25.5)
    
    // 增加
    temperature.Add(0.5)
    
    // 获取值
    value := temperature.Value()
    fmt.Printf("Temperature: %.2f\n", value) // Temperature: 26.00
    
    // 转换为 JSON
    json := temperature.String()
    fmt.Printf("JSON: %s\n", json) // JSON: 26
    
    // 创建未注册的变量
    ratio := expvar.NewFloatValue(0.75)
    fmt.Printf("Ratio: %s\n", ratio.String()) // Ratio: 0.75
}

使用场景:

  • 平均响应时间
  • CPU 使用率
  • 内存使用百分比
  • 任何需要小数点的指标

String 类型

String 是一个字符串变量:

type String struct {
    // 包含过滤或未导出的字段
}

创建方法:

// 创建并注册新的 String 变量
func NewString(name string) *String

// 创建未注册的 String 变量
func NewStringValue(value string) *String

方法:

// Set 设置值
func (s *String) Set(value string)

// String 返回 JSON 字符串
func (s *String) String() string

// Value 获取当前值
func (s *String) Value() string

完整示例:

package main

import (
    "expvar"
    "fmt"
)

func main() {
    // 创建并注册
    version := expvar.NewString("version")
    
    // 设置值
    version.Set("1.0.0")
    
    // 获取值
    value := version.Value()
    fmt.Printf("Version: %s\n", value) // Version: 1.0.0
    
    // 转换为 JSON
    json := version.String()
    fmt.Printf("JSON: %s\n", json) // JSON: "1.0.0"
    
    // 创建未注册的变量
    env := expvar.NewStringValue("production")
    fmt.Printf("Env: %s\n", env.String()) // Env: "production"
}

使用场景:

  • 版本号
  • 环境标识(dev/staging/prod)
  • 服务名称
  • 配置信息

Func 类型

Func 是一个函数类型的变量:

type Func struct {
    // 包含过滤或未导出的字段
}

创建方法:

// 创建并注册新的 Func 变量
func NewFunc(name string, f func() string) *Func

// 创建未注册的 Func 变量
func NewFuncValue(f func() string) *Func

方法:

// String 调用函数并返回结果
func (f *Func) String() string

完整示例:

package main

import (
    "expvar"
    "fmt"
    "runtime"
    "time"
)

func main() {
    // 创建并注册 - 返回 Goroutine 数量
    goroutines := expvar.NewFunc("goroutines", func() string {
        return fmt.Sprintf("%d", runtime.NumGoroutine())
    })
    
    // 创建并注册 - 返回运行时间
    startTime := time.Now()
    uptime := expvar.NewFunc("uptime", func() string {
        return fmt.Sprintf("%.0f", time.Since(startTime).Seconds())
    })
    
    // 创建未注册的函数变量
    custom := expvar.NewFuncValue(func() string {
        return `"custom value"`
    })
    
    // 调用
    fmt.Printf("Goroutines: %s\n", goroutines.String())
    fmt.Printf("Uptime: %s\n", uptime.String())
    fmt.Printf("Custom: %s\n", custom.String())
}

使用场景:

  • 动态计算的值
  • 运行时统计(Goroutine 数量、内存使用)
  • 运行时间
  • 任何需要实时计算的值

Map 类型

Map 是一个字符串到 Var 的映射:

type Map struct {
    // 包含过滤或未导出的字段
}

创建方法:

// 创建并注册新的 Map 变量
func NewMap(name string) *Map

// 创建未注册的 Map 变量
func NewMapValue() *Map

方法:

// Add 添加或更新一个键值对
func (m *Map) Add(key string, delta int64)

// AddFloat 添加或更新一个浮点键值对
func (m *Map) AddFloat(key string, delta float64)

// Get 获取指定键的值
func (m *Map) Get(key string) Var

// Set 设置键值对
func (m *Map) Set(key string, v Var)

// SetInt 设置整数键值对
func (m *Map) SetInt(key string, v int64)

// SetFloat 设置浮点数键值对
func (m *Map) SetFloat(key string, v float64)

// SetString 设置字符串键值对
func (m *Map) SetString(key string, v string)

// String 返回 JSON 字符串
func (m *Map) String() string

// Value 获取所有键值对
func (m *Map) Value() map[string]Var

// Delete 删除指定键
func (m *Map) Delete(key string)

// Do 遍历所有键值对
func (m *Map) Do(f func(kv string))

// Init 初始化 Map
func (m *Map) Init()

完整示例:

package main

import (
    "expvar"
    "fmt"
)

func main() {
    // 创建并注册
    stats := expvar.NewMap("stats")
    
    // 设置键值对
    stats.SetInt("requests", 1000)
    stats.SetInt("errors", 5)
    stats.SetFloat("latency", 50.5)
    stats.SetString("status", "running")
    
    // 获取值
    requests := stats.Get("requests")
    fmt.Printf("Requests: %s\n", requests.String()) // Requests: 1000
    
    // 增加计数
    stats.Add("requests", 100)
    stats.AddFloat("latency", 0.5)
    
    // 获取所有值
    all := stats.Value()
    for k, v := range all {
        fmt.Printf("%s: %s\n", k, v.String())
    }
    
    // 删除键
    stats.Delete("status")
    
    // 遍历
    stats.Do(func(kv string) {
        fmt.Printf("KV: %s\n", kv)
    })
    
    // JSON 输出
    fmt.Printf("JSON: %s\n", stats.String())
    // JSON: {"errors":5,"latency":51,"requests":1100}
    
    // 创建未注册的 Map
    temp := expvar.NewMapValue()
    temp.SetInt("temp", 25)
    fmt.Printf("Temp: %s\n", temp.String()) // Temp: {"temp":25}
}

使用场景:

  • 分组指标(按端点、按用户)
  • 多维度统计
  • 相关的指标集合

List 类型

List 是一个 Var 的列表:

type List struct {
    // 包含过滤或未导出的字段
}

创建方法:

// 创建并注册新的 List 变量
func NewList(name string) *List

// 创建未注册的 List 变量
func NewListValue() *List

方法:

// Add 添加一个值到列表末尾
func (l *List) Add(v Var)

// AddInt 添加整数
func (l *List) AddInt(v int64)

// AddFloat 添加浮点数
func (l *List) AddFloat(v float64)

// AddString 添加字符串
func (l *List) AddString(v string)

// AddFunc 添加函数
func (l *List) AddFunc(f func() string)

// AddMap 添加 Map
func (l *List) AddMap(m *Map)

// AddList 添加 List
func (l *List) AddList(sub *List)

// String 返回 JSON 字符串
func (l *List) String() string

完整示例:

package main

import (
    "expvar"
    "fmt"
)

func main() {
    // 创建并注册
    servers := expvar.NewList("servers")
    
    // 添加值
    servers.AddString("server1")
    servers.AddString("server2")
    servers.AddInt(100)
    servers.AddFloat(99.9)
    
    // 添加 Map
    server1 := expvar.NewMapValue()
    server1.SetString("name", "web-01")
    server1.SetInt("port", 8080)
    servers.AddMap(server1)
    
    // 添加函数
    servers.AddFunc(func() string {
        return `"dynamic"`
    })
    
    // JSON 输出
    fmt.Printf("Servers: %s\n", servers.String())
    // Servers: ["server1","server2",100,99.9,{"name":"web-01","port":8080},"dynamic"]
    
    // 创建未注册的 List
    temp := expvar.NewListValue()
    temp.AddInt(1)
    temp.AddInt(2)
    fmt.Printf("Temp: %s\n", temp.String()) // Temp: [1,2]
}

使用场景:

  • 服务器列表
  • 活动连接
  • 任务队列
  • 任何需要有序集合的场景

核心函数

注册函数

Publish - 注册一个变量:

func Publish(name string, v Var)

说明:

  • 将变量注册到 expvar 系统
  • 如果名称已存在,会 panic
  • 注册的变量可通过 HTTP 端点访问

示例:

counter := &expvar.Int{}
counter.Set(100)
expvar.Publish("my_counter", counter)

Get - 获取已注册的变量:

func Get(name string) Var

说明:

  • 获取已注册的变量
  • 如果不存在,返回 nil

示例:

v := expvar.Get("my_counter")
if v != nil {
    fmt.Printf("Value: %s\n", v.String())
}

删除函数

Unpublish - 删除已注册的变量:

func Unpublish(name string)

说明:

  • 从 expvar 系统中删除变量
  • 如果不存在,不执行任何操作

示例:

expvar.Unpublish("my_counter")

HTTP 服务

Handler - HTTP 处理函数:

var Handler http.Handler

说明:

  • 返回处理 /debug/vars 请求的 http.Handler
  • 自动注册到 http.DefaultServeMux
  • 返回 JSON 格式的所有已注册变量

完整示例:

package main

import (
    "expvar"
    "net/http"
)

func main() {
    // 注册变量
    expvar.NewInt("requests")
    expvar.NewString("version").Set("1.0.0")
    
    // 启动 HTTP 服务
    // 访问 http://localhost:8080/debug/vars
    http.ListenAndServe(":8080", nil)
}

访问结果:

{
    "cmdline": ["./myapp"],
    "memstats": {...},
    "requests": 0,
    "version": "1.0.0"
}

完整示例

示例 1:基本使用 - 计数器监控

package main

import (
    "expvar"
    "fmt"
    "net/http"
    "time"
)

// 定义全局变量
var (
    requests  *expvar.Int
    errors    *expvar.Int
    latency   *expvar.Float
    version   *expvar.String
    startTime *expvar.Func
)

func init() {
    // 初始化变量
    requests = expvar.NewInt("requests")
    errors = expvar.NewInt("errors")
    latency = expvar.NewFloat("avg_latency_ms")
    version = expvar.NewString("version")
    
    // 设置初始值
    version.Set("1.0.0")
    requests.Set(0)
    errors.Set(0)
    latency.Set(0.0)
    
    // 动态计算运行时间
    start := time.Now()
    startTime = expvar.NewFunc("uptime_seconds", func() string {
        return fmt.Sprintf("%.0f", time.Since(start).Seconds())
    })
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
    // 增加请求计数
    requests.Add(1)
    
    // 模拟处理
    start := time.Now()
    time.Sleep(10 * time.Millisecond)
    
    // 更新延迟
    currentLatency := float64(time.Since(start).Milliseconds())
    latency.Set(currentLatency)
    
    // 模拟错误
    if time.Now().Second()%10 == 0 {
        errors.Add(1)
        http.Error(w, "Server error", http.StatusInternalServerError)
        return
    }
    
    fmt.Fprintf(w, "Hello! Requests: %d, Errors: %d", 
        requests.Value(), errors.Value())
}

func main() {
    // 注册 HTTP 处理函数
    http.HandleFunc("/", handleRequest)
    
    fmt.Println("Server starting on :8080")
    fmt.Println("Visit http://localhost:8080/debug/vars for metrics")
    
    // 启动服务
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

访问:

# 查看指标
curl http://localhost:8080/debug/vars

# 响应示例
{
    "cmdline": ["./myapp"],
    "memstats": {...},
    "requests": 150,
    "errors": 15,
    "avg_latency_ms": 10.5,
    "version": "1.0.0",
    "uptime_seconds": "3600"
}

示例 2:使用 Map 分组统计

package main

import (
    "expvar"
    "fmt"
    "net/http"
    "sync/atomic"
)

// API 统计
var apiStats = expvar.NewMap("api")

// 端点统计
var (
    userStats   *expvar.Map
    orderStats  *expvar.Map
    productStats *expvar.Map
)

var requestCount int64

func init() {
    // 创建子 Map
    userStats = expvar.NewMapValue()
    orderStats = expvar.NewMapValue()
    productStats = expvar.NewMapValue()
    
    // 初始化统计
    initMap(userStats, "user")
    initMap(orderStats, "order")
    initMap(productStats, "product")
    
    // 添加到主 Map
    apiStats.Set("users", userStats)
    apiStats.Set("orders", orderStats)
    apiStats.Set("products", productStats)
}

func initMap(m *expvar.Map, prefix string) {
    m.SetInt("requests", 0)
    m.SetInt("errors", 0)
    m.SetFloat("avg_latency_ms", 0.0)
    m.SetString("status", "healthy")
}

func userHandler(w http.ResponseWriter, r *http.Request) {
    count := atomic.AddInt64(&requestCount, 1)
    
    userStats.Add("requests", 1)
    
    // 模拟处理
    if count%10 == 0 {
        userStats.Add("errors", 1)
        http.Error(w, "Error", http.StatusInternalServerError)
        return
    }
    
    fmt.Fprintf(w, "User API")
}

func orderHandler(w http.ResponseWriter, r *http.Request) {
    orderStats.Add("requests", 1)
    fmt.Fprintf(w, "Order API")
}

func productHandler(w http.ResponseWriter, r *http.Request) {
    productStats.Add("requests", 1)
    fmt.Fprintf(w, "Product API")
}

func main() {
    http.HandleFunc("/api/users", userHandler)
    http.HandleFunc("/api/orders", orderHandler)
    http.HandleFunc("/api/products", productHandler)
    
    fmt.Println("Server on :8080")
    fmt.Println("Metrics: http://localhost:8080/debug/vars")
    
    http.ListenAndServe(":8080", nil)
}

访问结果:

{
    "api": {
        "users": {
            "requests": 100,
            "errors": 10,
            "avg_latency_ms": 15.5,
            "status": "healthy"
        },
        "orders": {
            "requests": 50,
            "errors": 0,
            "avg_latency_ms": 20.0,
            "status": "healthy"
        },
        "products": {
            "requests": 200,
            "errors": 5,
            "avg_latency_ms": 12.0,
            "status": "healthy"
        }
    }
}

示例 3:自定义 Var 类型

package main

import (
    "expvar"
    "fmt"
    "net/http"
    "sync"
    "time"
)

// Histogram 直方图统计
type Histogram struct {
    mu    sync.Mutex
    count int64
    sum   int64
    min   int64
    max   int64
}

func (h *Histogram) Observe(value int64) {
    h.mu.Lock()
    defer h.mu.Unlock()
    
    h.count++
    h.sum += value
    
    if h.count == 1 || value < h.min {
        h.min = value
    }
    if h.count == 1 || value > h.max {
        h.max = value
    }
}

func (h *Histogram) String() string {
    h.mu.Lock()
    defer h.mu.Unlock()
    
    var avg float64
    if h.count > 0 {
        avg = float64(h.sum) / float64(h.count)
    }
    
    return fmt.Sprintf(`{"count":%d,"sum":%d,"min":%d,"max":%d,"avg":%.2f}`,
        h.count, h.sum, h.min, h.max, avg)
}

// 全局直方图
var latencyHistogram = &Histogram{}

func init() {
    expvar.Publish("latency_histogram", latencyHistogram)
}

func handler(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    
    // 处理请求
    time.Sleep(time.Duration(10+r.Intn(90)) * time.Millisecond)
    
    duration := time.Since(start).Milliseconds()
    latencyHistogram.Observe(duration)
    
    fmt.Fprintf(w, "OK")
}

func main() {
    http.HandleFunc("/", handler)
    
    fmt.Println("Server on :8080")
    http.ListenAndServe(":8080", nil)
}

访问结果:

{
    "latency_histogram": {
        "count": 1000,
        "sum": 50000,
        "min": 10,
        "max": 99,
        "avg": 50.00
    }
}

示例 4:数据库连接池监控

package main

import (
    "database/sql"
    "expvar"
    "fmt"
    "net/http"
    _ "github.com/lib/pq"
    "time"
)

var dbStats = expvar.NewMap("db")
var db *sql.DB

func init() {
    // 初始化数据库统计
    dbStats.SetInt("max_open_conns", 0)
    dbStats.SetInt("open_conns", 0)
    dbStats.SetInt("in_use", 0)
    dbStats.SetInt("idle", 0)
    dbStats.SetInt("wait_count", 0)
    dbStats.SetFloat("wait_duration_ms", 0.0)
    dbStats.SetInt("max_idle_closed", 0)
    dbStats.SetInt("max_lifetime_closed", 0)
    
    // 定期更新统计
    go updateDBStats()
}

func updateDBStats() {
    ticker := time.NewTicker(5 * time.Second)
    for range ticker.C {
        if db == nil {
            continue
        }
        
        stats := db.Stats()
        
        dbStats.SetInt("max_open_conns", int64(stats.MaxOpenConnections))
        dbStats.SetInt("open_conns", int64(stats.OpenConnections))
        dbStats.SetInt("in_use", int64(stats.InUse))
        dbStats.SetInt("idle", int64(stats.Idle))
        dbStats.SetInt("wait_count", int64(stats.WaitCount))
        
        waitDuration := float64(stats.WaitDuration) / float64(time.Millisecond)
        dbStats.SetFloat("wait_duration_ms", waitDuration)
        
        dbStats.SetInt("max_idle_closed", int64(stats.MaxIdleClosed))
        dbStats.SetInt("max_lifetime_closed", int64(stats.MaxLifetimeClosed))
    }
}

func main() {
    // 连接数据库
    var err error
    db, err = sql.Open("postgres", "postgres://user:pass@localhost/db?sslmode=disable")
    if err != nil {
        panic(err)
    }
    
    // 设置连接池
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    http.HandleFunc("/query", func(w http.ResponseWriter, r *http.Request) {
        var count int
        db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
        fmt.Fprintf(w, "User count: %d", count)
    })
    
    fmt.Println("Server on :8080")
    http.ListenAndServe(":8080", nil)
}

访问结果:

{
    "db": {
        "max_open_conns": 25,
        "open_conns": 8,
        "in_use": 3,
        "idle": 5,
        "wait_count": 0,
        "wait_duration_ms": 0.0,
        "max_idle_closed": 0,
        "max_lifetime_closed": 0
    }
}

示例 5:Goroutine 和内存监控

package main

import (
    "expvar"
    "fmt"
    "net/http"
    "runtime"
    "time"
)

var (
    runtimeStats = expvar.NewMap("runtime")
    goroutines   *expvar.Func
    memStats     *expvar.Func
)

func init() {
    // Goroutine 数量
    goroutines = expvar.NewFunc("num_goroutine", func() string {
        return fmt.Sprintf("%d", runtime.NumGoroutine())
    })
    
    // 内存统计
    memStats = expvar.NewFunc("mem_stats", func() string {
        var m runtime.MemStats
        runtime.ReadMemStats(&m)
        
        return fmt.Sprintf(
            `{"alloc_mb":%d,"sys_mb":%d,"num_gc":%d,"pause_total_ns":%d}`,
            m.Alloc/1024/1024,
            m.Sys/1024/1024,
            m.NumGC,
            m.PauseTotalNs,
        )
    })
    
    // 添加到 runtimeStats
    runtimeStats.Set("goroutines", goroutines)
    runtimeStats.Set("memory", memStats)
}

func worker(id int) {
    for {
        time.Sleep(1 * time.Second)
        fmt.Printf("Worker %d working\n", id)
    }
}

func main() {
    // 启动一些 Goroutine
    for i := 0; i < 5; i++ {
        go worker(i)
    }
    
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Check /debug/vars for runtime stats")
    })
    
    fmt.Println("Server on :8080")
    http.ListenAndServe(":8080", nil)
}

访问结果:

{
    "runtime": {
        "goroutines": 6,
        "memory": {
            "alloc_mb": 2,
            "sys_mb": 15,
            "num_gc": 3,
            "pause_total_ns": 1000000
        }
    }
}

示例 6:任务队列监控

package main

import (
    "expvar"
    "fmt"
    "net/http"
    "sync"
    "time"
)

// TaskQueue 任务队列
type TaskQueue struct {
    mu       sync.Mutex
    tasks    []string
    processed int64
    failed    int64
}

func (tq *TaskQueue) Add(task string) {
    tq.mu.Lock()
    defer tq.mu.Unlock()
    tq.tasks = append(tq.tasks, task)
}

func (tq *TaskQueue) Process() {
    tq.mu.Lock()
    defer tq.mu.Unlock()
    
    if len(tq.tasks) == 0 {
        return
    }
    
    task := tq.tasks[0]
    tq.tasks = tq.tasks[1:]
    tq.processed++
    
    fmt.Printf("Processed: %s\n", task)
}

func (tq *TaskQueue) String() string {
    tq.mu.Lock()
    defer tq.mu.Unlock()
    
    return fmt.Sprintf(
        `{"queue_size":%d,"processed":%d,"failed":%d}`,
        len(tq.tasks),
        tq.processed,
        tq.failed,
    )
}

var taskQueue = &TaskQueue{}

func init() {
    expvar.Publish("task_queue", taskQueue)
    
    // 启动后台处理器
    go func() {
        for {
            taskQueue.Process()
            time.Sleep(100 * time.Millisecond)
        }
    }()
}

func main() {
    http.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {
        task := r.URL.Query().Get("task")
        if task == "" {
            task = "default_task"
        }
        taskQueue.Add(task)
        fmt.Fprintf(w, "Task added: %s", task)
    })
    
    fmt.Println("Server on :8080")
    http.ListenAndServe(":8080", nil)
}

访问结果:

{
    "task_queue": {
        "queue_size": 5,
        "processed": 95,
        "failed": 0
    }
}

示例 7:多服务指标收集

package main

import (
    "expvar"
    "fmt"
    "net/http"
    "sync/atomic"
)

// 服务指标
type ServiceMetrics struct {
    name      string
    requests  *expvar.Int
    errors    *expvar.Int
    latency   *expvar.Float
    active    *expvar.Int
    status    *expvar.String
}

func NewServiceMetrics(name string) *ServiceMetrics {
    sm := &ServiceMetrics{
        name: name,
    }
    
    // 创建 Map
    m := expvar.NewMap(name)
    sm.requests = expvar.NewIntValue(0)
    sm.errors = expvar.NewIntValue(0)
    sm.latency = expvar.NewFloatValue(0.0)
    sm.active = expvar.NewIntValue(0)
    sm.status = expvar.NewStringValue("healthy")
    
    m.Set("requests", sm.requests)
    m.Set("errors", sm.errors)
    m.Set("latency_ms", sm.latency)
    m.Set("active_connections", sm.active)
    m.Set("status", sm.status)
    
    return sm
}

func (sm *ServiceMetrics) RecordRequest(latency float64) {
    sm.requests.Add(1)
    sm.latency.Set(latency)
}

func (sm *ServiceMetrics) RecordError() {
    sm.errors.Add(1)
}

func (sm *ServiceMetrics) SetActive(n int64) {
    sm.active.Set(n)
}

func (sm *ServiceMetrics) SetStatus(status string) {
    sm.status.Set(status)
}

// 全局指标
var (
    userService  *ServiceMetrics
    orderService *ServiceMetrics
    payService   *ServiceMetrics
)

func init() {
    userService = NewServiceMetrics("user_service")
    orderService = NewServiceMetrics("order_service")
    payService = NewServiceMetrics("pay_service")
}

func userHandler(w http.ResponseWriter, r *http.Request) {
    atomic.AddInt64(&userService.active.value, 1)
    defer atomic.AddInt64(&userService.active.value, -1)
    
    // 模拟处理
    userService.RecordRequest(15.5)
    
    fmt.Fprintf(w, "User Service")
}

func orderHandler(w http.ResponseWriter, r *http.Request) {
    orderService.RecordRequest(25.0)
    fmt.Fprintf(w, "Order Service")
}

func payHandler(w http.ResponseWriter, r *http.Request) {
    payService.RecordRequest(50.0)
    if time.Now().Second()%5 == 0 {
        payService.RecordError()
    }
    fmt.Fprintf(w, "Pay Service")
}

func main() {
    http.HandleFunc("/user", userHandler)
    http.HandleFunc("/order", orderHandler)
    http.HandleFunc("/pay", payHandler)
    
    fmt.Println("Server on :8080")
    fmt.Println("Metrics: http://localhost:8080/debug/vars")
    
    http.ListenAndServe(":8080", nil)
}

访问结果:

{
    "user_service": {
        "requests": 1000,
        "errors": 0,
        "latency_ms": 15.5,
        "active_connections": 5,
        "status": "healthy"
    },
    "order_service": {
        "requests": 500,
        "errors": 0,
        "latency_ms": 25.0,
        "active_connections": 2,
        "status": "healthy"
    },
    "pay_service": {
        "requests": 200,
        "errors": 20,
        "latency_ms": 50.0,
        "active_connections": 1,
        "status": "healthy"
    }
}

最佳实践

1. 使用有意义的变量名

// ✅ 推荐:清晰描述性名称
expvar.NewInt("http_requests_total")
expvar.NewFloat("response_latency_seconds")

// ❌ 不推荐:模糊名称
expvar.NewInt("req")
expvar.NewFloat("lat")

2. 使用 Map 分组相关指标

// ✅ 推荐:使用 Map 分组
dbStats := expvar.NewMap("database")
dbStats.SetInt("connections", 10)
dbStats.SetInt("queries", 1000)

// ❌ 不推荐:扁平化命名
expvar.NewInt("db_connections")
expvar.NewInt("db_queries")

3. 定期更新动态指标

// ✅ 推荐:后台 goroutine 定期更新
go func() {
    ticker := time.NewTicker(5 * time.Second)
    for range ticker.C {
        stats := getStats()
        metrics.SetFloat("cpu_usage", stats.CPU)
    }
}()

4. 使用 Func 计算实时值

// ✅ 推荐:实时计算
expvar.NewFunc("uptime", func() string {
    return fmt.Sprintf("%.0f", time.Since(start).Seconds())
})

5. 保护敏感信息

// ✅ 推荐:只暴露非敏感指标
expvar.NewInt("request_count")

// ❌ 不推荐:暴露敏感信息
expvar.NewString("api_key")  // 不要暴露!
expvar.NewString("password") // 不要暴露!

6. 使用自定义类型处理复杂统计

// ✅ 推荐:自定义类型处理复杂逻辑
type Histogram struct {
    // ...
}

func (h *Histogram) String() string {
    // 返回 JSON 格式
}

expvar.Publish("latency_histogram", &Histogram{})

7. 限制导出的变量数量

// ✅ 推荐:只导出关键指标
// 选择最重要的 10-20 个指标

// ❌ 不推荐:导出所有变量
// 会导致 JSON 响应过大,影响性能

注意事项

1. 并发安全

所有 expvar 类型的方法都是并发安全的,可以直接在多个 goroutine 中使用。

// ✅ 安全:直接调用
counter.Add(1)

// ✅ 安全:多个 goroutine 同时调用
go counter.Add(1)
go counter.Add(2)

2. 名称冲突

// ❌ 会导致 panic
expvar.NewInt("requests")
expvar.NewInt("requests") // panic: duplicate expvar name

// ✅ 推荐:先检查
if expvar.Get("requests") == nil {
    expvar.NewInt("requests")
}

3. HTTP 端点安全

// ⚠️ 注意:/debug/vars 端点默认无认证
// 生产环境应该添加认证或限制访问

http.HandleFunc("/debug/vars", func(w http.ResponseWriter, r *http.Request) {
    // 添加认证逻辑
    if !isAuthorized(r) {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }
    expvar.Handler.ServeHTTP(w, r)
})

4. JSON 格式

String() 方法应该返回有效的 JSON:

// ✅ 正确
func (i *Int) String() string {
    return fmt.Sprintf("%d", i.value) // 数字不需要引号
}

// ✅ 正确
func (s *String) String() string {
    return fmt.Sprintf(`"%s"`, s.value) // 字符串需要引号
}

性能优化

1. 避免频繁创建变量

// ✅ 推荐:全局变量
var counter = expvar.NewInt("counter")

// ❌ 不推荐:每次调用都创建
func handler() {
    expvar.NewInt("counter") // 浪费资源
}

2. 减少 String() 调用频率

// ✅ 推荐:缓存结果
var cached string
var lastUpdate time.Time

func getCached() string {
    if time.Since(lastUpdate) > time.Second {
        cached = computeExpvar()
        lastUpdate = time.Now()
    }
    return cached
}

3. 使用原子操作

// ✅ expvar.Int 内部使用原子操作
counter.Add(1) // 线程安全且高效

// ❌ 不推荐:手动加锁
var mu sync.Mutex
var count int64

mu.Lock()
count++
mu.Unlock()

总结

核心类型

类型用途线程安全
Int整数计数器✅
Float浮点数指标✅
String字符串值✅
Func动态计算值✅
Map键值对集合✅
List值列表✅

核心函数

函数用途说明
NewInt创建 Int自动注册
NewFloat创建 Float自动注册
NewString创建 String自动注册
NewMap创建 Map自动注册
NewList创建 List自动注册
NewFunc创建 Func自动注册
Publish注册变量手动注册
Get获取变量查询已注册变量
Unpublish删除变量移除注册

使用场景

场景推荐类型示例
计数器Int请求数、错误数
仪表Float延迟、使用率
状态String版本、环境
实时计算Func运行时间、Goroutine 数
分组指标Map按端点、按服务
列表List服务器列表、连接列表
复杂统计自定义 Var直方图、百分位

HTTP 端点

端点用途格式
/debug/vars导出所有变量JSON

最佳实践

  • ✅ 使用有意义的变量名
  • ✅ 使用 Map 分组相关指标
  • ✅ 定期更新动态指标
  • ✅ 使用 Func 计算实时值
  • ✅ 保护敏感信息
  • ✅ 使用自定义类型处理复杂统计
  • ✅ 限制导出的变量数量
  • ✅ 注意 HTTP 端点安全

参考资料


最后更新:2026-04-03
Go 版本:Go 1.23+