1. 程式人生 > 實用技巧 >go語言程式設計之旅筆記6

go語言程式設計之旅筆記6

第六章: Go中的大殺器

  1. 簡介

    介紹了PProf,trace,godebug,gops,metrrics,prometheus等等庫來進行效能監控等等的功能

  2. PProf

    1. 使用net/http/pprof可以能方便的採集web服務在執行時的資料,直接import十分簡單
    import (
        _ "net/http/pprof"
    )
    
    // 看看pprof的init發現注入了很多handler
    func init() {
    	http.HandleFunc("/debug/pprof/", Index)
    	http.HandleFunc("/debug/pprof/cmdline", Cmdline)
    	http.HandleFunc("/debug/pprof/profile", Profile)
    	http.HandleFunc("/debug/pprof/symbol", Symbol)
    	http.HandleFunc("/debug/pprof/trace", Trace)
    }
    
    

    可以通過瀏覽器或是互動式終端進行訪問,我選擇瀏覽器。-> IP地址/debug/pprof

    瀏覽器有時效性,真要查問題還是用終端

    go tool pprof ip/debug/pprof/profile?seconds=60
    

    不同的路由對應的項,比如cpu/heap/goroutine等等,這就不貼了。

    採集生成的profile檔案也是可以用web方式查閱的,go tool pprof -http=:6000 profile,如果報graphviz的錯說明要裝元件。

    1. 通過Lookup進行採集,這種方式需要寫code,支援6種類型,goroutine,threadcreate,heap,block,mutex
    這種方式需要寫code,支援6種類型,goroutine,threadcreate,heap,block,mutex
    package main
    
    import (
    	"io"
    	"net/http"
    	_ "net/http/pprof"
    	"os"
    	"runtime"
    	"runtime/pprof"
    )
    
    //go tool pprof http://localhost:6060/debug/pprof/profile?seconds=60
    
    func main() {
    	http.HandleFunc("/lookup/heap", func(w http.ResponseWriter, r *http.Request) {
    		_ = pprofLookup(LookupHeap, os.Stdout)
    	})
    
    	http.HandleFunc("/lookup/threadcreate", func(w http.ResponseWriter, r *http.Request) {
    		_ = pprofLookup(LookupThreadcreate, os.Stdout)
    	})
    
    	http.HandleFunc("/lookup/block", func(w http.ResponseWriter, r *http.Request) {
    		_ = pprofLookup(LookupBlock, os.Stdout)
    	})
    
    	http.HandleFunc("/lookup/goroutine", func(w http.ResponseWriter, r *http.Request) {
    		_ = pprofLookup(LookupGoroutine, os.Stdout)
    	})
    
    	_ = http.ListenAndServe("0.0.0.0:6060", nil)
    }
    
    type LookupType int8
    
    const (
    	LookupGoroutine LookupType = iota
    	LookupThreadcreate
    	LookupHeap
    	LookupAllocs
    	LookupBlock
    	LookupMutex
    )
    
    func pprofLookup(lookupType LookupType, w io.Writer) error {
    	var err error
    	switch lookupType {
    	case LookupGoroutine:
    		p := pprof.Lookup("goroutine")
    		err = p.WriteTo(w, 2)
    	case LookupThreadcreate:
    		p := pprof.Lookup("threadcreate")
    		err = p.WriteTo(w, 2)
    	case LookupHeap:
    		p := pprof.Lookup("heap")
    		err = p.WriteTo(w, 2)
    	case LookupAllocs:
    		p := pprof.Lookup("allocs")
    		err = p.WriteTo(w, 2)
    	case LookupBlock:
    		p := pprof.Lookup("block")
    		err = p.WriteTo(w, 2)
    	case LookupMutex:
    		p := pprof.Lookup("mutex")
    		err = p.WriteTo(w, 2)
    	}
    	return err
    }
    
    func init() {
    	runtime.SetMutexProfileFraction(1)
    	runtime.SetBlockProfileRate(1)
    }
    
    
  3. trace

    詳細的使用方式/指標之類還是看書吧,字太多。
    package main
    
    // --go run .\cmd\trace\main.go 2> trace.out
    
    // go build .\cmd\trace\main.go
    //  .\main.exe
    // go tool trace trace.dat
    
    import (
    	"context"
    	"fmt"
    	"os"
    	"runtime"
    	"runtime/trace"
    	"sync"
    )
    
    func main() {
    	// 為了看協程搶佔,這裡設定了一個cpu 跑
    	runtime.GOMAXPROCS(1)
    
    	f, _ := os.Create("trace.dat")
    	defer f.Close()
    
    	_ = trace.Start(f)
    	defer trace.Stop()
    
    	ctx, task := trace.NewTask(context.Background(), "sumTask")
    	defer task.End()
    
    	var wg sync.WaitGroup
    	wg.Add(10)
    	for i := 0; i < 10; i++ {
    		// 啟動10個協程,只是做一個累加運算
    		go func(region string) {
    			defer wg.Done()
    
    			// 標記region
    			trace.WithRegion(ctx, region, func() {
    				var sum, k int64
    				for ; k < 1000000000; k++ {
    					sum += k
    				}
    				fmt.Println(region, sum)
    			})
    		}(fmt.Sprintf("region_%02d", i))
    	}
    	wg.Wait()
    }
    
    
  4. godebug

    這個同樣太長不寫了。環境變數可以通過vscode寫入launch.json檔案中,比如

      "env": {
                "GODEBUG":"scheddetail=1,schedtrace=1000"
            } 
    
  5. 程序診斷工具 gops

    go get -u github.com/google/gops 
    
    package main
    
    import (
    	"log"
    	"net/http"
    
    	"github.com/google/gops/agent"
    )
    
    func main() {
    	if err := agent.Listen(agent.Options{}); err != nil {
    		log.Fatal("agent listen err : %v", err)
    	}
    	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    		_, _ = w.Write([]byte("golang projecct"))
    	})
    	_ = http.ListenAndServe(":6060", http.DefaultServeMux)
    }
    
    

    gops help檢視命令,也是有很多,不細寫

  6. metrics 使用expvar標準庫

    code中自定義了型別,並且封裝成了gin中介軟體,可以和gin聯動了
    package main
    
    import (
    	"expvar"
    	_ "expvar"
    	"fmt"
    	"net/http"
    	"runtime"
    	"time"
    
    	"github.com/gin-gonic/gin"
    )
    
    //http://localhost:6060/debug/vars
    func main() {
    	router := NewRouter()
    	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    		appleCounter.Add(1)
    		_, _ = w.Write([]byte("go project"))
    	})
    
    	_ = http.ListenAndServe(":6060", router)
    }
    
    var (
    	appleCounter      *expvar.Int
    	GOMAXPROCSMetrics *expvar.Int
    	upTimeMetrice     *upTimeVar
    )
    
    type upTimeVar struct {
    	value time.Time
    }
    
    func (v *upTimeVar) Set(date time.Time) {
    	v.value = date
    }
    
    func (v *upTimeVar) Add(duration time.Duration) {
    	v.value = v.value.Add(duration)
    }
    
    func (v *upTimeVar) String() string {
    	return v.value.Format(time.UnixDate)
    }
    
    func init() {
    	upTimeMetrice = &upTimeVar{value: time.Now().Local()}
    	expvar.Publish("uptime", upTimeMetrice)
    	appleCounter = expvar.NewInt("apple")
    	GOMAXPROCSMetrics = expvar.NewInt("GOMAXPROCS")
    	GOMAXPROCSMetrics.Set(int64(runtime.NumCPU()))
    }
    
    func Expvar(c *gin.Context) {
    	c.Writer.Header().Set("content-type", "application/json; charset=utf-8")
    	first := true
    	report := func(key string, value interface{}) {
    		if !first {
    			fmt.Fprintf(c.Writer, ",\n")
    		}
    		first = false
    		if str, ok := value.(string); ok {
    			fmt.Fprintf(c.Writer, "%q: %q", key, str)
    		} else {
    			fmt.Fprintf(c.Writer, "%q: %v", key, value)
    		}
    	}
    
    	fmt.Fprintf(c.Writer, "{\n")
    	expvar.Do(func(kv expvar.KeyValue) {
    		report(kv.Key, kv.Value)
    	})
    	fmt.Fprintf(c.Writer, "\n}\n")
    }
    
    func NewRouter() *gin.Engine {
    	r := gin.New()
    	r.Use(gin.Logger())
    	r.Use(gin.Recovery())
    	r.GET("/debug/vars", Expvar)
    	return r
    }
    
  1. Pronmetheus

    Pronmetheus還是很出名的。四大指標型別Counter累計指標,Histogram一定時間範圍內取樣,Gauge可任意變化的指標,Summary也是一定時間內取樣,他有仨指標,分位數分佈/樣本值大小總和/樣本總數。

    go get -u github.com/prometheus/client_golang
    
    package main
    
    import (
    	"net/http"
    
    	"github.com/prometheus/client_golang/prometheus/promhttp"
    )
    
    func main() {
    	http.Handle("/metrics", promhttp.Handler())
    	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    		_, _ = w.Write([]byte("go project"))
    	})
    	_ = http.ListenAndServe(":6060", http.DefaultServeMux)
    }
    
    

    啟動後訪問 :6060/metrics

  2. 其他 包括附錄

    1. 逃逸分析,有很多情況會造成逃逸,這個還是要經驗的,初學者的我還是用命令最直接。
    // 用-gcflags檢視逃逸分析過程
    go build -gcflags '-m -l' main.go
    
    // 反編譯命令檢視
    go tool compile -S main.go
    
    1. Go modules
      去年剛開始學時用過gopath,那玩意兒能坑死,還是modules適合老子。
    // go get後的模組會快取在gopath/pkg/mod 和gopath/pkg/sumdb中,如果需要清理可以執行
    go clean -modcache
    
    1. 為什麼defer才能recover

    panic結構是一個連結串列,defer結構中包含了一個對panic結構的引用,在gopanic(interface{})方法中,會觸發defer,如果沒有defer則會直接跳出,就不會進行接來下的recover了。

    還有一些defer了也無法recover的方法,比如fatalthrow,fatalpanic等,比如併發寫入map時就會引起fatalthrow。

    10種panic方法:陣列切片越界,空指標呼叫,過早關閉HTTP響應體(resp.body.calose()),除零,向關閉的chan傳送訊息,重複關閉chan,關閉未初始化的的chan,使用未初始化的map,跨goroutine處理panic,sync計數負數。

    1. 讓golang更適應docker
    // 這個庫可以根據cgroup的掛載資訊來修改GOMAXPROCS核數
    import _ "go.uber.org/automaxprocs"
    

完!