golang服務器優化之旅

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,這函數是一定崩的!

相關文章
相關標籤/搜索