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+