Redis系列1:深刻理解高效能Redis的本質
Redis系列2:資料持久化提高可用性
Redis系列3:高可用之主從架構
Redis系列4:高可用之Sentinel(哨兵模式)
Redis系列5:深入分析Cluster 叢集模式
我們在第一篇《Redis系列1:深刻理解高效能Redis的本質》中就已經提到了,Redis 的網路 IO 以及鍵值對指令讀寫是由單個執行緒來執行的,避免了不必要的contextswitch和資源競爭,對於效能提升有很大的幫助。
而到了2020年的5月份,Redis官方 推出了 令人矚目的 Redis 6.0,提出很多新特性,包含 多執行緒網路IO 的概念,如下:
新特性 | 核心優化 | 應用優化 | 其他 |
---|---|---|---|
ACL細粒度許可權管控(包括ACL LOG) | 過期Key回收優化,增加設定引數 | 新版本Module API | 全面支援SSL協定、並新增TSL協定 |
使用者端快取(Client side caching) | Resp3協定,相容Resp2,更加簡單、高效 | disque訊息佇列模組 | Redis-benchmark支援叢集模式 |
多執行緒處理網路 IO(Threaded I/O) | 優化了INFO命令,效率更高 | 新增設定,支援Del命令如unlink執行 | Systemd支援重寫 |
Redis叢集代理(Cluster proxy) | 優化阻塞命令,複雜度從O(n)到O(1) | XINFO STREAM FULL流命令 | 新增設定引數來刪除用於在非永續性範例中進行復制的RDB檔案 |
支援linux/bsd系統的CPU和執行緒(包括子執行緒如aof、dbIO執行緒)親和力系結 | RDB載入速度優化 | CLIENT KILL USER username命令 | 無磁碟複製副本(Diskless replication on replicas),從測試版優化,目前無磁碟複製在load rdb仍是測試版。 |
叢集Slots命令優化 | |||
Psync2優化,修復了5.0的鏈式複製不一致問題。 | |||
defrag優化,從試驗版到正式版 |
這其中比較引人注意的就是 Threaded I/O 和 Client side caching 這兩項了。
這時候我們不免疑問,為什麼6.0之前是單執行緒模式的,是基於什麼考慮。而現在為什麼又要優化成 多執行緒網路IO模式,主要解決了哪些問題 ,帶來了那些變化?
這一篇咱們就詳細就來聊下這個 Threaded I/O。
瞭解單執行緒模式之前,大家可以先回顧一下Redis系列第一篇 Redis系列1:深刻理解高效能Redis的本質 。
就會明白,Redis所謂的單執行緒並不是所有工作都是隻有一個執行緒在執行,而是指Redis的網路IO和鍵值對讀寫是由一個執行緒來完成的,Redis在處理使用者端的請求時包括獲取 (socket 讀)、解析、執行、內容返回 (socket 寫) 等都由一個順序序列的主執行緒處理。
這就是所謂的「單執行緒」。這也是Redis對外提供鍵值儲存服務的主要流程。
由於Redis在處理命令的時候是單執行緒作業的,所以會有一個Socket佇列,每一個到達的伺服器端命令來了之後都不會馬上被執行,而是進入佇列,然後被執行緒的事件分發器逐個執行。如下圖:
至於Redis的其他功能, 比如持久化、非同步刪除、叢集資料同步等等,其實是由額外的執行緒執行的。 可以這麼說,Redis工作執行緒是單執行緒的。但是在4.0之後,對於整個Redis服務來說,還是多執行緒運作的。
那麼問題來了,6.0之前為什麼要使用單執行緒,通過 Redis官方的檔案 ,我們看到他們有給出了說明:
可以看出,Redis對CPU計算力的要求並不迫切,相反單執行緒機制讓 Redis 內部實現的複雜度大大降低,同時降低了因為上下文切換和資源競爭造成的效能損耗。那既然單執行緒這麼好用,為什麼要引入多執行緒模式。
我們知道, 近年來底層網路硬體效能越來越好,Redis 的效能瓶頸逐漸體現在網路 I/O 的讀寫上,單個執行緒處理網路 I/O 讀寫的速度跟不上底層網路硬體執行的速度。
從下圖我們可以看到,Redis 在處理網路資料時,呼叫 epoll 的過程是阻塞的,這個過程會阻塞執行緒。如果並行量很高,達到萬級別的 QPS,就會形成瓶頸,影響整體吞吐能力。
既然讀寫網路的 read/write 系統呼叫佔用了Redis 執行期間大部分CPU 時間,那麼要想真正做到提速,必須改善網路IO效能。我們可以從這兩個方面來優化:
協定棧優化的這種方式跟 Redis 關係不大,所以最便捷高效的方式就是支援多執行緒。總結起來,redis支援多執行緒就是以下兩個原因:
6.0版本優化之後,主執行緒和多執行緒網路IO的執行流程如下:
具體步驟如下:
本質上是將主執行緒 IO 讀寫的這個操作 獨立出來,單獨交給一個I/O執行緒組處理。
這樣多個 socket 讀寫可以並行執行,整體效率也就提高了。同時注意 Redis 命令還是主執行緒序列執行。
Redis6.0的多執行緒預設是禁用的,只使用主執行緒。如需開啟需要修改redis.conf組態檔:
# io-threads-do-reads no
io-threads-do-reads yes
開啟多執行緒後,還需要設定執行緒數,否則是不生效的。同樣修改redis.conf組態檔。
關於執行緒數的設定,官方有一個建議:4 核的機器建議設定為 2 或 3 個執行緒,8核的建議設定為 6 個執行緒,執行緒數一定要小於機器核數。
執行緒數並不是越大越好,官方認為超過了 8 個就很難繼續提效了,沒什麼意義。
# 假設你的CPU核數是8核,儘量設定成 5~6
io-threads 5