偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一個95分位延遲要求5ms的場景,如何做性能優(yōu)化

存儲
組內(nèi)的數(shù)據(jù)系統(tǒng)在承接一個業(yè)務(wù)需求時無法滿足性能需求,于是針對這個場景做了一些優(yōu)化,在此寫篇文章做記錄。

[[414231]]

本文轉(zhuǎn)載自微信公眾號「薯條的編程修養(yǎng)」,作者程序員薯條。轉(zhuǎn)載本文請聯(lián)系薯條的編程修養(yǎng)公眾號。

組內(nèi)的數(shù)據(jù)系統(tǒng)在承接一個業(yè)務(wù)需求時無法滿足性能需求,于是針對這個場景做了一些優(yōu)化,在此寫篇文章做記錄。

業(yè)務(wù)場景是這樣:調(diào)用方一次獲取某個用戶的幾百個特征(可以把特征理解為屬性),特征以 redis hash 的形式存儲在持久化 KV 數(shù)據(jù)庫中,特征數(shù)據(jù)以天級別為更新粒度。要求 95 分位的延遲在 5ms 左右。

這個數(shù)據(jù)系統(tǒng)屬于無狀態(tài)的服務(wù),為了增大吞吐量和降低延遲,從存儲和代碼兩方面進行優(yōu)化。

存儲層面

存儲層面,一次調(diào)用一個用戶的三百個特征原方案是用 redis hash 做表,每個 field 為用戶的一個特征。由于用戶單個請求會獲取幾百個特征,即使用hmget做合并,存儲也需要去多個 slot 中獲取數(shù)據(jù),效率較低,于是對數(shù)據(jù)進行歸一化,即:把 hash 表的所有 filed 打包成一個 json 格式的 string,舉個例子:

  1. // 優(yōu)化前的特征為 hash 格式 
  2. hash key : user_2837947 
  3. 127.0.0.1:6379> hgetall user_2837947 
  4. 1) "name"    // 特征1 
  5. 2) "薯條"     // 特征1的值 
  6. 3) "age"    // 特征2 
  7. 4) "18"     // 特征2的值 
  8. 5) "address" // 特征3 
  9. 6) "China"   // 特征3的值 
  10.  
  11. // 優(yōu)化后的特征為 string json格式 
  12. string key: user_2837947 
  13. val: 
  14.   "name":"薯條"
  15.   "age":18, 
  16.   "address":"China" 

特征進行打包后解決了一次請求去多個 slot 獲取數(shù)據(jù)時延較大的問題。但是這樣做可能帶來新的問題:若 hash filed 過多,string 的 value 值會很大。目前想到的解法有兩種,一種是按照類型將特征做細分,比如原來一個 string 里面有 300 的字段,拆分成 3 個有 100 個值的 string 類型。第二種是對 string val 進行壓縮,在數(shù)據(jù)存儲時壓縮存儲,讀取數(shù)據(jù)時在程序中解壓縮。這兩種方法也可以結(jié)合使用。

如果這樣仍不能滿足需求,可以在持久化 KV 存儲前再加一層緩存,緩存失效時間根據(jù)業(yè)務(wù)特點設(shè)置,這樣程序交互的流程會變成這樣:

代碼層面

接著來優(yōu)化一下代碼。首先需要幾個工具去協(xié)助我們做性能優(yōu)化。首先是壓測工具,壓測工具可以模擬真實流量,在預(yù)估的 QPS 下觀察系統(tǒng)的表現(xiàn)情況。發(fā)壓時注意漸進式加壓,不要一下次壓得太死。

然后還需要 profiler 工具。Golang 的生態(tài)中相關(guān)工具我們能用到的有 pprof 和 trace。pprof 可以看 CPU、內(nèi)存、協(xié)程等信息在壓測流量進來時系統(tǒng)調(diào)用的各部分耗時情況。而 trace 可以查看 runtime 的情況,比如可以查看協(xié)程調(diào)度信息等。本次優(yōu)化使用 壓測工具+pprof 的 CPU profiler。

下面來看一下 CPU 運行耗時情況:

右側(cè)主要是 runtime 部分,先忽略

火焰圖中圈出來的大平頂山都是可以優(yōu)化的地方,

這里的三座平頂山的主要都是json.Marshal和json.Unmarshal操作引起的,對于 json 的優(yōu)化,有兩種思路,一種是換個高性能的 json 解析包 ,另一種是根據(jù)業(yè)務(wù)需求看能否繞過解析。下面分別來介紹:

高性能解析包+一點黑科技

