追求性能極致:Redis6.0的多線程模型

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 的概念,如下:

追求性能極致:Redis6.0的多線程模型

文章插圖
新特性內核優化應用優化其他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 。
6.0之前的單線程模式了解單線程模式之前,大家可以先回顧一下Redis系列第一篇 Redis系列1:深刻理解高性能Redis的本質。就會明白,Redis所謂的單線程并不是所有工作都是只有一個線程在執行,而是指Redis的網絡IO和鍵值對讀寫是由一個線程來完成的,Redis在處理客戶端的請求時包括獲取 (socket 讀)、解析、執行、內容返回 (socket 寫) 等都由一個順序串行的主線程處理 。這就是所謂的“單線程” 。這也是Redis對外提供鍵值存儲服務的主要流程 。由于Redis在處理命令的時候是單線程作業的,所以會有一個Socket隊列 , 每一個到達的服務端命令來了之后都不會馬上被執行,而是進入隊列 , 然后被線程的事件分發器逐個執行 。如下圖:
追求性能極致:Redis6.0的多線程模型

文章插圖
至于Redis的其他功能, 比如持久化、異步刪除、集群數據同步等等,其實是由額外的線程執行的 。可以這么說,Redis工作線程是單線程的 。但是在4.0之后 , 對于整個Redis服務來說,還是多線程運作的 。
那么問題來了,6.0之前為什么要使用單線程,通過 Redis官方的文檔,我們看到他們有給出了說明:
追求性能極致:Redis6.0的多線程模型

文章插圖
  • 在使用 Redis 時,Redis 主要受限是在內存和網絡上,CPU 幾乎沒有性能瓶頸的問題 。
  • 以Linux 系統為例子,在Linux系統上Redis 通過 pipelining 可以處理 100w 個請求每秒 , 而應用程序的計算復雜度主要是 O(N) 或 O(log(N)) ,不會消耗太多 CPU 。
  • 使用了單線程后,提高了可維護性 。多線程模型在某些方面表現優異,卻增加了程序執行順序的不確定性,并且帶來了并發讀寫的一系列問題 , 增加了系統復雜度 。同時因為線程切換、加解鎖 , 甚至死鎖,造成一定的性能損耗 。
  • Redis 通過 AE 事件模型以及 IO 多路復用等技術,擁有超高的處理性能 , 因此沒有使用多線程的必要 。
可以看出,Redis對CPU計算力的要求并不迫切,相反單線程機制讓 Redis 內部實現的復雜度大大降低,同時降低了因為上下文切換和資源競爭造成的性能損耗 。那既然單線程這么好用 , 為什么要引入多線程模式 。
6.0之后的多線程主要解決什么問題我們知道 ,  近年來底層網絡硬件性能越來越好,Redis 的性能瓶頸逐漸體現在網絡 I/O 的讀寫上,單個線程處理網絡 I/O 讀寫的速度跟不上底層網絡硬件執行的速度 。從下圖我們可以看到,Redis 在處理網絡數據時 , 調用 epoll 的過程是阻塞的,這個過程會阻塞線程 。如果并發量很高,達到萬級別的 QPS , 就會形成瓶頸,影響整體吞吐能力 。

推薦閱讀