前言

你是否曾经遇到过这样的情况,在开发环境排查问题,因为一些数据保存在了一些全局变量中,这些变量往往是一个 map 或者是一个数组,想看看在运行过程中,这里面究竟存放了什么数据,有时不得不在运行的时候将它输出到日志中,那么如果我想实时看到这些数据的情况又怎么办呢?

expvar

使用案例

废话不多数,直接上案例

package main

import (
    "expvar"
    "net/http"
)

var (
    s    map[string]string
    user User
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func showMap() interface{} {
    return s
}

func showUser() interface{} {
    return user
}

func main() {
    user.Name = "tom"
    user.Age = 18
    s = make(map[string]string, 10)
    s["1"] = "111"
    s["2"] = "222"
    expvar.Publish("a_map", expvar.Func(showMap))
    expvar.Publish("b_user", expvar.Func(showUser))
    http.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
        s["3"] = "333"
    })
    http.ListenAndServe(":8080", nil)
}

监控变量

直接访问 http://127.0.0.1:8080/debug/vars 你就能看到一个 json 格式的返回数据,数据如下所示:

{
  "a_map": {
    "1": "111",
    "2": "222"
  },
  "b_user": {
    "name": "tom",
    "age": 18
  },
  "cmdline": [
    "/private/var/folders/37/qpz6_ndd1w72bhrg2sgg042r0000gn/T/___go_build_go_demo_vars"
  ],
  "memstats": {
    "Alloc": 188992,
    "TotalAlloc": 188992,
    "Sys": 70453248,
    "Lookups": 0,
    "Mallocs": 818,
    "Frees": 21,
    "HeapAlloc": 188992,
    "HeapSys": 66650112,
    "HeapIdle": 65716224,
    "HeapInuse": 933888,
    "HeapReleased": 65683456,
    "HeapObjects": 797,
    "StackInuse": 458752,
    "StackSys": 458752,
    "MSpanInuse": 15776,
    "MSpanSys": 16384,
    "MCacheInuse": 6944,
    "MCacheSys": 16384,
    "BuckHashSys": 2638,
    "GCSys": 2240512,
    "OtherSys": 1068466,
    "NextGC": 4473924,
    "LastGC": 0,
    "PauseTotalNs": 0,
    ......
  }
}
cmdlinememstats

这是你可以访问一次 http://127.0.0.1:8080/test 然后再回来看看,就会发现变量的值以及改变,说明这个变量显示的是实时的

监控原理

其实原理非常简单,你自己都能写,下面就是源码中的一部分

// Handler returns the expvar HTTP Handler.
//
// This is only needed to install the handler in a non-standard location.
func Handler() http.Handler {
    return http.HandlerFunc(expvarHandler)
}

func cmdline() interface{} {
    return os.Args
}

func memstats() interface{} {
    stats := new(runtime.MemStats)
    runtime.ReadMemStats(stats)
    return *stats
}

func init() {
    http.HandleFunc("/debug/vars", expvarHandler)
    Publish("cmdline", Func(cmdline))
    Publish("memstats", Func(memstats))
}

其实就是在 init 的时候注册了对应的路由,还注册了 cmdline 和 memstats 两个值,这两个值很有用:

  • cmdline 展示了当前启动时通过命令行传递的参数是什么
  • memstats 展示了当前运行时内存的使用情况,还有 gc 的部分信息等等

其他方法

当然这个包不止有 Publish 方法,还有 Add、Set 等等,个人最常用的还是 Publish

监控内存并展示

既然 expvar 暴露了内存的使用情况,那我们当然能利用这个信息来作图了,所以就推荐一个很好用的库

安装之后,就可以用命令通过之前的接口来监控内存等使用情况咯

jplot --url http://127.0.0.1:8080/debug/vars \
    memstats.HeapSys+memstats.HeapAlloc+memstats.HeapIdle+marker,counter:memstats.NumGC \
    counter:memstats.TotalAlloc \
    memstats.HeapObjects \
    memstats.StackSys+memstats.StackInuse

我觉得这样的使用方式在本地和开发环境的时候测试更加轻量,不用部署一些监控软件,即开即测;当然线上环境肯定会有 prometheus + grafana 这样的监控神器,这里也只是抛个砖头。

总结

如果线上需要监控一些全局变量的使用情况可以考虑使用 expvar 进行监控和查看,或者用它来监控你的配置文件或者一些任务数量等等也是一个不错的选择。