Go語言學習之expvar包(公共變量)(the way to go)

生命不止,繼續 go go go!!!

基礎還是要打好,很久沒有分享golang的標準包了,今天就來一個expvar包。

Package 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.
Operations to set or modify these public variables are atomic.

In addition to adding the HTTP handler, this package registers the following variables:

cmdline   os.Args
memstats  runtime.Memstats

The package is sometimes only imported for the side effect of registering its HTTP handler and the above variables. To use it this way, link this package into your program:

import _ "expvar"

expvar包提供了公共變量的標準接口,如服務的操作計數器。本包通過HTTP在/debug/vars位置以JSON格式導出了這些變量。+
對這些公共變量的讀寫操作都是原子級的。
爲了增加HTTP處理器,本包註冊瞭如下變量:+

cmdline   os.Args
memstats  runtime.Memstats

有時候本包被導入只是爲了獲得本包註冊HTTP處理器和上述變量的副作用。此時可以如下方式導入本包:

import _ "expvar"

支持類型
支持一些常見的類型:float64、int64、Map、String。
如果我們的程序要用到上面提的四種類型(其中,Map 類型要求 Key 是字符串)。可以考慮使用這個包。

功能
支持對變量的基本操作,修改、查詢

整形類型,可以用來做計數器

線程安全的

此外還提供了調試接口,/debug/vars。它能夠展示所有通過這個包創建的變量

所有的變量都是Var類型,可以自己通過實現這個接口擴展其它的類型

Handler()方法可以得到調試接口的http.Handler,和自己的路由對接

func Do

func Do(f func(KeyValue))

Do calls f for each exported variable. The global variable map is locked during the iteration, but existing entries may be concurrently updated.
Do對映射的每一條記錄都調用f。迭代執行時會鎖定該映射,但已存在的記錄可以同時更新。

func Handler

func Handler() http.Handler

Handler returns the expvar HTTP Handler.

This is only needed to install the handler in a non-standard location.

func Publish

func Publish(name string, v Var)

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.
Publish聲明一個導出變量。必須在init函數裏調用。如果name已經被註冊,會調用log.Panic。

源碼
由於expvar的源碼篇幅不是很大,就貼上了:

package expvar

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "math"
    "net/http"
    "os"
    "runtime"
    "sort"
    "strconv"
    "sync"
    "sync/atomic"
)

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

// Int is a 64-bit integer variable that satisfies the Var interface.
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)
}

// Float is a 64-bit float variable that satisfies the Var interface.
type Float struct {
    f uint64
}

func (v *Float) Value() float64 {
    return math.Float64frombits(atomic.LoadUint64(&v.f))
}

func (v *Float) String() string {
    return strconv.FormatFloat(
        math.Float64frombits(atomic.LoadUint64(&v.f)), 'g', -1, 64)
}

// Add adds delta to v.
func (v *Float) Add(delta float64) {
    for {
        cur := atomic.LoadUint64(&v.f)
        curVal := math.Float64frombits(cur)
        nxtVal := curVal + delta
        nxt := math.Float64bits(nxtVal)
        if atomic.CompareAndSwapUint64(&v.f, cur, nxt) {
            return
        }
    }
}

// Set sets v to value.
func (v *Float) Set(value float64) {
    atomic.StoreUint64(&v.f, math.Float64bits(value))
}

// Map is a string-to-Var map variable that satisfies the Var interface.
type Map struct {
    m      sync.Map // map[string]Var
    keysMu sync.RWMutex
    keys   []string // sorted
}

// KeyValue represents a single entry in a Map.
type KeyValue struct {
    Key   string
    Value Var
}

func (v *Map) String() string {
    var b bytes.Buffer
    fmt.Fprintf(&b, "{")
    first := true
    v.Do(func(kv KeyValue) {
        if !first {
            fmt.Fprintf(&b, ", ")
        }
        fmt.Fprintf(&b, "%q: %v", kv.Key, kv.Value)
        first = false
    })
    fmt.Fprintf(&b, "}")
    return b.String()
}

// Init removes all keys from the map.
func (v *Map) Init() *Map {
    v.keysMu.Lock()
    defer v.keysMu.Unlock()
    v.keys = v.keys[:0]
    v.m.Range(func(k, _ interface{}) bool {
        v.m.Delete(k)
        return true
    })
    return v
}

// updateKeys updates the sorted list of keys in v.keys.
func (v *Map) addKey(key string) {
    v.keysMu.Lock()
    defer v.keysMu.Unlock()
    v.keys = append(v.keys, key)
    sort.Strings(v.keys)
}

func (v *Map) Get(key string) Var {
    i, _ := v.m.Load(key)
    av, _ := i.(Var)
    return av
}

