Golang 公共变量包——expvar

栏目: Go · 发布时间: 8年前

内容简介:Golang 公共变量包——expvar

写在前面

expvar 包是 Golang 官方提供的公共变量包,它可以辅助调试全局变量。支持一些常见的类型: float64int64MapString 。如果我们的程序要用到上面提的四种类型(其中,Map 类型要求 Key 是字符串)。可以考虑使用这个包。

功能

  1. 它支持对变量的基本操作,修改、查询这些;
  2. 整形类型,可以用来做计数器;
  3. 操作都是线程安全的。这点很不错。相信大家都自己整过全局变量,除了变量还得整的锁,自己写确实挺麻烦的;
  4. 此外还提供了调试接口, /debug/vars 。它能够展示所有通过这个包创建的变量;
  5. 所有的变量都是 Var 类型,可以自己通过实现这个接口扩展其它的类型;

    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 }

  6. Handler() 方法可以得到调试接口的 http.Handler ,和自己的路由对接。

这些基础的功能就不多说了,大家可以直接看官方的 文档

调试接口

看源码的时候发现一个非常有意思的调试接口, /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
)
  1. varKeys 是全局变量所有的变量名,而且是有序的;
  2. vars 根据变量名保存了对应的数据。当然 mutex 就是这个 Map 的锁;
  3. 这三个变量组合起来其实是一个有序线程安全哈希表的实现。

    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)
     }
  4. 这个包里面的所有类型都实现了这个接口;
  5. 以 Int 类型举例。实现非常的简单,注意 AddSet 方法是线程安全的。别的类型实现也一样

    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
     }
  6. 将变量注册到一开始介绍的 varsvarKeys 里面;
  7. 注册时候也是线程安全的,所有的变量名在注册的最后排了个序;
  8. 创建对象的时候会自动注册。

    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)
     }
  9. Do 方法,利用一个闭包,按照 varKeys 的顺序遍历所有全局变量;
  10. expvarHandler 方法是 http.Handler 类型,将所有变量通过接口输出,里面通过 Do 方法,把所有变量遍历了一遍。挺巧妙;
  11. 通过 http.HandleFunc 方法把 expvarHandler 这个外部不可访问的方法对外,这个方法用于对接自己的路由;
  12. 输出数据的类型, fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) ,可以发现,值输出的字符串,所以输出的内容是 String() 的结果。这里有一个技巧,虽然调用的字符串的方法,但是由于输出格式 %s 外面并没有引号,所有对于 JSON 来说,输出的内容是对象类型。相当于在 JSON 编码的时候做了一次类型转换。

    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
     }
  13. 这是一个非常有意思的写法,它可以把任何类型转换成 Var 类型;
  14. Func 定义的是函数,它的类型是 func() interface{}
  15. Func(cmdline) ,使用的地方需要看清楚,参数是 cmdline 而不是 cmdline() ,所以这个写法是类型转换。转换完之后 cmdline 方法就有了 String() 方法,在 String() 方法里又调用了 f() ,通过 JSON 编码输出。这个小技巧在前面提到的 http.HandleFunc 里面也有用到,Golang 的 程序员 对这个是真爱,咱们编码的时候也要多用用啊。

不足

感觉这个包还是针对简单变量,比如整形、字符串这种比较好用。

  1. 前面已经说了,Map 类型只支持 Key 是字符串的变量。其它类型还得自己扩展,扩展的话锁的问题还是得自己搞。而且 JSON 编码低版本不支持 Key 是整形类型的编码,也是个问题;
  2. Var 接口太简单,只有一个 String() 方法,基本上只能输出变量所有内容,别的东西都没办法控制,如果你的变量有10000个键值对,那么这个接口基本上属于不能用。多说一句,这是 Golang 设计的常见问题,比如日志包,输出的类型是 io.Writer ,而这个接口只支持一个方法 Write([]byte) ,想扩展日志包的功能很难,这也失去了抽象出来一个接口的意义。
  3. 路由里面还默认追加了启动参数和 MemStats 内存相关参数。我个人觉得后面这个不应该加,调用 runtime.ReadMemStats(stats) 会引起 Stop The World,总感觉不值当。

总结

看到就写了,并没有什么沉淀,写得挺乱的。这个包很简单,但是里面还是有些可以借鉴的编码和设计。新版本的 Golang 已经能解析整形为 Key 的哈希表了,这个包啥时候能跟上支持一下?


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

Clean Architecture

Clean Architecture

Robert C. Martin / Prentice Hall / 2017-9-20 / USD 34.99

Practical Software Architecture Solutions from the Legendary Robert C. Martin (“Uncle Bob”) By applying universal rules of software architecture, you can dramatically improve developer producti......一起来看看 《Clean Architecture》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具