使用 Elastic Stack 來監控和調優 Golang 應用程序 原 薦

Golang 因爲其語法簡單,上手快且方便部署正被越來越多的開發者所青睞,一個 Golang 程序開發好了之後,勢必要關心其運行情況,今天在這裏就給大家介紹一下如果使用 Elastic Stack 來分析 Golang 程序的內存使用情況,方便對 Golang 程序做長期監控進而調優和診斷,甚至發現一些潛在的內存泄露等問題。
 
Elastic Stack 其實是一個集合,包含 Elasticsearch、Logstash 和 Beats 這幾個開源軟件,而 Beats 又包含 Filebeat、Packetbeat、Winlogbeat、Metricbeat 和新出的 Heartbeat,呵呵,有點多吧,恩,每個 beat 做的事情不一樣,沒關係,今天主要用到 Elasticsearch、Metricbeat 和 Kibana 就行了。
 
Metricbeat 是一個專門用來獲取服務器或應用服務內部運行指標數據的收集程序,也是 Golang 寫的,部署包比較小才10M 左右,對目標服務器的部署環境也沒有依賴,內存資源佔用和 CPU 開銷也較小,目前除了可以監控服務器本身的資源使用情況外,還支持常見的應用服務器和服務,目前支持列表如下:

  • Apache Module
  • Couchbase Module
  • Docker Module
  • HAProxy Module
  • kafka Module
  • MongoDB Module
  • MySQL Module
  • Nginx Module
  • PostgreSQL Module
  • Prometheus Module
  • Redis Module
  • System Module
  • ZooKeeper Module

當然,也有可能你的應用不在上述列表,沒關係,Metricbeat 是可以擴展的,你可以很方便的實現一個模塊,而本文接下來所用的 Golang Module 也就是我剛剛爲 Metricbeat 添加的擴展模塊,目前已經 merge 進入 Metricbeat 的 master 分支,預計會在 6.0 版本發佈,想了解是如何擴展這個模塊的可以查看 代碼路徑 和 PR地址
 
上面的這些可能還不夠吸引人,我們來看一下 Kibana 對 Metricbeat 使用 Golang 模塊收集的數據進行的可視化分析吧:
 

df9c563e-f831-11e6-835c-183f3f9e5b94.png


 
上面的圖簡單解讀一下:
最上面一欄是 Golang Heap 的摘要信息,可以大致瞭解 Golang 的內存使用和 GC 情況,System 表示 Golang 程序從操作系統申請的內存,可以理解爲進程所佔的內存(注意不是進程對應的虛擬內存),Bytes allocated 表示 Heap 目前分配的內存,也就是 Golang 裏面直接可使用的內存,GC limit 表示當 Golang 的 Heap 內存分配達到這個 limit 值之後就會開始執行 GC,這個值會隨着每次 GC 而變化, GC cycles 則代表監控週期內的 GC 次數;
 
中間的三列分別是堆內存、進程內存和對象的統計情況;Heap Allocated 表示正在用和沒有用但還未被回收的對象的大小;Heap Inuse 顯然就是活躍的對象大小了;Heap Idle 表示已分配但空閒的內存;

底下兩列是 GC 時間和 GC 次數的監控統計,CPUFraction 這個代表該進程 CPU 佔用時間花在 GC 上面的百分比,值越大說明 GC 越頻繁,浪費更多的時間在 GC 上面,上圖雖然趨勢陡峭,但是看範圍在0.41%~0.52%之間,看起來還算可以,如果GC 比率佔到個位數甚至更多比例,那肯定需要進一步優化程序了。
 
有了這些信息我們就能夠知道該 Golang 的內存使用和分配情況和 GC 的執行情況,假如要分析是否有內存泄露,看內存使用和堆內存分配的趨勢是否平穩就可以了,另外 GC_Limit 和 Byte Allocation 一直上升,那肯定就是有內存泄露了,結合歷史信息還能對不同版本/提交對 Golang 的內存使用和 GC 影響進行分析。

