Golang pprof 效能調優實戰,效能提升 3 倍!

花菜發表於2020-12-15

1.需求背景

服務升級,需要對kafka訊息持久化服務進行壓測,預計每分鐘要產生訊息400w條。
目前使用Golang實現了批量傳送kafka訊息的介面,但100w條訊息就要還是50s多,無法滿足需求,因此需要對傳送kafka介面進行效能調優

2.問題分析

2.1傳送的訊息量是否已經達到了網路io的瓶頸

經過測試,本地除錯時,確實存在這個問題。
在伺服器上除錯,則可以避免這個問題

2.2傳送kafka介面的實現存在效能問題

  • 組裝kafka訊息的邏輯(BenchMark測試也沒啥問題)
// 使用jsonpath替換某個欄位
// source {"name":{"first":"Janet","last":"Prichard"}}
// jsonKeyValue {"name.first": {"name.first", "_", "name.last"}}
// want {"name":{"first":"Janet_Prichard","last":"Prichard"}}

func replaceJsonMsg(source string, jsonKeyValue map[string][]string) string {
for keyPath, valueList := range jsonKeyValue {
var builder strings.Builder
for _, valuePath := range valueList {
value := gjson.Get(source, valuePath)
if value.Exists() {
builder.WriteString(value.Str)
} else {
builder.WriteString(valuePath)
}
}
source, _ = sjson.Set(source, keyPath, builder.String())
}
return source
}
rikasai@huacainoMBP handlers % go test -bench=BenchmarkReplaceJsonMsg -benchtime=5s -cpuprofile jsonCpu.out
goos: darwin
goarch: amd64
pkg: BigDataTestTool/handlers
BenchmarkReplaceJsonMsg-12 7363956 814 ns/op
PASS

啟動web ui檢視效能分析圖
rikasai@huacainoMBP handlers % go tool pprof -http=:8081 jsonCpu.out
replaceJsonMsg cpu profile

  • 傳送kafka的第三方庫有問題(經過測試,直接發訊息,不經過任何處理時,第三方庫能滿足需求)

2.3kafka服務端配置存在問題

  • 經研發大佬確認過,配置正常

3.除錯過程

上述的幾種猜測都沒發現什麼大問題
我想replaceJsonMsg這個函式用的是string替換,改成map會不會更快
於是就有了replaceMapMsg

func replaceMapMsg(msg *map[string]interface{}, jsonKeyValue *map[string][]string) *[]byte {
for keyPath, valueList := range *jsonKeyValue {
var builder strings.Builder
for _, valuePath := range valueList {
property, err := GetProperty(*msg, valuePath)
if err == nil {
s := fmt.Sprintf("%v", property)
builder.WriteString(s)
} else {
s := fmt.Sprintf("%v", valuePath)
builder.WriteString(s)
}
}
UpdateProperty(*msg, keyPath, builder.String())
}
b, _ := json.Marshal(msg)
return &b
}
func BenchmarkReplaceMapMsg(b *testing.B) {
msg := map[string]interface{}{"name": map[string]interface{}{"first": "Janet", "last": "Prichard"}}
j := map[string][]string{"
name.first": {"name.first", "_", "name.last"}}
for i := 0; i < b.N; i++ {
ReplaceMapMsg(&msg, &j)
}
}

結果大吃一驚!
反向優化了!!!

rikasai@huacainoMBP handlers % go test -bench=BenchmarkReplaceMapMsg -benchtime=5s -cpuprofile mapCpu.out 
goos: darwin
goarch: amd64
pkg: BigDataTestTool/handlers
BenchmarkReplaceMapMsg-12 41468 419347 ns/op
PASS
ok BigDataTestTool/handlers 19.071s

rikasai@huacainoMBP handlers % go tool pprof -http=:8080 mapCpu.out
Serving web UI on http://localhost:8080

趕緊看一波pprof結果
replaceMapMsg cpu profile
一語驚醒夢中人~
json.Marshal原來是這個傢伙佔了大頭
通過map替換是很快,但終究還是要序列化成[]byte型別才能傳送kafka訊息
那用replaceJsonMsg是事先序列化好了,怎麼還會那麼慢呢?

for begin <= end {
deviceNo := fmt.Sprintf("%v%v", b.DevicePrefix, begin)
for _, m := range b.MsgPayload {
var msg string
if b.MsgType == "json" {
m["deviceNo"] = deviceNo
marshal, _ := json.Marshal(m)
s := string(marshal)
msg = replaceJsonMsg(s, b.JsonKeyValue)
}
p.Produce(&kafka.Message{
TopicPartition: kafka.TopicPartition{Topic: &t, Partition: kafka.PartitionAny},
Value: []byte(msg),
}, nil)
}
if (end-begin)*int64(len(b.MsgPayload))%100000 == 0 {
p.Flush(5 * 1000)
}
begin++
}

好傢伙,每個迴圈都執行一次json.Marshal(m), 發100w條訊息就會執行100w次,簡直要命啊!

4.優化結果

// 先序列化,下面用到的時候,只需要迭代strMsg這個切片取出即可
strMsg := make([]string, 0, len(b.MsgPayload)) // 定義切片要小心,要先指定好容量,否則append會觸發自動擴容
if b.MsgType == "json" {
for _, m := range b.MsgPayload {
marshal, _ := json.Marshal(m)
s := string(marshal)
strMsg = append(strMsg, s)
}
}

for begin <= end {
deviceNo := fmt.Sprintf("%v%v", b.DevicePrefix, begin)
var msg string
if b.MsgType == "json" {
for _, s := range strMsg {
msg = replaceJsonMsg(s, deviceNo, b.JsonKeyValue)
p.Produce(&kafka.Message{
TopicPartition: kafka.TopicPartition{Topic: &t, Partition: kafka.PartitionAny},
Value: []byte(msg),
}, nil)
}
}

優化前傳送100w條訊息,耗時將近56s

{
"success_count": 1000001,
"fail_count": 0,
"parse_fail": [],
"elapsed": "55.874803s",
"msg": "success"
}

優化後傳送100w條訊息,耗時將近18s,提升了三倍的效能!
距離每分鐘400w還差一點點,還得繼續加油~

{
"success_count": 1000001,
"fail_count": 0,
"parse_fail": [],
"elapsed": "17.823006273s",
"msg": "success"
}

相關文章