Redis 6.0 多線程性能測試結果及分析

 
單線程的Redis一貫以簡潔高效著稱,但也有其阿喀琉斯之踵:阻塞!單個線程在最容易產生瓶頸的網絡讀寫(Redis大key,也包括其餘一些重量級的操做SORT/SUNION/ZUNIONSTORE,集中性的expired key清理,內存溢出的maxmemory-policy策略等)請求完成以前,其餘全部請求都將會被阻塞,嚴重影響其效率,所以Redis的多線程呼聲就愈來愈高。因爲是基於內存的操做延遲很是低,因此即使是單線程模式下CPU資源也不會是的瓶頸。最容易出現瓶頸的仍是網絡IO操做。在Redis 6.0開始支持多線程以後,所謂的多線程也只是socket層面的多線程,核心的內存讀寫仍是單線程模式。
弄清楚了多線程的本質以後,就會有一系列的問題,多線程會比單線程有多大的提高?設置多少個線程合適?見一些大神測試過(目前全網也只有美圖作過unstable版本的測試,全部的轉載都是來源於這個測試),其結果也很是理想,但只是看看也不太過癮,決定一試爲快,本文將對Redis的多線程進行一個粗淺的測試驗證。同時須要思考另一個問題:面對多線程版本的Redis,和Redis cluster,該如何選擇?

多線程Redis

redis 6.0 的「多線程」特性讓不少標題黨高潮連連,參考圖片源自於:美圖技術團隊侵刪,核心的線程(Execute Command)仍是單線程,多線程是指網絡IO(socket)讀寫的多線程化。
以下圖所示,讀寫網絡socket中的數據是能夠用多個線程,因此Redis的多線程也叫作io thread,相關參數:「io-threads」。另外一個參數是io-threads-do-reads,這裏涉及另一個細節:多線程IO主要用在請求完成後返回結果集的過程,也就是socket寫操做,至於讀socket,單線程自己採用的多路IO複用技術,也不會產生瓶頸,所以Redis給出了一個io-threads-do-reads 參數,決定讀socket的時候是否啓用多線程。其實io-threads-do-reads是否啓用,對性能的影響並不大,最後會作一個驗證。
html

 

測試環境及策略

本機配置:centos 7,16C+32GB內存

Redis版本:6.0.6
下面分別以1線程,2線程,4線程,6線程,8線程,10線程的配置下,200個併發鏈接進行100百萬次請求(./bin/redis-benchmark -d 128  -c 200 -n 1000000 -t set -q ),同時爲避免網絡延遲帶來的影響,redis-benchmark在Redis實例本地,測試Redis的get和set性能。

 

翻車

整個測試開始以前,經歷了兩次翻車才得以繼續
翻車現場1
centos 7上默認的gcc是4.*版本,沒法編譯Redis 6.0,因此須要升級gcc,由於本機不支持yum安裝, 參考這裏使用源碼包安裝,gcc編譯的時候那個酸爽,本機16C+32GB內存的環境下,由於缺乏某些依賴包,致使失敗了幾回,最終編譯成功的一次,花了大概1個小時10分鐘
翻車現場2
沒有認真讀配置文件中的說明,設置io-threads後,重啓Redis服務後,上來就用redis-benchmark直接懟,其結果跟單線程差很少,使人大跌眼鏡。
最後仍是在原始配置文件發現了這段話:
If you want to test the Redis speedup using redis-benchmark, make sure you also run the benchmark itself in threaded mode, using the --threads option to match the number of Redis threads, otherwise you'll not be able to notice the improvements.
意思是必須在redis-benchmark設置--threads參數,而且要match Redis中的線程設置,--threads參數是redis 6.0後新增的一個參數。只有加上--threads這個參數才能體現出來多線程Redis的效率。

關於Thread IO的說明

經歷了第二次翻車以後決定好好看一看redis.conf中關於thread io的註釋信息
################################ THREADED I/O #################################