接下來就要給大家介紹如何具體使用了,首先需要啓用 Golang 的 expvar 服務,expvar(https://golang.org/pkg/expvar/) 是 Golang 提供的一個暴露內部變量或統計信息的標準包。
使用的方法很簡單,只需要在 Golang 的程序引入該包即可,它會自動註冊現有的 http 服務上,如下:

import _ "expvar"

如果 Golang 沒有啓動 http 服務,使用下面的方式啓動一個即可,這裏端口是 6060,如下:

func metricsHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	first := true
	report := func(key string, value interface{}) {
		if !first {
			fmt.Fprintf(w, ",\n")
		}
		first = false
		if str, ok := value.(string); ok {
			fmt.Fprintf(w, "%q: %q", key, str)
		} else {
			fmt.Fprintf(w, "%q: %v", key, value)
		}
	}

	fmt.Fprintf(w, "{\n")
	expvar.Do(func(kv expvar.KeyValue) {
		report(kv.Key, kv.Value)
	})
	fmt.Fprintf(w, "\n}\n")
}

func main() {
   mux := http.NewServeMux()
   mux.HandleFunc("/debug/vars", metricsHandler)
   endpoint := http.ListenAndServe("localhost:6060", mux)
}

默認註冊的訪問路徑是/debug/vars, 編譯啓動之後,就可以通過 http://localhost:6060/debug/vars  來訪問 expvar 以 JSON 格式暴露出來的這些內部變量,默認提供了 Golang 的 runtime.Memstats 信息,也就是上面分析的數據源,當然你還可以註冊自己的變量,這裏暫時不提。
 
OK,現在我們的 Golang 程序已經啓動了,並且通過 expvar 暴露出了運行時的內存使用情況,現在我們需要使用 Metricbeat 來獲取這些信息並存進 Elasticsearch。
 