func (v *Map) Set(key string, av Var) {
    // Before we store the value, check to see whether the key is new. Try a Load
    // before LoadOrStore: LoadOrStore causes the key interface to escape even on
    // the Load path.
    if _, ok := v.m.Load(key); !ok {
        if _, dup := v.m.LoadOrStore(key, av); !dup {
            v.addKey(key)
            return
        }
    }

    v.m.Store(key, av)
}

// Add adds delta to the *Int value stored under the given map key.
func (v *Map) Add(key string, delta int64) {
    i, ok := v.m.Load(key)
    if !ok {
        var dup bool
        i, dup = v.m.LoadOrStore(key, new(Int))
        if !dup {
            v.addKey(key)
        }
    }

    // Add to Int; ignore otherwise.
    if iv, ok := i.(*Int); ok {
        iv.Add(delta)
    }
}

// AddFloat adds delta to the *Float value stored under the given map key.
func (v *Map) AddFloat(key string, delta float64) {
    i, ok := v.m.Load(key)
    if !ok {
        var dup bool
        i, dup = v.m.LoadOrStore(key, new(Float))
        if !dup {
            v.addKey(key)
        }
    }

    // Add to Float; ignore otherwise.
    if iv, ok := i.(*Float); ok {
        iv.Add(delta)
    }
}

// Do calls f for each entry in the map.
// The map is locked during the iteration,
// but existing entries may be concurrently updated.
func (v *Map) Do(f func(KeyValue)) {
    v.keysMu.RLock()
    defer v.keysMu.RUnlock()
    for _, k := range v.keys {
        i, _ := v.m.Load(k)
        f(KeyValue{k, i.(Var)})
    }
}

// String is a string variable, and satisfies the Var interface.
type String struct {
    s atomic.Value // string
}

func (v *String) Value() string {
    p, _ := v.s.Load().(string)
    return p
}

// String implements the Val interface. To get the unquoted string
// use Value.
func (v *String) String() string {
    s := v.Value()
    b, _ := json.Marshal(s)
    return string(b)
}

func (v *String) Set(value string) {
    v.s.Store(value)
}

// Func implements Var by calling the function
// and formatting the returned value using JSON.
type Func func() interface{}

func (f Func) Value() interface{} {
    return f()
}

func (f Func) String() string {
    v, _ := json.Marshal(f())
    return string(v)
}

// All published variables.
var (
    vars      sync.Map // map[string]Var
    varKeysMu sync.RWMutex
    varKeys   []string // sorted
)

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

// Get retrieves a named exported variable. It returns nil if the name has
// not been registered.
func Get(name string) Var {
    i, _ := vars.Load(name)
    v, _ := i.(Var)
    return v
}

// Convenience functions for creating new exported variables.

func NewInt(name string) *Int {
    v := new(Int)
    Publish(name, v)
    return v
}

func NewFloat(name string) *Float {
    v := new(Float)
    Publish(name, v)
    return v
}

func NewMap(name string) *Map {
    v := new(Map).Init()
    Publish(name, v)
    return v
}

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

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

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

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

簡單應用

例1
main.go

package main

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

var (
    test = expvar.NewMap("Test")
)

func init() {
    test.Add("go", 10)
    test.Add("go1", 10)
}

func main() {
    sock, err := net.Listen("tcp", "localhost:8080")
    if err != nil {
        panic("error")
    }
    go func() {
        http.Serve(sock, nil)
    }()
    select {}
}

瀏覽器訪問:http://localhost:8080/debug/vars
這裏寫圖片描述

例2
main.go

package main

import (
    "expvar"
    "io"
    "net/http"
    "strconv"
    "time"

    "github.com/paulbellamy/ratecounter"
)

var (
    counter       *ratecounter.RateCounter
    hitsperminute = expvar.NewInt("hits_per_minute")
)

func increment(w http.ResponseWriter, r *http.Request) {
    counter.Incr(1)
    hitsperminute.Set(counter.Rate())
    io.WriteString(w, strconv.FormatInt(counter.Rate(), 10))
}

func main() {
    counter = ratecounter.NewRateCounter(1 * time.Minute)
    http.HandleFunc("/increment", increment)
    http.ListenAndServe(":8000", nil)
}

瀏覽器輸入:http://localhost:8000/increment
curl命令行訪問: curl http://localhost:8000/increment

瀏覽器訪問:http://localhost:8000/debug/vars
這裏寫圖片描述

完美例子

https://golang.org/src/net/http/triv.go?m=text

延伸

https://github.com/divan/expvarmon
之後會有詳細介紹

發佈了565 篇原創文章 · 獲贊 1543 · 訪問量 701萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章