使用 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 開銷也較小,目前除了能夠監控服務器自己的資源使用狀況外,還支持常見的應用服務器和服務,目前支持列表以下:html

  • 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 模塊收集的數據進行的可視化分析吧:
 git

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 服務上,以下:github

import _ "expvar"

若是 Golang 沒有啓動 http 服務,使用下面的方式啓動一個便可,這裏端口是 6060,以下:golang

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.ymlredis

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

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

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


如今啓動 Metricbeat:服務器

./metricbeat -e -v

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

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

在 Metricbeat 內部也一樣暴露了不少內部運行的信息,因此 Metricbeat 能夠本身監控本身了。。。
首先,啓動的時候帶上參數設置pprof監控的地址,以下:elasticsearch

./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] 訪問到內部運行狀況了,以下:ide

{
"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 發佈,有興趣嚐鮮的能夠本身下載源碼打包。

相關文章
相關標籤/搜索