關於 Metricbeat 的安裝其實很簡單,下載對應平臺的包解壓(下載地址:https://www.elastic.co/downloads/beats/metricbeat ),啓動 Metricbeat 前,修改配置文件:metricbeat.yml

metricbeat.modules:
  - module: golang
     metricsets: ["heap"]
     enabled: true
     period: 10s
     hosts: ["localhost:6060"]
     heap.path: "/debug/vars"

上面的參數啓用了 Golang 監控模塊,並且會10秒獲取一次配置路徑的返回內存數據,我們同樣配置該配置文件,設置數據輸出到本機的 Elasticsearch:

output.elasticsearch:
  hosts: ["localhost:9200"]


現在啓動 Metricbeat:

./metricbeat -e -v

現在在 Elasticsearch 應該就有數據了,當然記得確保 Elasticsearch 和 Kibana 是可用狀態,你可以在 Kibana 根據數據靈活自定義可視化,推薦使用 Timelion 來進行分析,當然爲了方便也可以直接導入提供的樣例儀表板,就是上面第一個圖的效果。
關於如何導入樣例儀表板請參照這個文檔:https://www.elastic.co/guide/e ... .html 
 
除了監控已經有的內存信息之外,如果你還有一些內部的業務指標想要暴露出來,也是可以的,通過 expvar 來做同樣可以。一個簡單的例子如下:

var inerInt int64 = 1024
pubInt := expvar.NewInt("your_metric_key")
pubInt.Set(inerInt)
pubInt.Add(2)

在 Metricbeat 內部也同樣暴露了很多內部運行的信息,所以 Metricbeat 可以自己監控自己了。。。
首先,啓動的時候帶上參數設置pprof監控的地址,如下:

./metricbeat -httpprof="127.0.0.1:6060" -e -v

這樣我們就能夠通過 [url=http://127.0.0.1:6060/debug/vars]http://127.0.0.1:6060/debug/vars[/url] 訪問到內部運行情況了,如下:

{
"output.events.acked": 1088,
"output.write.bytes": 1027455,
"output.write.errors": 0,
"output.messages.dropped": 0,
"output.elasticsearch.publishEvents.call.count": 24,
"output.elasticsearch.read.bytes": 12215,
"output.elasticsearch.read.errors": 0,
"output.elasticsearch.write.bytes": 1027455,
"output.elasticsearch.write.errors": 0,
"output.elasticsearch.events.acked": 1088,
"output.elasticsearch.events.not_acked": 0,
"output.kafka.events.acked": 0,
"output.kafka.events.not_acked": 0,
"output.kafka.publishEvents.call.count": 0,
"output.logstash.write.errors": 0,
"output.logstash.write.bytes": 0,
"output.logstash.events.acked": 0,
"output.logstash.events.not_acked": 0,
"output.logstash.publishEvents.call.count": 0,
"output.logstash.read.bytes": 0,
"output.logstash.read.errors": 0,
"output.redis.events.acked": 0,
"output.redis.events.not_acked": 0,
"output.redis.read.bytes": 0,
"output.redis.read.errors": 0,
"output.redis.write.bytes": 0,
"output.redis.write.errors": 0,
"beat.memstats.memory_total": 155721720,
"beat.memstats.memory_alloc": 3632728,
"beat.memstats.gc_next": 6052800,
"cmdline": ["./metricbeat","-httpprof=127.0.0.1:6060","-e","-v"],
"fetches": {"system-cpu": {"events": 4, "failures": 0, "success": 4}, "system-filesystem": {"events": 20, "failures": 0, "success": 4}, "system-fsstat": {"events": 4, "failures": 0, "success": 4}, "system-load": {"events": 4, "failures": 0, "success": 4}, "system-memory": {"events": 4, "failures": 0, "success": 4}, "system-network": {"events": 44, "failures": 0, "success": 4}, "system-process": {"events": 1008, "failures": 0, "success": 4}},
"libbeat.config.module.running": 0,
"libbeat.config.module.starts": 0,
"libbeat.config.module.stops": 0,
"libbeat.config.reloads": 0,
"memstats": {"Alloc":3637704,"TotalAlloc":155
... ...

比如,上面就能看到output模塊Elasticsearch的處理情況,如 output.elasticsearch.events.acked 參數表示發送到 Elasticsearch Ack返回之後的消息。
 
現在我們要修改 Metricbeat 的配置文件,Golang 模塊有兩個 metricset,可以理解爲兩個監控的指標類型,我們現在需要加入一個新的 expvar 類型,這個即自定義的其他指標,相應配置文件修改如下:

- module: golang
  metricsets: ["heap","expvar"]
  enabled: true
  period: 1s
  hosts: ["localhost:6060"]
  heap.path: "/debug/vars"
  expvar:
    namespace: "metricbeat"
    path: "/debug/vars"

上面的一個參數 namespace 表示自定義指標的一個命令空間,主要是爲了方便管理,這裏是 Metricbeat 自身的信息,所以 namespace 就是 metricbeat。
 
重啓 Metricbeat 應該就能收到新的數據了,我們前往 Kibana。
 
這裏假設關注 output.elasticsearch.events.acked和
output.elasticsearch.events.not_acked這兩個指標,我們在Kibana裏面簡單定義一個曲線圖就能看到 Metricbeat 發往 Elasticsearch 消息的成功和失敗趨勢。
Timelion 表達式:

.es("metricbeat*",metric="max:golang.metricbeat.output.elasticsearch.events.acked").derivative().label("Elasticsearch Success"),.es("metricbeat*",metric="max:golang.metricbeat.output.elasticsearch.events.not_acked").derivative().label("Elasticsearch Failed")

效果如下:
 

Snip20170304_9.png


從上圖可以看到,發往 Elasticsearch 的消息很穩定,沒有出現丟消息的情況,同時關於 Metricbeat 的內存情況,我們打開導入的 Dashboard 查看:
 

Snip20170304_10.png



關於如何使用 Metricbeat 來監控 Golang 應用程序的內容基本上就差不多到這裏了,上面介紹瞭如何基於 expvar 來監控 Golang 的內存情況和自定義業務監控指標,在結合 Elastic Stack 可以快速的進行分析,希望對大家有用。

最後,這個 Golang 模塊目前還沒 release,估計在 beats 6.0 發佈,有興趣嚐鮮的可以自己下載源碼打包。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章