寫在前面
expvar包是 Golang 官方提供的公共變量包,它可以輔助調(diào)試全局變量荧嵌。支持一些常見的類型:float64
慰枕、int64
具则、Map
、String
具帮。如果我們的程序要用到上面提的四種類型(其中博肋,Map 類型要求 Key 是字符串)》涮可以考慮使用這個包匪凡。
功能
它支持對變量的基本操作,修改掘猿、查詢這些锹雏;
整形類型,可以用來做計數(shù)器术奖;
操作都是線程安全的礁遵。這點很不錯轻绞。相信大家都自己整過全局變量,除了變量還得整的鎖佣耐,自己寫確實挺麻煩的政勃;
此外還提供了調(diào)試接口,
/debug/vars
兼砖。它能夠展示所有通過這個包創(chuàng)建的變量奸远;-
所有的變量都是
Var
類型,可以自己通過實現(xiàn)這個接口擴(kuò)展其它的類型讽挟;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 }
Handler()
方法可以得到調(diào)試接口的http.Handler
懒叛,和自己的路由對接。
這些基礎(chǔ)的功能就不多說了耽梅,大家可以直接看官方的文檔薛窥。
調(diào)試接口
看源碼的時候發(fā)現(xiàn)一個非常有意思的調(diào)試接口,/debug/vars
會把所有注冊的變量打印到接口里面眼姐。這個接口很有情懷诅迷。
func init() {
http.HandleFunc("/debug/vars", expvarHandler)
Publish("cmdline", Func(cmdline))
Publish("memstats", Func(memstats))
}
源碼
var (
mutex sync.RWMutex
vars = make(map[string]Var)
varKeys []string // sorted
)
varKeys
是全局變量所有的變量名,而且是有序的众旗;vars
根據(jù)變量名保存了對應(yīng)的數(shù)據(jù)罢杉。當(dāng)然mutex
就是這個 Map 的鎖;-
這三個變量組合起來其實是一個有序線程安全哈希表的實現(xiàn)贡歧。
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 } type Int struct { i int64 } func (v *Int) Value() int64 { return atomic.LoadInt64(&v.i) } func (v *Int) String() string { return strconv.FormatInt(atomic.LoadInt64(&v.i), 10) } func (v *Int) Add(delta int64) { atomic.AddInt64(&v.i, delta) } func (v *Int) Set(value int64) { atomic.StoreInt64(&v.i, value) }
這個包里面的所有類型都實現(xiàn)了這個接口滩租;
-
以 Int 類型舉例。實現(xiàn)非常的簡單利朵,注意
Add
和Set
方法是線程安全的律想。別的類型實現(xiàn)也一樣func Publish(name string, v Var) { mutex.Lock() defer mutex.Unlock() if _, existing := vars[name]; existing { log.Panicln("Reuse of exported var name:", name) } vars[name] = v varKeys = append(varKeys, name) sort.Strings(varKeys) } func NewInt(name string) *Int { v := new(Int) Publish(name, v) return v }
將變量注冊到一開始介紹的
vars
和varKeys
里面;注冊時候也是線程安全的哗咆,所有的變量名在注冊的最后排了個序蜘欲;
-
創(chuàng)建對象的時候會自動注冊。
func Do(f func(KeyValue)) { mutex.RLock() defer mutex.RUnlock() for _, k := range varKeys { f(KeyValue{k, vars[k]}) } } 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") } func Handler() http.Handler { return http.HandlerFunc(expvarHandler) }
Do
方法晌柬,利用一個閉包姥份,按照varKeys
的順序遍歷所有全局變量;expvarHandler
方法是http.Handler
類型年碘,將所有變量通過接口輸出澈歉,里面通過Do
方法,把所有變量遍歷了一遍屿衅。挺巧妙埃难;通過
http.HandleFunc
方法把expvarHandler
這個外部不可訪問的方法對外,這個方法用于對接自己的路由;-
輸出數(shù)據(jù)的類型涡尘,
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
忍弛,可以發(fā)現(xiàn),值輸出的字符串考抄,所以輸出的內(nèi)容是String()
的結(jié)果细疚。這里有一個技巧,雖然調(diào)用的字符串的方法川梅,但是由于輸出格式%s
外面并沒有引號疯兼,所有對于 JSON 來說,輸出的內(nèi)容是對象類型贫途。相當(dāng)于在 JSON 編碼的時候做了一次類型轉(zhuǎn)換吧彪。type Func func() interface{} func (f Func) Value() interface{} { return f() } func (f Func) String() string { v, _ := json.Marshal(f()) return string(v) } func cmdline() interface{} { return os.Args }
這是一個非常有意思的寫法,它可以把任何類型轉(zhuǎn)換成
Var
類型丢早;Func
定義的是函數(shù)姨裸,它的類型是func() interface{}
Func(cmdline)
,使用的地方需要看清楚香拉,參數(shù)是cmdline
而不是cmdline()
啦扬,所以這個寫法是類型轉(zhuǎn)換中狂。轉(zhuǎn)換完之后cmdline
方法就有了String()
方法凫碌,在String()
方法里又調(diào)用了f()
,通過 JSON 編碼輸出胃榕。這個小技巧在前面提到的http.HandleFunc
里面也有用到盛险,Golang 的程序員對這個是真愛,咱們編碼的時候也要多用用啊勋又。
不足
感覺這個包還是針對簡單變量苦掘,比如整形、字符串這種比較好用楔壤。
- 前面已經(jīng)說了鹤啡,Map 類型只支持 Key 是字符串的變量。其它類型還得自己擴(kuò)展蹲嚣,擴(kuò)展的話鎖的問題還是得自己搞递瑰。而且 JSON 編碼低版本不支持 Key 是整形類型的編碼,也是個問題隙畜;
-
Var
接口太簡單抖部,只有一個String()
方法,基本上只能輸出變量所有內(nèi)容议惰,別的東西都沒辦法控制慎颗,如果你的變量有10000個鍵值對,那么這個接口基本上屬于不能用。多說一句俯萎,這是 Golang 設(shè)計的常見問題傲宜,比如日志包,輸出的類型是io.Writer
夫啊,而這個接口只支持一個方法Write([]byte)
蛋哭,想擴(kuò)展日志包的功能很難,這也失去了抽象出來一個接口的意義涮母。 - 路由里面還默認(rèn)追加了啟動參數(shù)和
MemStats
內(nèi)存相關(guān)參數(shù)谆趾。我個人覺得后面這個不應(yīng)該加,調(diào)用runtime.ReadMemStats(stats)
會引起 Stop The World叛本,總感覺不值當(dāng)沪蓬。
總結(jié)
看到就寫了,并沒有什么沉淀来候,寫得挺亂的跷叉。這個包很簡單,但是里面還是有些可以借鑒的編碼和設(shè)計营搅。新版本的 Golang 已經(jīng)能解析整形為 Key 的哈希表了云挟,這個包啥時候能跟上支持一下?