# Redis is mostly single threaded, however there are certain threaded
# operations such as UNLINK, slow I/O accesses and other things that are
# performed on side threads.
#
# Now it is also possible to handle Redis clients socket reads and writes
# in different I/O threads. Since especially writing is so slow, normally
# Redis users use pipelining in order to speed up the Redis performances per
# core, and spawn multiple instances in order to scale more. Using I/O
# threads it is possible to easily speedup two times Redis without resorting
# to pipelining nor sharding of the instance.
#
# By default threading is disabled, we suggest enabling it only in machines
# that have at least 4 or more cores, leaving at least one spare core.
# Using more than 8 threads is unlikely to help much. We also recommend using
# threaded I/O only if you actually have performance problems, with Redis
# instances being able to use a quite big percentage of CPU time, otherwise
# there is no point in using this feature.
#
# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
# threads, if you have a 8 cores, try to use 6 threads. In order to
# enable I/O threads use the following configuration directive:
#
# io-threads 4
#
# Setting io-threads to 1 will just use the main thread as usual.
# When I/O threads are enabled, we only use threads for writes, that is
# to thread the write(2) syscall and transfer the client buffers to the
# socket. However it is also possible to enable threading of reads and
# protocol parsing using the following configuration directive, by setting
# it to yes:
#
# io-threads-do-reads no
#
# Usually threading reads doesn't help much.
#
# NOTE 1: This configuration directive cannot be changed at runtime via
# CONFIG SET. Aso this feature currently does not work when SSL is
# enabled.
#
# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make
# sure you also run the benchmark itself in threaded mode, using the
# --threads option to match the number of Redis threads, otherwise you'll not
# be able to notice the improvements.
大概意思以下:
大多數狀況下redis是以單線程的方式運行的,然而,有一些線程操做,如斷開連接,耗時的I/O操做(bgsave,expired key清理之類的操做)和其餘任務是在side線程(主線程fork出來的子線程)中執行的。
如今也能夠在不一樣的I/O線程中處理Redis客戶端socket讀和寫。因爲寫入(指socket寫入)速度很是慢,Redis用戶一般使用pipelining來提升Redis在單核上的性能,並使用多個實例的方式來擴容。使用I/O線程能夠很容易地提高Redis在socket讀寫上的性能,而無需求助於管道或實例的分片。
Redis 6.0中默認狀況下多線程被是被禁用的,建議至少有4個或更多核的機器中啓用多線程,且至少留下1備用核。使用超過8個線程不大可能有太大幫助。
因爲Redis實例可以充分利用CPU資源(譯者注:意思是即使是單線程下,CPU並非瓶頸),多線程I/O只有在你確實有性能問題的狀況下才能提高運行效率,不然就沒有必要使用這個特性。
若是你有一個4核的服務器,嘗試使用2或3個I/O線程,若是是8核,嘗試使用6個線程。要啓用多線程I/O,請使用如下配置參數:io-threads 4
設置io-threads爲1會像傳統的redis同樣啓用單個主線程,當I/O threads被啓用以後,僅僅支持寫操做(譯者注:指的是socket的寫操做,socket的讀操做使用多路io複用技術,自己也不是瓶頸)即IO線程調用syscall並將客戶端緩衝區傳輸到socket。可是,也能夠啓用讀寫線程,使用如下配置指令進行協議解析,方法是將其設置爲「yes」:io-threads-do-reads no
一般狀況下,threading reads線程對性能的提高幫助並不大
注1:此配置指令不能在運行時經過配置集進行更改,只能在修改配置文件以後重啓。啓用SSL時,當前此特性也無效。
注2:若是你想用Redis-benchmark測試Redis的性能,務必以threaded mode的方式運行Redis-benchmark,使用--threads選項來匹配Redis線程的數量,不然沒法觀察到測試結果的提高。
 

測試結果及分析

以下是不一樣線程requests per second測試結果的橫向對比

從中能夠看到:
1,1個線程,也就是傳統的單線程模式,get 操做的QPS能夠達到9W左右
2,2個線程,get 操做的QPS能夠達到18W左右,相比單線程有100%+的提高
3,4個線程,與2線程相比,會有30%左右的提升,可是已經沒有從1個線程到2個線程翻一倍的提高了
4,6個線程,與4線程相比,沒有明顯的提高,對於SET操做,QPS從4線程到6線程,8線程開始沒有出現明顯的差別,都在23W~24W之間
5,8個線程,與4線程和6線程相比,8線程下大概有10%的提高
6,10個線程,相比效率最高的8線程,此時性能反卻是開始降低了,與4線程或者6線程的效率至關
所以在本機環境下,io-threads 4設置成2或者4個都ok,最多不超過8個,超出後性能反而會降低,同時也不能超出cpu的個數,正如配置文件中註釋中說的,至少要留出一個CPU。
 
以下是不一樣線程下10測試結果中GET和SET的requests per second 平均值對比:

 

關於io-threads-do-reads參數

上文提到過io-threads-do-reads這個參數,它是決定socket讀操做是否開啓多線程,Redis的socket讀操做採用多路IO複用技術,自己不會成爲瓶頸,因此這個參數對多線程下測試影響比較小。依舊參考這裏的這個圖 侵刪,這個io-threads-do-reads在筆者理解起來,就是socket讀的線程,是否開啓影響不大。
Redis.conf中關於此參數的註釋爲:When I/O threads are enabled, we only use threads for writes, that is to thread the write(2) syscall and transfer the client buffers to the socket. However it is also possible to enable threading of reads and protocol parsing using the following configuration directive, by setting it to yes Usually threading reads doesn't help much.
如下就該參數啓用與否進行一次對比測試
redis

參考以下截圖,在開啓了兩個線程的狀況下,分別開啓和禁用io-threads-do-reads,從總體上看,性能影響差異很小。固然專業的大神能夠從源碼的角度去分析。
io-threads爲2,且啓動io-threads-do-reads
數據庫

io-threads爲2,且禁動io-threads-do-reads
centos

 

多線程版本的Redis和Redis Cluster的選擇

redis集羣有兩種模式:sentinel和cluster,這裏暫時先不提sentinel,來思考多線程版本的Redis和Redis Cluster的選擇問題。
Redis的Cluster解決的就是擴展性和單節點單線程的阻塞隱患,若是Redis支持了多線程(目前多線程的Redis最對不建議超出8個線程),在不考慮單個節點網卡瓶頸的狀況下,其實這兩個問題都已經解決了,單節點能夠支持多線程和充分利用多核CPU,單節點能夠支持到25W QPS,還要啥自行車?
同時要考慮到Redis cluster的痛點:
1,不支持multiple/pipline操做(第三方插件也不必定穩定)。
2,cluster中每一個主節點要掛一個從節點,無論這個節點是否是獨立的物理節點仍是單機多實例中的一個節點,終究是增長了維護成本。
3,只能使用一個數據庫
4,集羣自身擴容、縮容帶來的一系列slot遷移等性能問題,以及集羣的管理問題
這些所謂的痛點也不復存在了,因此這裏就面臨一個從新選擇的問題:是使用多線程版本的Redis,仍是使用Redis cluster?這是一個須要思考的問題。服務器

 

疑問

關於redis-benchmark 測試時候 ./bin/redis-benchmark -d 128  -c 200 -n 1000000 -t set -q --threads 2,涉及的兩個參數-c和--threads,我的是不太理解的
-c的解釋是:指定併發鏈接數
--threads是redis-benchmark的線程數?
對此去跟老錢(素未謀面,感謝)求證了一下,說該參數是redis-benchmark客戶端的epoll,服務器端的多路IO複用原理已經看得我七葷八素了,客戶端也是帶epoll的,仍是不太理解這二者之間的關係。網絡

 

redis-benchmark測試現場

以下是redis-benchmark測試過程當中部分截圖多線程

圖太多了,就不一一貼上來了。
相關文章
相關標籤/搜索