這里使用了陶師傅的包github.com/json-iterator/go??戳怂?benchmark 結(jié)果,比 golang 原生庫還是要快很多的。自己再寫個比較符合我們場景的Benchmark看陶師傅有沒有騙我們:

  1. package main 
  2.  
  3. import ( 
  4.  "encoding/json" 
  5.  jsoniter "github.com/json-iterator/go" 
  6.  "testing" 
  7.  
  8. var s = `{....300多個filed..}` 
  9.  
  10. func BenchmarkDefaultJSON(b *testing.B) { 
  11.  for i := 0; i < b.N; i++ { 
  12.   param := make(map[string]interface{}) 
  13.   _ = json.Unmarshal([]byte(s), &param) 
  14.  } 
  15.  
  16. func BenchmarkIteratorJSON(b *testing.B) { 
  17.  for i := 0; i < b.N; i++ { 
  18.   param := make(map[string]interface{}) 
  19.   var json = jsoniter.ConfigCompatibleWithStandardLibrary 
  20.   _ = json.Unmarshal([]byte(s), &param) 
  21.  } 

運行結(jié)果:

這個包易用性也很強,在原來 json 代碼解析的上面加一行代碼就可以了:

  1. var json = jsoniter.ConfigCompatibleWithStandardLibrary 
  2. err = json.Unmarshal(datautil.String2bytes(originData), &fieldMap 

還有一個可以優(yōu)化的地方是string和[]byte之間的轉(zhuǎn)化,我們在代碼里用的參數(shù)類型是string,而 json 解析接受的參數(shù)是[]byte,所以一般在json解析時需要進行轉(zhuǎn)化:

  1. err = json.Unmarshal([]byte(originData), &fieldMap) 

那么string轉(zhuǎn)化為[]byte發(fā)生了什么呢。

  1. package main 
  2.  
  3. func main(){ 
  4.   a := "string" 
  5.   b := []byte(a) 
  6.   println(b) 

我們用匯編把編譯器悄悄做的事抓出來:

來看一下這個函數(shù)做了啥:

這里底層會發(fā)生拷貝現(xiàn)象,我們可以拿到[]byte和string的底層結(jié)構(gòu)后,用黑科技去掉拷貝過程:

  1. func String2bytes(s string) []byte { 
  2.  x := (*[2]uintptr)(unsafe.Pointer(&s)) 
  3.  h := [3]uintptr{x[0], x[1], x[1]} 
  4.  return *(*[]byte)(unsafe.Pointer(&h)) 
  5.  
  6. func Bytes2String(b []byte) string { 
  7.  return *(*string)(unsafe.Pointer(&b)) 

下面寫 benchmark 看一下黑科技好不好用:

  1. package main 
  2.  
  3. import ( 
  4.  "strings" 
  5.  "testing" 
  6.  
  7. var s = strings.Repeat("hello", 1024) 
  8.  
  9. func testDefault() { 
  10.  a := []byte(s) 
  11.  _ = string(a) 
  12.  
  13. func testUnsafe() { 
  14.  a := String2bytes(s) 
  15.  _ = Bytes2String(a) 
  16.  
  17. func BenchmarkTestDefault(b *testing.B) { 
  18.  for i := 0; i < b.N; i++ { 
  19.   testDefault() 
  20.  } 
  21.  
  22. func BenchmarkTestUnsafe(b *testing.B) { 
  23.  for i := 0; i < b.N; i++ { 
  24.   testUnsafe() 
  25.  } 

運行速度,內(nèi)存分配上效果都很明顯,黑科技果然黑:

加 cache,空間換時間

項目中有一塊代碼負責(zé)處理 N 個請求中的參數(shù)。代碼如下:

  1. for _, item := range items { 
  2.   var params map[string]string 
  3.   err := json.Unmarshal([]byte(items[1]), &params) 
  4.   if err != nil { 
  5.     ... 
  6.   } 

在這個需要優(yōu)化的場景中,上游在單次請求獲取某個用戶300多個特征,如果用上面的代碼我們需要json.Unmarshal300多次,這是個無用且非常耗時的操作,可以加 cache 優(yōu)化一下:

  1. paramCache := make(map[string]map[string]string) 
  2.  for _, item := range items { 
  3.   var params map[string]string 
  4.  
  5.   tmpParams, ok := cacheDict[items[1]] 
  6.   // 沒有解析過,進行解析 
  7.   if ok == false { 
  8.    err := json.Unmarshal([]byte(items[1]), &params) 
  9.    if err != nil { 
  10.     ... 
  11.    } 
  12.    cacheDict[items[1]] = params 
  13.   } else { 
  14.       // 解析過,copy出一份 
  15.       // 這里的copy是為了預(yù)防并發(fā)問題 
  16.    params = DeepCopyMap(tmpParams) 
  17.   } 
  18.  } 

這樣理論上不會存在任何的放大現(xiàn)象,讀者朋友如果有批處理的接口,代碼中又有類似這樣的操作,可以看下這里是否有優(yōu)化的可能性。

  1. for { 
  2.   dosomething() 

替換耗時邏輯

火焰圖中的 TplToStr 模板函數(shù)同樣占到了比較大的 CPU 耗時,此函數(shù)的功能是把用戶傳來的參數(shù)和預(yù)制的模板拼出一個新的 string 字符串,比如:

  1. 入?yún)ⅲ篢pl: shutiao_test_{{user_id}} user_id: 123478 
  2. 返回:shutiao_test_123478 

在我們的系統(tǒng)中,這個函數(shù)根據(jù)模板和用戶參數(shù)拼出一個 flag,根據(jù)這個 flag 是否相同作為某個操作的標(biāo)記。這個拼模板是一個非常耗時的操作,這塊可以直接用字符串拼接去代替模板功能,比如:

  1. 入?yún)ⅲ篢pl: shutiao_test_{{user_id}} user_id: 123478 
  2. 返回:shutiao_test_user_id_123478 

優(yōu)化完之后,火焰圖中已經(jīng)看不到這個函數(shù)的平頂山了,直接節(jié)省了 5%的 CPU 的調(diào)用百分比。

prealloc

還發(fā)現(xiàn)一些 growslice 占得微量 cpu 耗時,本以為預(yù)分配可以解決問題,但做 benchmark 測試發(fā)現(xiàn) slice 容量較小時是否做預(yù)分配在性能上差異不大:

  1. package main 
  2.  
  3. import "testing" 
  4.  
  5. func test(m *[]string) { 
  6.  for i := 0; i < 300; i++ { 
  7.   *m = append(*m, string(i)) 
  8.  } 
  9.  
  10. func BenchmarkSlice(b *testing.B) { 
  11.  for i := 0; i < b.N; i++ { 
  12.   b.StopTimer() 
  13.   m := make([]string, 0) 
  14.   b.StartTimer() 
  15.  
  16.   test(&m) 
  17.  } 
  18.  
  19. func BenchmarkCapSlice(b *testing.B) { 
  20.  for i := 0; i < b.N; i++ { 
  21.   b.StopTimer() 
  22.   m := make([]string, 300) 
  23.   b.StartTimer() 
  24.  
  25.   test(&m) 
  26.  } 

對于代碼中用到的 map 也可以做一些預(yù)分配,寫 map 時如果能確認容量盡量用 make 函數(shù)對容量進行初始化。

  1. package main 
  2.  
  3. import "testing" 
  4.  
  5. func test(m map[string]string) { 
  6.  for i := 0; i < 300; i++ { 
  7.   m[string(i)] = string(i) 
  8.  } 
  9.  
  10. func BenchmarkMap(b *testing.B) { 
  11.  for i := 0; i < b.N; i++ { 
  12.   b.StopTimer() 
  13.   m := make(map[string]string) 
  14.   b.StartTimer() 
  15.  
  16.   test(m) 
  17.  } 
  18.  
  19. func BenchmarkCapMap(b *testing.B) { 
  20.  for i := 0; i < b.N; i++ { 
  21.   b.StopTimer() 
  22.   m := make(map[string]string, 300) 
  23.   b.StartTimer() 
  24.  
  25.   test(m) 
  26.  } 

這個優(yōu)化還是比較有效的:

異步化

接口流程中有一些不影響主流程的操作完全可以異步化,比如:往外發(fā)送的統(tǒng)計工作。在 golang 中異步化就是起個協(xié)程。

總結(jié)一下套路:

代碼層面的優(yōu)化,是 us 級別的,而針對業(yè)務(wù)對存儲進行優(yōu)化,可以做到 ms 級別的,所以優(yōu)化越靠近應(yīng)用層效果越好。對于代碼層面,優(yōu)化的步驟是:

壓測工具模擬場景所需的真實流量

pprof 等工具查看服務(wù)的 CPU、mem 耗時

鎖定平頂山邏輯,看優(yōu)化可能性:異步化,改邏輯,加 cache 等

局部優(yōu)化完寫 benchmark 工具查看優(yōu)化效果

整體優(yōu)化完回到步驟一,重新進行 壓測+pprof 看效果,看 95 分位耗時能否滿足要求(如果無法滿足需求,那就換存儲吧~。

 

另外推薦一個不錯的庫,這是 Golang 布道師 Dave Cheney 搞的用來做性能調(diào)優(yōu)的庫,使用起來非常方便:https://github.com/pkg/profile,可以看 pprof和 trace 信息。有興趣讀者可以了解一下。

 

責(zé)任編輯:武曉燕 來源: 薯條的編程修養(yǎng)
相關(guān)推薦

2022-08-03 09:11:31

React性能優(yōu)化

2023-12-29 08:29:15

QPS系統(tǒng)應(yīng)用

2025-03-31 01:55:00

2011-03-01 10:42:23

無線局域網(wǎng)局域網(wǎng)性能優(yōu)化

2012-05-07 08:49:57

Clojure

2020-02-05 14:49:04

網(wǎng)絡(luò)性能優(yōu)化微調(diào)

2020-08-24 08:34:03

命令性能優(yōu)化

2012-12-17 12:58:18

WebjQuery重構(gòu)

2017-06-30 15:18:24

對賬系統(tǒng)互聯(lián)網(wǎng)

2025-07-03 08:21:16

2021-12-28 09:23:58

數(shù)據(jù)中心

2022-07-25 08:02:57

Tomcat調(diào)優(yōu)組件

2024-02-22 16:55:13

2020-10-30 15:04:16

開發(fā)技能代碼

2021-12-29 08:21:01

Performance優(yōu)化案例工具

2012-03-12 16:42:54

測試

2022-09-22 08:05:23

架構(gòu)

2022-01-04 09:01:10

開源項目開源技術(shù)

2023-06-01 07:49:51

2015-07-30 11:21:16

代碼審查
點贊
收藏

51CTO技術(shù)棧公眾號