Golang expvar库源码阅读

栏目: IT技术 · 发布时间: 4年前

内容简介:最近看到同事用一个库,叫做意思是这个库用来暴露一些公共的变量,他会自动注册到请求一下就可以看到输出:

最近看到同事用一个库,叫做 expvar ,这个库的作用官网描述如下:

Package expvar provides a standardized interface to public variables, such as operation counters in servers.
It exposes these variables via HTTP at /debug/vars in JSON format.

意思是这个库用来暴露一些公共的变量,他会自动注册到 /debug/vars 这个路径下。我们来看看基本用法:

package main

import (
        "expvar"
        "net/http"
)

func main() {
        http.ListenAndServe(":8080", expvar.Handler())
}

// 或者如下
package main

import (
        _ "expvar"
        "net/http"
)

func main() {
        http.ListenAndServe(":8080", nil)
}

请求一下就可以看到输出:

$ http :8080  # 如果是第二种代码,这里的命令应该改成 `http :8080/debug/vars`
HTTP/1.1 200 OK                               
Content-Type: application/json; charset=utf-8
Date: Sun, 12 Apr 2020 01:22:14 GMT
Transfer-Encoding: chunked   
                              
{                            
    "cmdline": [              
        "/tmp/go-build998114494/b001/exe/main"
    ],                       
    "memstats": {                
        "Alloc": 246696,     
        "BuckHashSys": 3565, 
        "BySize": [           
            {                 
                "Frees": 0,   
                "Mallocs": 0, 
                "Size": 0    
            },                
            {                  
                "Frees": 0,   
                "Mallocs": 15,
                "Size": 8    
            },              
...

这是因为expvar实现的时候,它会自动带上 cmdlinememstats 这两节:

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))
}

我们可以加上自己想要展示的公共变量:

package main

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

func main() {
        lastAccess := expvar.NewString("lastAccess")
        http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
                w.Write([]byte("hello"))
                lastAccess.Set(time.Now().String())
        })

        http.ListenAndServe(":8080", nil)
}

测试效果:

$ http :8080/debug/vars | grep lastAccess
"lastAccess": "2020-04-12 09:52:14.127334409 +0800 CST m=+15.783055827",
$ http :8080/
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain; charset=utf-8
Date: Sun, 12 Apr 2020 01:52:27 GMT

hello

$ http :8080/debug/vars | grep lastAccess
"lastAccess": "2020-04-12 09:52:27.533390067 +0800 CST m=+29.189111480",

可以看到,每次我们都能看到最近访问时间。

源码阅读

我们要知其然知其所以然,来看看expvar是怎么实现的吧!为了方便,我把阅读思路也写在了注释里面。

// 从 NewString 入手
lastAccess := expvar.NewString("lastAccess")

func NewString(name string) *String {
	v := new(String)
	Publish(name, v)
	return v
}

// 看看String的定义
type String struct {
	s atomic.Value // string
}

// 看看Publish的定义
// Publish declares a named exported variable. This should be called from a
// package's init function when it creates its Vars. If the name is already
// registered then this will log.Panic.
func Publish(name string, v Var) {
	if _, dup := vars.LoadOrStore(name, v); dup {
		log.Panicln("Reuse of exported var name:", name)
	}
	varKeysMu.Lock()
	defer varKeysMu.Unlock()
	varKeys = append(varKeys, name)
	sort.Strings(varKeys)
}

// 可以看到 Var 是一个接口
// Var is an abstract type for all exported variables.
type Var interface {
	// String returns a valid JSON value for the variable.
	// Types with String methods that do not return valid JSON
	// (such as time.Time) must not be used as a Var.
	String() string
}

// vars 和 varKeys 的定义是这样的
// All published variables.
var (
	vars      sync.Map // map[string]Var
	varKeysMu sync.RWMutex
	varKeys   []string // sorted
)

// 所以可以看到,逻辑就是每次把数值存储到 `vars`,`vars` 是一个map[string]Var类型的map。因为map迭代时是无序的,
// 所以有 `varKeys` 用来排序,这样输出的时候,就可以每次都有序输出

// 来看看如何暴露这些变量
func expvarHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	fmt.Fprintf(w, "{\n")
	first := true
	Do(func(kv KeyValue) {
		if !first {
			fmt.Fprintf(w, ",\n")
		}
		first = false
		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
	})
	fmt.Fprintf(w, "\n}\n")
}

// 可以看到,很粗暴,先输出 `{\n`,然后输出key value的字符串,最后输出 `\n}\n`,相当于代码拼接JSON
// Do这个函数用来迭代
// Do calls f for each exported variable.
// The global variable map is locked during the iteration,
// but existing entries may be concurrently updated.
func Do(f func(KeyValue)) {
	varKeysMu.RLock()
	defer varKeysMu.RUnlock()
	for _, k := range varKeys {
		val, _ := vars.Load(k)
		f(KeyValue{k, val.(Var)})
	}
}

// 就如我们前面所说,按 varKeys 的顺序来迭代,然后依次执行传入的函数

至此我们了解了expvar的作用和实现,用起来吧!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

500 Lines or Less

500 Lines or Less

Amy Brown、Michael DiBernardo / 2016-6-28 / USD 35.00

This book provides you with the chance to study how 26 experienced programmers think when they are building something new. The programs you will read about in this book were all written from scratch t......一起来看看 《500 Lines or Less》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具