电竞比分网-中国电竞赛事及体育赛事平台

分享

Redis 6.0 多線程性能測試結(jié)果及分析

 路人甲Java 2022-02-17
 
單線程的Redis一向以簡潔高效著稱,但也有其阿喀琉斯之踵:阻塞!單個(gè)線程在最容易產(chǎn)生瓶頸的網(wǎng)絡(luò)讀寫(Redis大key,也包括其他一些重量級(jí)的操作sort/sunion/zunionstore/sdiff,集中性的expired key清理,內(nèi)存溢出的maxmemory-policy策略等)請求完成之前,其他所有請求都將會(huì)被阻塞,嚴(yán)重影響其效率,因此Redis的多線程呼聲就越來越高。由于是基于內(nèi)存的操作延遲非常低,所以即便是單線程模式下CPU資源也不會(huì)是的瓶頸。最容易出現(xiàn)瓶頸的還是網(wǎng)絡(luò)IO操作。在Redis 6.0開始支持多線程之后,所謂的多線程也只是socket層面的多線程,核心的內(nèi)存讀寫還是單線程模式。
弄清楚了多線程的本質(zhì)之后,就會(huì)有一系列的問題,多線程會(huì)比單線程有多大的提升?設(shè)置多少個(gè)線程合適?見一些大神測試過(目前全網(wǎng)也只有美圖做過unstable版本的測試,所有的轉(zhuǎn)載都是來源于這個(gè)測試),其結(jié)果也非常理想,但只是看看也不太過癮,決定一試為快,本文將對(duì)Redis的多線程進(jìn)行一個(gè)粗淺的測試驗(yàn)證。同時(shí)需要思考另外一個(gè)問題:面對(duì)多線程版本的Redis,和Redis cluster,該如何選擇?

多線程Redis

redis 6.0 的“多線程”特性讓很多標(biāo)題黨高潮連連,參考圖片源自于:美圖技術(shù)團(tuán)隊(duì)侵刪,核心的線程(Execute Command)還是單線程,多線程是指網(wǎng)絡(luò)IO(socket)讀寫的多線程化。
如下圖所示,讀寫網(wǎng)絡(luò)socket中的數(shù)據(jù)是可以用多個(gè)線程,所以Redis的多線程也叫做io thread,相關(guān)參數(shù):“io-threads”。另一個(gè)參數(shù)是io-threads-do-reads,這里涉及另外一個(gè)細(xì)節(jié):多線程IO主要用在請求完成后返回結(jié)果集的過程,也就是socket寫操作,至于讀socket,單線程本身采用的多路IO復(fù)用技術(shù),也不會(huì)產(chǎn)生瓶頸,因此Redis給出了一個(gè)io-threads-do-reads 參數(shù),決定讀socket的時(shí)候是否啟用多線程。其實(shí)io-threads-do-reads是否啟用,對(duì)性能的影響并不大,最后會(huì)做一個(gè)驗(yàn)證。

 

測試環(huán)境及策略

本機(jī)配置:centos 7,16C+32GB內(nèi)存

Redis版本:6.0.6
下面分別以1線程,2線程,4線程,6線程,8線程,10線程的配置下,200個(gè)并發(fā)連接進(jìn)行100百萬次請求(./bin/redis-benchmark -d 128  -c 200 -n 1000000 -t set -q ),同時(shí)為避免網(wǎng)絡(luò)延遲帶來的影響,redis-benchmark在Redis實(shí)例本地,測試Redis的get和set性能。

 

翻車

整個(gè)測試開始之前,經(jīng)歷了兩次翻車才得以繼續(xù)
翻車現(xiàn)場1
centos 7上默認(rèn)的gcc是4.*版本,無法編譯Redis 6.0,所以需要升級(jí)gcc,因?yàn)楸緳C(jī)不支持yum安裝,參考這里使用源碼包安裝,gcc編譯的時(shí)候那個(gè)酸爽,本機(jī)16C+32GB內(nèi)存的環(huán)境下,因?yàn)槿鄙倌承┮蕾嚢瑢?dǎo)致失敗了幾次,最終編譯成功的一次,花了大概1個(gè)小時(shí)10分鐘
翻車現(xiàn)場2
沒有認(rèn)真讀配置文件中的說明,設(shè)置io-threads后,重啟Redis服務(wù)后,上來就用redis-benchmark直接懟,其結(jié)果跟單線程差不多,令人大跌眼鏡。
最后還是在原始配置文件發(fā)現(xiàn)了這段話:
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設(shè)置--threads參數(shù),并且要match Redis中的線程設(shè)置,--threads參數(shù)是redis 6.0后新增的一個(gè)參數(shù)。只有加上--threads這個(gè)參數(shù)才能體現(xiàn)出來多線程Redis的效率。

關(guān)于Thread IO的說明

經(jīng)歷了第二次翻車之后決定好好看一看redis.conf中關(guān)于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.
大概意思如下:
大多數(shù)情況下redis是以單線程的方式運(yùn)行的,然而,有一些線程操作,如斷開鏈接,耗時(shí)的I/O操作(bgsave,expired key清理之類的操作)和其他任務(wù)是在side線程(主線程fork出來的子線程)中執(zhí)行的。
現(xiàn)在也可以在不同的I/O線程中處理Redis客戶端socket讀和寫。由于寫入(指socket寫入)速度非常慢,Redis用戶通常使用pipelining來提高Redis在單核上的性能,并使用多個(gè)實(shí)例的方式來擴(kuò)容。使用I/O線程可以很容易地提升Redis在socket讀寫上的性能,而無需求助于管道或?qū)嵗姆制?/div>
Redis 6.0中默認(rèn)情況下多線程被是被禁用的,建議至少有4個(gè)或更多核的機(jī)器中啟用多線程,且至少留下1備用核。使用超過8個(gè)線程不大可能有太大幫助。
由于Redis實(shí)例能夠充分利用CPU資源(譯者注:意思是即便是單線程下,CPU并不是瓶頸),多線程I/O只有在你確實(shí)有性能問題的情況下才能提升運(yùn)行效率,否則就沒有必要使用這個(gè)特性。
如果你有一個(gè)4核的服務(wù)器,嘗試使用2或3個(gè)I/O線程,如果是8核,嘗試使用6個(gè)線程。要啟用多線程I/O,請使用以下配置參數(shù):io-threads 4
設(shè)置io-threads為1會(huì)像傳統(tǒng)的redis一樣啟用單個(gè)主線程,當(dāng)I/O threads被啟用之后,僅僅支持寫操作(譯者注:指的是socket的寫操作,socket的讀操作使用多路io復(fù)用技術(shù),本身也不是瓶頸)即IO線程調(diào)用syscall并將客戶端緩沖區(qū)傳輸?shù)絪ocket。但是,也可以啟用讀寫線程,使用以下配置指令進(jìn)行協(xié)議解析,方法是將其設(shè)置為“yes”:io-threads-do-reads no
通常情況下,threading reads線程對(duì)性能的提升幫助并不大
注1:此配置指令不能在運(yùn)行時(shí)通過配置集進(jìn)行更改,只能在修改配置文件之后重啟。啟用SSL時(shí),當(dāng)前此特性也無效。
注2:如果你想用Redis-benchmark測試Redis的性能,務(wù)必以threaded mode的方式運(yùn)行Redis-benchmark,使用--threads選項(xiàng)來匹配Redis線程的數(shù)量,否則無法觀察到測試結(jié)果的提升。
 

測試結(jié)果及分析

如下是不同線程requests per second測試結(jié)果的橫向?qū)Ρ?,分別在不同的線程下,進(jìn)行100W次get/set請求的QPS結(jié)果

從中可以看到:
1,1個(gè)線程,也就是傳統(tǒng)的單線程模式,get 操作的QPS可以達(dá)到9W左右
2,2個(gè)線程,get 操作的QPS可以達(dá)到18W左右,相比單線程有100%+的提升
3,4個(gè)線程,與2線程相比,會(huì)有30%左右的提高,但是已經(jīng)沒有從1個(gè)線程到2個(gè)線程翻一倍的提升了
4,6個(gè)線程,與4線程相比,沒有明顯的提升,對(duì)于SET操作,QPS從4線程到6線程,8線程開始沒有出現(xiàn)明顯的差異,都在23W~24W之間
5,8個(gè)線程,與4線程和6線程相比,8線程下大概有10%的提升
6,10個(gè)線程,相比效率最高的8線程,此時(shí)性能反倒是開始下降了,與4線程或者6線程的效率相當(dāng)
因此在本機(jī)環(huán)境下,io-threads 4設(shè)置成2或者4個(gè)都o(jì)k,最多不超過8個(gè),超出后性能反而會(huì)下降,同時(shí)也不能超出cpu的個(gè)數(shù),正如配置文件中注釋中說的,至少要留出一個(gè)CPU。
 
如下是不同線程下10測試結(jié)果中GET和SET的requests per second 平均值對(duì)比:

 

關(guān)于io-threads-do-reads參數(shù)

上文提到過io-threads-do-reads這個(gè)參數(shù),它是決定socket讀操作是否開啟多線程,Redis的socket讀操作采用多路IO復(fù)用技術(shù),本身不會(huì)成為瓶頸,所以這個(gè)參數(shù)對(duì)多線程下測試影響比較小。依舊參考這里的這個(gè)圖 侵刪,這個(gè)io-threads-do-reads在筆者理解起來,就是socket讀的線程,是否開啟影響不大。
Redis.conf中關(guān)于此參數(shù)的注釋為: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.
以下就該參數(shù)啟用與否進(jìn)行一次對(duì)比測試

參考如下截圖,在開啟了兩個(gè)線程的情況下,分別開啟和禁用io-threads-do-reads,從整體上看,性能影響差別很小。當(dāng)然專業(yè)的大神可以從源碼的角度去分析。
io-threads為2,且啟動(dòng)io-threads-do-reads

io-threads為2,且禁動(dòng)io-threads-do-reads

 

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

redis集群有兩種模式:sentinel和cluster,這里暫時(shí)先不提sentinel,來思考多線程版本的Redis和Redis Cluster的選擇問題。
Redis的Cluster解決的就是擴(kuò)展性和單節(jié)點(diǎn)單線程的阻塞隱患,如果Redis支持了多線程(目前多線程的Redis最對(duì)不建議超出8個(gè)線程),在不考慮單個(gè)節(jié)點(diǎn)網(wǎng)卡瓶頸的情況下,其實(shí)這兩個(gè)問題都已經(jīng)解決了,單節(jié)點(diǎn)可以支持多線程和充分利用多核CPU,單節(jié)點(diǎn)可以支持到25W QPS,還要啥自行車?
同時(shí)要考慮到Redis cluster的痛點(diǎn):
1,不支持multiple/pipline操作(第三方插件也不一定穩(wěn)定)。
2,cluster中每個(gè)主節(jié)點(diǎn)要掛一個(gè)從節(jié)點(diǎn),不管這個(gè)節(jié)點(diǎn)是不是獨(dú)立的物理節(jié)點(diǎn)還是單機(jī)多實(shí)例中的一個(gè)節(jié)點(diǎn),終究是增加了維護(hù)成本。
3,只能使用一個(gè)數(shù)據(jù)庫
4,集群自身擴(kuò)容、縮容帶來的一系列slot遷移等性能問題,以及集群的管理問題
這些所謂的痛點(diǎn)也不復(fù)存在了,所以這里就面臨一個(gè)重新選擇的問題:是使用多線程版本的Redis,還是使用Redis cluster?這是一個(gè)需要思考的問題。

 

疑問

關(guān)于redis-benchmark 測試時(shí)候 ./bin/redis-benchmark -d 128  -c 200 -n 1000000 -t set -q --threads 2,涉及的兩個(gè)參數(shù)-c和--threads,個(gè)人是不太理解的
-c的解釋是:指定并發(fā)連接數(shù)
--threads是redis-benchmark的線程數(shù)?
對(duì)此去跟老錢(素未謀面,感謝)求證了一下,說該參數(shù)是redis-benchmark客戶端的epoll,服務(wù)器端的多路IO復(fù)用原理已經(jīng)看得我七葷八素了,客戶端也是帶epoll的,還是不太理解這兩者之間的關(guān)系。

 

redis-benchmark測試現(xiàn)場

如下是redis-benchmark測試過程中部分截圖

圖太多了,就不一一貼上來了。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多