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+