程序員過關斬將--請不要誤會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多線程機制是針對這兩個步驟的。

爲了直觀的更好了解整個流程,一般分爲以下幾個步驟:

  1. 當客戶端有新的socket連接時,主線程會負責接收連接,並把socket放入全局的等待隊列中,當主線程處理完讀事件之後,通過輪訓的方式將這些連接分配給IO線程。

  2. 主線程會一直阻塞到IO線程讀取Socket並解析完畢,這個解析過程是多個IO線程並行處理的,所以會很快。

  3. 等到IO線程解析命令完成,主線程以單線程的方式來執行這些命令,並把執行結果寫入緩衝區,然後阻塞的等待IO線程回寫socket數據

  4. IO線程讀取緩衝區結果數據,把這些數據回寫socket,返回給客戶端。這個過程也是多個IO線程並行處理的,所以也會很快。

  5. 當所有的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 是否有機制來保證這個順序呢?請大佬在留言區賜教!!

程序員過關斬將--論系統設計的高可擴展性


程序員過關斬將--從未停止過的系統架構設計步伐


程序員過關斬將--真的可以用版本號的方式來保證MQ消費消息的冪等性?



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章