您可能想知道——特別是如果您剛開始使用 Go,該如何給您的微服務應用添加監控。正如那些有追蹤記錄的人告訴您——監控是很困難的。那麼我要告訴您的是至少基本的監控不是那樣的。您不需要為您的簡單應用啟動一個 [Prometheus](https://prometheus.io/) 叢集去獲得報告,事實上,您甚至不需要額外的服務去添加一個您的應用統計的簡單輸出。但是我們的應用程式的哪些特性是我們感興趣的呢?這個 Go 的 [runtime](https://golang.org/pkg/runtime/) 包包含了一些和 Go 的運行系統互動的函數——像這個調度器和記憶體管理器等。這意味著我們能夠訪問一些內部的應用程式:## Goroutinesgoroutine 是 Go 的調度管理器為我們準備的非常輕量級的線程。在任何代碼中可能會出現的一個典型問題被稱為“ goroutines 泄露”。這個問題的原因有很多種,如忘記設定預設的 http 請求逾時,SQL 逾時,缺乏對上下文包取消的支援,向已關閉的通道發資料等。當這個問題發生時,一個 goroutine 可能無限期的存活,並且永遠不釋放它所使用的資源。我們可能會對 [runtime.NumGoroutine() int](https://golang.org/pkg/runtime/#NumGoroutine) 這個很基本當函數感興趣,它會返回當前存在的 goroutines 數量。我們只要列印這個數字並在一段時間內檢查它,就可以合理的確認我們可能 goroutines 泄漏,然後調查這些問題。## 記憶體佔用在 Go 的世界裡記憶體佔用問題是很普遍的。當大多數人傾向於使用高效的指標時(比在 Node.js 中的任何東西都高效),一個經常遇到的與效能相關的問題是關於記憶體配置。示範一個簡單的,但低效的反轉字串的方式:```gopackage mainimport ("strings""testing")func BenchmarkStringReverseBad(b *testing.B) {b.ReportAllocs()input := "A pessimist sees the difficulty in every opportunity; an optimist sees the opportunity in every difficulty."for i := 0; i < b.N; i++ {words := strings.Split(input, " ")wordsReverse := make([]string, 0)for {word := words[len(words)-1:][0]wordsReverse = append(wordsReverse, word)words = words[:len(words)-1]if len(words) == 0 {break}}output := strings.Join(wordsReverse, " ")if output != "difficulty. every in opportunity the sees optimist an opportunity; every in difficulty the sees pessimist A" {b.Error("Unexpected result: " + output)}}}func BenchmarkStringReverseBetter(b *testing.B) {b.ReportAllocs()input := "A pessimist sees the difficulty in every opportunity; an optimist sees the opportunity in every difficulty."for i := 0; i < b.N; i++ {words := strings.Split(input, " ")for i := 0; i < len(words)/2; i++ {words[len(words)-1-i], words[i] = words[i], words[len(words)-1-i]}output := strings.Join(words, " ")if output != "difficulty. every in opportunity the sees optimist an opportunity; every in difficulty the sees pessimist A" {b.Error("Unexpected result: " + output)}}}``````gopackage mainimport ("strings""testing")func BenchmarkStringReverseBad(b *testing.B) {b.ReportAllocs()input := "A pessimist sees the difficulty in every opportunity; an optimist sees the opportunity in every difficulty."for i := 0; i < b.N; i++ {words := strings.Split(input, " ")wordsReverse := make([]string, 0)for {word := words[len(words)-1:][0]wordsReverse = append(wordsReverse, word)words = words[:len(words)-1]if len(words) == 0 {break}}output := strings.Join(wordsReverse, " ")if output != "difficulty. every in opportunity the sees optimist an opportunity; every in difficulty the sees pessimist A" {b.Error("Unexpected result: " + output)}}}func BenchmarkStringReverseBetter(b *testing.B) {b.ReportAllocs()input := "A pessimist sees the difficulty in every opportunity; an optimist sees the opportunity in every difficulty."for i := 0; i < b.N; i++ {words := strings.Split(input, " ")for i := 0; i < len(words)/2; i++ {words[len(words)-1-i], words[i] = words[i], words[len(words)-1-i]}output := strings.Join(words, " ")if output != "difficulty. every in opportunity the sees optimist an opportunity; every in difficulty the sees pessimist A" {b.Error("Unexpected result: " + output)}}}```這個糟糕的函數做了不必要的分配,即:1. 我們建立了一個空 slice 儲存結果字串,2. 我們填充 這個 slice(append 分配記憶體是必要的,但不是最優的)由於調用 b.reportAllocs() 這個基準測試和相關的輸出繪製了一幅精準的圖片:```BenchmarkStringReverseBad-4 1413 ns/op 976 B/op 8 allocs/opBenchmarkStringReverseBetter-4 775 ns/op 480 B/op 3 allocs/op```由於在 Go 中實現的虛擬記憶體,記憶體配置的另個方面是垃圾收集暫停或簡稱 GC 。關於 GC 暫停一個常用語是“停止世界”,注意在 GC 暫停期間您的應用程式將完全停止回應。google 團隊不斷提升 [GC 的效能](https://groups.google.com/forum/?fromgroups#!topic/golang-dev/Ab1sFeoZg_8),但將來那些經驗不足的開發人員仍然會面對記憶體管理不良的問題。這個 runtime 包暴露了 runtime.ReadMemStats(m *MemStats) 函數用於填充一個 MemStats 對象。這個對象有很多欄位可以作為記憶體配置策略和效能相關問題的良好指標。+ Alloc -當前在堆中分配位元組數,+ TotalAlloc -在堆中累計分配最大位元組數(不會減少),+ Sys -從系統獲得的總記憶體,+ Mallocs 和 Frees - 分配,釋放和存活對象數(mallocs - frees),+ PauseTotalNs -從應用開始總GC暫停,+ NumGC - GC 迴圈完成數## 方法因此,我們開始的前提是,我們不希望使用外部服務來提供簡單的應用程式監控。我的目標是每隔一段時間將收集到的度量指標列印到控制台上。我們應該啟動一個 goroutine,每隔X秒就可以得到這個資料,然後把它列印到控制台。```gopackage mainimport ("encoding/json""fmt""runtime""time")type Monitor struct {Alloc,TotalAlloc,Sys,Mallocs,Frees,LiveObjects,PauseTotalNs uint64NumGC uint32NumGoroutine int}func NewMonitor(duration int) {var m Monitorvar rtm runtime.MemStatsvar interval = time.Duration(duration) * time.Secondfor {<-time.After(interval)// Read full mem statsruntime.ReadMemStats(&rtm)// Number of goroutinesm.NumGoroutine = runtime.NumGoroutine()// Misc memory statsm.Alloc = rtm.Allocm.TotalAlloc = rtm.TotalAllocm.Sys = rtm.Sysm.Mallocs = rtm.Mallocsm.Frees = rtm.Frees// Live objects = Mallocs - Freesm.LiveObjects = m.Mallocs - m.Frees// GC Statsm.PauseTotalNs = rtm.PauseTotalNsm.NumGC = rtm.NumGC// Just encode to json and printb, _ := json.Marshal(m)fmt.Println(string(b))}}```要使用它,你可以用 go NewMonitor(300) 來調用它,它每5分鐘列印一次你的應用程式度量。然後,您可以從控制台或曆史日誌中檢查這些,以查看應用程式的行為。將其添加到應用程式中的任何效能影響都很小。```{"Alloc":1143448,"TotalAlloc":1143448,"Sys":5605624,"Mallocs":8718,"Frees":301,"LiveObjects":8417,"PauseTotalNs":0,"NumGC":0,"NumGoroutine":6}{"Alloc":1144504,"TotalAlloc":1144504,"Sys":5605624,"Mallocs":8727,"Frees":301,"LiveObjects":8426,"PauseTotalNs":0,"NumGC":0,"NumGoroutine":5}...```我認為控制台中的這些輸出是一個有用的洞察力,它會讓你知道在不久的將來可能會碰到一些問題。## 使用 expvarGo 實際上有兩個內建外掛程式,協助我們監控生產中的應用程式。其中一個內建的是包 expvar。該包為公開變數提供了標準化介面,例如伺服器中的操作計數器。預設情況下,這些變數將在 `/debug/vars` 上可用。讓我們把度量放在 expvar 中儲存。幾分鐘後,我註冊了 expvar 的 HTTP 處理常式,我意識到完整的 MemStats 結構已經在上面了。那太好了!除了添加HTTP處理常式外,此包還記錄以下變數:+ cmdline os.Args+ memstats runtime.Memstats該包有時僅用於註冊其HTTP處理常式和上述變數的副作用。要這樣使用,把這個包連結到你的程式中:`import _ "expvar"`由於度量現在已經匯出,您只需要在應用程式上指向監視系統,並在那裡匯入 memstats 輸出。我知道,我們仍然沒有 goroutine 計數,但這很容易添加。匯入 expvar 包並添加以下幾行:```go// The next line goes at the start of NewMonitor()var goroutines = expvar.NewInt("num_goroutine")// The next line goes after the runtime.NumGoroutine() callgoroutines.Set(int64(m.NumGoroutine))```這個 “num_goroutine” 欄位現在在 `/debug/vars output` 可用, 僅此於完整的記憶體統計。## 超越基礎監測Go 標準庫中的另外一個強大的補充是 [net/http/pprof](https://golang.org/pkg/net/http/pprof/) 包。這個包有很多函數,但主要目的是為 `go pprof` 工具提供運行時的分析資料,該工具已捆綁在 Go 工具鏈中。使用它,您可以進一步檢查您的應用程式在生產中的操作。如果您想瞭解更多關於 pprof 和代碼最佳化的內容,您可以查看我以前的文章:+ [Go 程式基準測試](https://scene-si.org/2017/06/06/benchmarking-go-programs/),+ [Go 程式基準測試,第二部](https://scene-si.org/2017/07/07/benchmarking-go-programs-part-2/)並且,如果您想要 Go 程式持續分析,可以用 Google 的一個服務,StackDriver 分析器。但是,不管什麼原因如果您想監視您自己的基礎設施,[Prometheus](https://prometheus.io/) 可能是最好的選擇。如果您想看的話,請在下面輸入您的電子郵件。## 看這裡……如果您能買我的書那就太好了:+ [API Foundations in Go](https://leanpub.com/api-foundations)+ [12 Factor Apps with Docker and Go](https://leanpub.com/12fa-docker-golang)+ [The SaaS Handbook(work in progress)](https://leanpub.com/saas-handbook)我保證如果您買任何一本的話您可以學到很多東西。購買副本支援我寫更多關於相同的主題。感謝您買我的書。如果您想和我約時間為了諮詢/外包服務可以發[電子郵件](black@scene-si.org)給我。我很擅長 API,Go,Docker,VueJS 和 擴充服務等等。
via: https://scene-si.org/2018/08/06/basic-monitoring-of-go-apps-with-the-runtime-package/
作者:Tit Petric 譯者:themoonbear 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
148 次點擊