Redis MGET性能衰減分析(轉)


轉:https://www.jianshu.com/p/172b39244c85

 

MGET是redis中較為常用的命令,用來批量獲取給定key對應的value。因為redis使用基於RESP (REdis Serialization Protocol)協議的rpc接口,而redis本身的數據結構非常高效,因此在日常使用中,IO和協議解析是個不容忽略的資源消耗。通過mget將多個get請求匯聚成一條命令,可以大大降低網絡、rpc協議解析的開銷,從而大幅提升緩存效率。mget的定義如下(來自REDIS命令參考):

MGET key [key ...]
返回所有(一個或多個)給定 key 的值。 如果給定的 key 里面,有某個 key 不存在,那么這個 key 返回特殊值 nil 。因此,該命令永不失敗。 返回值: 一個包含所有給定 key 的值的列表。q 例: redis> SET redis redis.com OK redis> SET mongodb mongodb.org OK redis> MGET redis mongodb 1) "redis.com" 2) "mongodb.org" 

但是,在某些需要一次批量查詢大量key的場景下,我們會發現mget並沒有想象中的那么完美。

以電商庫存系統(或價格系統)為例,作為原子級的服務,我們經常要面對商品列表頁、活動頁、購物車、交易等系統的批量查詢,一次請求中動輒包含幾十甚至上百個sku,此時mget是否還能像我們想象中那般保持極高的吞吐?

我們先來設計一個實驗,探一探mget性能的底。

1 實驗設計

在本地進行了壓測模擬,redis key設計:

  1. key為string類型,固定為八位數字字符串以模擬SKU ID,不足8位者高位填0
  2. value為5位整型數字,模擬商品庫存
  3. 實驗中將SKU ID設置為1~500000的數字

單元測試代碼設計原則:

  1. 可以方便地調整測試參數
  2. 盡量減少GC對結果的影響,設置合適的堆空間和垃圾回收器

壓測代碼做了局部優化以盡量保證結果的准確性,包括:

  • 針對每一輪壓測,提前准備好隨機的key列表,避免隨機生成key列表時大量的內存操作對測試結果造成影響
  • 每一輪壓測統計多次mget的平均執行時間
  • 每一輪壓測完成后,強制觸發fullgc,盡量避免在壓測進行中發生gc

2 JVM調優

考慮到redis平均響應時間在0.1ms以內,而一次minor gc一般需要耗時50ms以上,而full gc更可能耗費數秒,因此需要格外注意壓測時的jvm內存設置和GC配置。

經過調試,設置Java啟動參數:-Xms3g -Xmx3g -XX:+UseG1GC -XX:MaxNewSize=1g -server -XX:MaxTenuringThreshold=1時,在本實驗中可以獲得理想的結果。

通過下面jstat日志可以看出,實驗過程中沒有出現minor gc,每一輪結束后強制進行一次full gc,full gc觸發次數與壓測輪數一致。因此測試中統計的時間僅包含了redis響應時間和相關代碼執行時間 (在性能越高的場景下,代碼執行時間相對影響越大)。

jinlu$ jstat -gcutil $(jps | grep AppMain | cut -d " " -f 1) 4000 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 100.00 24.82 20.75 97.13 93.88 9 0.719 0 0.000 0.719 0.00 100.00 2.42 31.50 97.16 93.88 12 0.746 0 0.000 0.746 0.00 0.00 1.39 28.67 98.06 94.73 12 0.746 1 1.832 2.579 0.00 0.00 7.90 0.08 98.12 94.78 12 0.746 2 2.104 2.850 ... 0.00 0.00 9.49 0.07 95.66 95.10 13 1.035 33 5.245 6.280 0.00 100.00 16.90 18.24 95.66 95.10 14 1.375 34 5.307 6.683 0.00 100.00 42.14 23.35 95.66 95.10 15 1.425 34 5.307 6.733 

另外,為了保證結果的可靠性,整個測試期間,通過top對系統性能進行監控,結果如下:

  1. redis CPU占用率很高但未飽和,即沒有出現redis性能飽和導致的響應變慢
  2. java進程的CPU占用率維持在30%上下,表明java代碼沒有遇到瓶頸,時間主要用於等待redis返回mget結果。

可見壓測過程中,redis的CPU占比保持在50%~80%但沒有飽和,java進程的cpu保持30%~50%。因此不存在因為CPU導致的響應變慢,結果完全反應在中重度壓力下redis對mget的處理能力。壓測過程中抓取的top圖如下:

 
top系統性能

3 實驗結果

通過針對不同的mgetkey長度進行多輪壓測,得到不同的key長度下redis響應能力表如下:

keyLen 耗時ms qps % lg(keyLen)   keyLen 耗時ms qps % lg(keyLen)
1 0.040 25056.377 100 0.000   18 0.049 20242.914 80.7 1.255  
2 0.040 25284.449 100 0.301   20 0.055 18214.936 72.7 1.301  
3 0.040 24752.475 99.8 0.477   25 0.053 18811.137 75.0 1.398  
4 0.042 23618.328 94.2 0.602   32 0.057 17525.412 70.0 1.505  
5 0.044 22696.322 90.6 0.699   40 0.063 15888.147 63.4 1.602  
6 0.042 23849.273 95.2 0.778   50 0.067 14889.815 59.4 1.699  
7 0.043 23255.814 92.8 0.845   60 0.075 13276.687 53.0 1.778  
8 0.044 22888.533 91.3 0.903   80 0.091 10979.358 43.8 1.903  
9 0.045 22040.996 88.0 0.954   100 0.096 10405.827 41.5 2.000  
10 0.045 22065.313 88.0 1.000   200 0.161 6211.180 24.8 2.301  
11 0.046 21901.008 87.4 1.041   500 0.348 2871.913 11.5 2.699  
12 0.046 21691.975 86.6 1.079   800 0.552 1812.251 7.2 2.903  
13 0.047 21105.951 84.2 1.114   1000 0.639 1564.945 6.2 3.000  
14 0.047 21258.504 84.4 1.146   2000 1.201 832.639 3.3 3.301  
15 0.049 20300.447 81.0 1.176   5000 3.140 318.471 1.2 3.699  
16 0.050 20032.051 79.9 1.204   8000 5.297 188.786 0.7 3.903  
17 0.049 20234.723 80.8 1.230   10000 6.141 162.840 0.6 4.000  

下面分段進行分析。

3.1 單次mget的key數目在50以內時

  • 一次操作10個key的性能達到一次操作1個key的88%
  • 一次操作20個key的性能達到一次操作1個key的72%
  • 一次操作50個key的性能達到一次操作1個key的59%
 
keyLen<50曲線擬合

可以看出,此時redis整體響應非常好,包含50個以內的key時,mget既可以保持高qps,又可以大幅提升吞吐量。

3.2 單次mget的key數目在100以內時

  • 一次操作60個key的性能達到一次操作1個key的53%
  • 一次操作80個key的性能達到一次操作1個key的43%
  • 一次操作100個key的性能大道一次操作1個key的41%
 
keyLen<100曲線擬合

單次操作key數量在100以內時,性能大概能達到redis最大性能的40%以上,考慮到key的吞吐量,這樣做是有足夠的收益的,但是需要清楚當前場景下單個redis實例的最大吞吐量,必要時需要進行分片以提高系統整體性能。

3.3 單次mget的key數目在1000以內

  • 一次操作200個key的性能只能達到一次操作1個key的25%,大約是一次處理100個key的60%
  • 一次操作500個key的性能只能達到一次操作1個key的11%,大約是一次處理100個key的28%
  • 一次操作800個key的性能只能達到一次操作1個key的7%,大約是一次處理100個key的17%
 
keyLen<1000曲線擬合

可見,雖然相比較較少的key,單次請求處理更多的key還是有性能上的微弱優勢,但是此時性能衰減已經比較嚴重,此時的redis實例不在是那個動輒每秒幾萬qps的超人了,可能從性能上來說可以接受,但是我們要清楚此時redis的響應能力,並結合業務場景並考慮是否需要通過其他手段來為redis減負,比如分片、讀寫分離、多級緩存等。

3.4 單次mget的key數目在1000以上

  • 性能急劇惡化,即使在高性能服務器上,這樣的操作在單redis實例上也只能維持在千上下,此時單次請求的響應時間退化到與key數目成正比。除非你確定需要這么做,否則就要盡量避免如此多的key的批量獲取,而應該從業務上、架構上考慮這么做的必要性。
 
keyLen>1000曲線擬合

3.5 請求時間與key數目對數的關系

對mget的key數目取對數,可以得到如下曲線。

 
log10(keyLen) and keyLen<10000

【注意】 x軸為key的數目對10取對數,即log10(keyLen)

  • 當key數目在10以內時,mget性能下降趨勢非常小,性能基本上能達到redis實例的極限
  • 當key數目在10~100之間時,mget性能下降明顯,需要考慮redis性能衰減對系統吞吐的影響
  • 當key數目在100以上時,mget性能下降幅度趨緩,此時redis性能已經較差,不建議使用在OLTP系統中,或者需要考慮其他手段來提升性能。



作者:近路
鏈接:https://www.jianshu.com/p/172b39244c85
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM