程序員過關斬將--請不要誤會Redis 6.0 的多線程
你對redis的單線程是不是有點誤會?
你對redis 6.0的多線程是不是也有點誤會?
redis多線程一定可以提高性能嗎?
redis官方剛剛發布的6.0版本已經掀起了業界一陣熱波,在這個版本中新加了很多新特性,如果你打開redis的官網,可以看到6.0現在已經是穩定版本了。
image
redis現在已經成為了面試官必問的知識點之一,尤其是當新版本加入了“多線程”概念之后,面試題又是增加了一道難題。
redis單線程
redis在6.0之前的版本,很多同學認為是單線程,其實這個說法嚴格意義上來講不太準確。“單線程”是指客戶端發送的命令的接收,解析,執行,結果返回這個過程是由一個線程處理,這個線程就是主線程,這也是redis素有“單線程”定義的來源。
但是,redis也有其他后臺線程在處理其他操作,比如那些比較慢,不太適合放主線程執行的操作,例如大key的刪除,AOF的重寫,快照的生成,無用連接的釋放等。
單線程機制使得redis內部代碼實現的復雜度和難度大大降低,請求都是按照串行化的順序來依次執行,這大大降低了由于線程切換帶來的資源消耗,而且又可以避免鎖帶來的一系列問題。所以平時開發使用redis的時候,我們可以實現分布式鎖等一系列騷操作,這和Actor模型中,單個Actor的行為十分類似,串行的操作使我們可以擺脫多線程帶來的一系列執行順序的痛苦。
至于redis為什么不使用多線程呢?官方曾經做過類似問題的回復,大體意思是,redis由于cpu成為瓶頸的幾率幾乎不存在,redis的性能主要受限于內存和網絡,當然,我認為這個解釋并非是絕對的。當redis開啟持久化之后,吞吐量會大幅度下降,除非非常必要,不然在很多業務場景下盡量不要開啟redis的持久化。
redis單線程瓶頸
redis將所有數據放在內存中,在充分利用pipelining技術的情況下,QPS可達百萬,這個量級對于普通的中小公司已經足以,就算是再大一點的請求量,利用redis集群方式也能抗住。但是redis集群也是有缺陷的:
- redis集群需要更多的服務器資源來支撐,這無疑加大了公司的支出費用和資源成本。
- redis集群雖然可以通過增加副本的方式來解決熱點key的讀問題,但是熱點key的寫依然比較棘手
從redis自身的處理請求過程來看,對網絡數據的讀寫以及對命令的解析占用了大部分cpu時間,瓶頸在于網絡IO的消耗以及對CPU不能充分的利用。而要突破這個瓶頸呢,一般有兩種解決方案:
網絡請求的處理不再依靠內核,而在用戶態處理。但是這種方式需要修改內核網絡棧的實現方式,這會帶來很多開發工作量,而且設計到核心代碼修改,可能會引入新的bug,導致系統不穩定
利用多線程優勢,充分利用服務器多核的特性,采用多個IO線程來并行處理網絡請求。
很顯然,redis6.0采用的是多線程的方式。
無論是針對redis集群,還是針對單體架構,提高單機redis的處理速度和吞吐量目前看百利而無一害。
redis多線程
無論redis采用單線程還是多線程,其實每個請求的整體處理流程是一致的。
image
在整個流程中,讀取解析redis客戶端命令和返回客戶端結果兩個步驟分別對應網絡數據的讀取和寫入,這兩個步驟對于redis來說,占用了大部分cpu時間,所以redis6.0多線程機制是針對這兩個步驟的。
為了直觀的更好了解整個流程,一般分為以下幾個步驟:
- 當客戶端有新的socket連接時,主線程會負責接收連接,并把socket放入全局的等待隊列中,當主線程處理完讀事件之后,通過輪訓的方式將這些連接分配給IO線程。
- 主線程會一直阻塞到IO線程讀取Socket并解析完畢,這個解析過程是多個IO線程并行處理的,所以會很快。
- 等到IO線程解析命令完成,主線程以單線程的方式來執行這些命令,并把執行結果寫入緩沖區,然后阻塞的等待IO線程回寫socket數據
- IO線程讀取緩沖區結果數據,把這些數據回寫socket,返回給客戶端。這個過程也是多個IO線程并行處理的,所以也會很快。
- 當所有的IO線程回寫socket完畢,主線程回清空全局隊列,等待下次新的請求到來。
image
從以上步驟可以看出,IO線程只涉及到socket的讀和寫,而實際命令的執行還是主線程以順序化的方式來執行,這不僅僅是利用多線程的優勢,同時又保留了單線程的優勢,在命令執行上不會產生多線程的一系列問題,比如加鎖帶來的耗時,控制 key、lua、事務,LPUSH/LPOP 等等的并發及線程安全問題。
“其實關于上面所說,我有一點沒想明白:假如有兩個socket,在單線程的時候,主線程可以保證優先到來的socket命令數據優先被執行,但是在加入了多個IO線程并行解析過程之后,本來先接收的命令是否可以保證優先執行呢?希望大佬在評論區給予指點
redis 6.0默認是禁用多線程機制的,如果需要開啟,請修改redis.conf:
- io-threads-do-reads yes
開啟時候還要設置線程數,否則多線程機制是不生效的。至于設置多少個線程,官方有一個建議:4核的機器建議設置為2或3個線程,8核的建議設置為6個線程,線程數一定要小于機器核數。還需要注意的是,線程數并不是越大越好,官方認為超過了8個基本就沒什么意義了。
redis6.0多線程測試
Redis 作者 antirez 在 RedisConf 2019 分享時曾提到:Redis 6 引入的多線程 IO 特性對性能提升至少是一倍以上。國內也有大牛曾使用 unstable 版本在阿里云 esc 進行過測試,GET/SET 命令在 4 線程 IO 時性能相比單線程是幾乎是翻倍了。
“Redis Server:阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ, 8G 內存,主機型號 ecs.ic5.2xlarge Redis Benchmark Client:阿里云 Ubuntu 18.04,8 2.5 GHZ CPU, 8G 內存,主機型號 ecs.ic5.2xlarge
image
image
“這些性能驗證的測試并沒有針對嚴謹的延時控制和不同并發的場景進行壓測。數據僅供驗證參考而不能作為線上指標。
“如果開啟多線程,至少要4核的機器,且Redis實例已經占用相當大的CPU耗時的時候才建議采用,否則使用多線程沒有意義。所以估計80%的公司開發人員看看就好。
寫在最后
redis6.0利用多線程的優勢很好的解決了當前redis的瓶頸問題,同時又保留了核心命令執行過程單線程機制。不過將來單線程的命令執行機制會不會是redis的瓶頸呢?這個留給大佬們在評論區!!
最后提出一個我的疑問:redis6.0在啟用了多線程機制之后,那先后到達的socket數據,在命令執行的時候是否有可能不是按照數據到達的順序呢?redis6.0 是否有機制來保證這個順序呢?請大佬在留言區賜教!!
本文轉載自微信公眾號「架構師修行之路」,可以通過以下二維碼關注。轉載本文請聯系架構師修行之路公眾號。