long time 沒寫博客了,最近在搞golang服務器優化,很有心得mysql
場景一:linux
定時器隨着人數增多有延遲,延時蠻大,用法是在 time.AfterFunc以後往通道里面發送消息,golang
一直打印從開始發到,通道從緩衝區取出消息的時間差,發覺不對。redis
錯誤:打印方式不對,不該該在從緩衝區取出消息那一刻打印,由於通道里面當前消息處理若是比較耗時,是沒法從緩衝區取出下一個的。sql
優化:多開幾個協程處理,或者優化通道處理的那個函數。數據庫
場景二:編程
戰鬥結果通道kafka,而後再處理戰報等相關操做,處理比較耗時,致使做爲消費者的遊戲服處理戰鬥結果緩存
太慢了。服務器
優化:每次有戰鬥返回,直接開一條新協程go去處理,處理完會自動銷燬,不須要for循環保持。併發
場景三:
遊戲裏面大地圖視野返回太慢了,基本要0.3~0.4s返回結果,人數多的時候要等待好久。
優化:由於以前一直是直接用redis做爲大地圖的緩存,因此有不少i/o損耗,再加上存儲消息時的序列化和反序列化,消耗很是大。
因此後面改成大地圖100w地塊,分紅100個map,直接操做map裏面的地塊數據,而後再用個獨立協程去回寫數據到redis持久化。
場景四:
數據庫mysql的優化:exec的操做不須要玩家同步等待的都用協程異步操做(啓動了10個專門寫mysq的協程),全部操做都給個update來標記是否須要定時回寫mysql
redis的優化:可使用通道pipe批量讀寫。
如何定位問題?
先用pprof,分析運行時的cpu和memory:
import ( "net/http" _ "net/http/pprof" ) func main() { go func() { http.ListenAndServe("localhost:8080", nil) }() }
經過網頁查看overview:
http://localhost:8080/debug/pprof/
也能夠經過命令行查看信息:
查看cpu運行狀況:
go tool pprof --seconds 30 http://localhost:8080/debug/pprof/profile
查看總分配內存/正在運行內存:
go tool pprof -alloc_space/-inuse_space http://localhost:8080/debug/pprof/heap
進入後,可使用命令
help
top
top 50
list 某個函數
分析完pprof後,能夠猜測大概哪裏消耗比較大,而後能夠本身寫benchmark進行基準測試:
import ( "sync" "testing" ) var mutex = sync.Mutex{} func BenchmarkTest1(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { mutex.Lock() mutex.Unlock() } }
//go test world_test.go -bench=.
//go test world_test.go -bench=. -benchmem -run=none
goos: linux goarch: amd64 pkg: benchmark BenchmarkScriptf-2 10000000 106 ns/op 16 B/op 2 allocs/op BenchmarkFormat-2 500000000 3.79 ns/op 0 B/op 0 allocs/op BenchmarkItoa-2 300000000 5.49 ns/op 0 B/op 0 allocs/op BenchmarkUseMutex-2 100000000 17.4 ns/op 0 B/op 0 allocs/op BenchmarkUseChan-2 30000000 57.0 ns/op 0 B/op 0 allocs/op PASS ok benchmark 9.262s 106 ns/op 表示每次調用花費106納秒 16 B/op 函數每次調用須要分配16字節的內存 2 allocs/op 函數每次調用須要進行1次內存分配
何謂基準測試:就是指至少執行多少次,之後每次執行這個函數時間都是同樣的,穩定值!
當運行時間達到穩態時,benchmark纔會終止,算出每一次跑的平均時間
最後還能夠查看gc的狀況:
GODEBUG=gctrace=1 go run main.go
gc 1 @0.038s 1%: 0.55+0.12+0.081 ms clock, 2.2+0/0.42/1.1+0.32 ms cpu, 4->4->0 MB, 5 MB goal, 4 P。 1 表示第一次執行 @0.038s 表示程序執行的總時間 1% 垃圾回收時間佔用總的運行時間百分比 0.018+1.3+0.076 ms clock 垃圾回收的時間,分別爲STW(stop-the-world)清掃的時間, 併發標記和掃描的時間,STW標記的時間 0.054+0.35/1.0/3.0+0.23 ms cpu 垃圾回收佔用cpu時間 4->4->3 MB 堆的大小,gc後堆的大小,存活堆的大小 5 MB goal 總體堆的大小 4 P 使用的處理器數量 嘗試把這個比例調大 export GOGC=400,試圖下降 gc 觸發頻率
最後還有一個小技巧,很方便快捷檢查靜態代碼的bug:
go tool vet .
如何查找服務器崩潰宕機:
首先咱們服務器在每一個有for循環的協程裏面都會有recover去恢復現場,而且打印堆棧,因此說通常的錯誤例若有返回err或者空引用等,都
不會致使服務器崩潰,由於這個至關於try-catch了,是不會crash的,固然有些人是反對這種防護性編程的,just let it crash。可是在線上環境
你就會以爲很痛苦了,若是沒有recover,策劃一個小小的配置錯誤就能讓你服務器宕機!
言歸正傳,咱們服務器宕機了,可是由於沒法recover,因此單純經過普通的日誌是查不出來的。
由於我在啓動服務器的時候,都忽略了nohup的輸出,由於這個文件實在太大了,因此致使致命的錯誤沒法打印
nohup ./main > /dev/null &
改成:
nohup ./main > /dev/null 2> /tmp/error.log&
將錯誤日誌輸出到error.log
發現是map的緣由引發的:
併發讀寫,併發寫,迭代都報錯了,並且能夠看到最終是調用了panic,這函數是一定崩的!