性能測試報告
查看了下阿里 Redis 的性能測試報告如下,能夠達到數十萬、百萬級別的 QPS,就以 4GB 集群版本,2 個節點,2 核,qps 基本上就已經達到 16 萬。
Redis 的設計與實現
其實 Redis 主要是通過三個方面來滿足這樣高效吞吐量的性能需求
- 高效的數據結構
- 多路復用 IO 模型
- 事件機制
高效的數據結構
Redis 支持的幾種高效的數據結構 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)
以上幾種對外暴露的數據結構它們的底層編碼方式都是做了不同的優化的,不細說了,不是本文重點。具體細節可查看我的兩篇博客:redis-01 常用五種類型與應用場景 和 redis-02 五種類型底層數據結構
多路復用 IO 模型
假設某一時刻與 Redis 服務器建立了 1 萬個長連接,對於阻塞式 IO 的做法就是,對每一條連接都建立一個線程來處理,那么就需要 1萬個線程,同時根據我們的經驗對於 IO 密集型的操作我們一般設置,線程數 = 2 * CPU 數量 + 1,對於 CPU 密集型的操作一般設置 線程數 = CPU 數量 + 1,當然各種書籍或者網上也有一個詳細的計算公式可以算出更加合適准確的線程數量,但是得到的結果往往是一個比較小的值,像阻塞式 IO 這種動輒創建成千上萬的線程,系統是無法承載這樣的負荷的,更加談不上高效的吞吐量和服務了。
而多路復用 IO 模型的做法是,用一個線程將這一萬個建立成功的鏈接陸續的放入 event_poll,event_poll 會為這一萬個長連接注冊回調函數,當某一個長連接准備就緒后(建立成功、數據讀取完成等),就會通過回調函數寫入到 event_poll 的就緒隊列 rdlist 中,這樣這個單線程就可以通過讀取 rdlist 獲取到需要的數據。
需要注意的是,除了異步 IO 外,其它的 I/O 模型其實都可以歸類為阻塞式 I/O 模型,不同的是像阻塞式 I/O 模型在第一階段讀取數據的時候,如果此時數據未准備就緒需要阻塞,在第二階段數據准備就緒后需要將數據從內核態復制到用戶態這一步也是阻塞的。而多路復用 IO 模型在第一階段是不阻塞的,只會在第二階段阻塞
具體針對多路復用 epoll 和 redis 請查看我的博客:redis-10 redis和 I/O多路復用
事件機制
redis 客戶端與 redis 服務端建立連接,發送命令,redis 服務器響應命令都是需要通過事件機制來做的,
- 首先 redis 服務器運行,監聽套接字的 AE_READABLE 事件處於監聽的狀態下,此時 連接應答處理器 工作,
- 客戶端 與 redis 服務器發起建立連接,監聽套接字產生 AE_READABLE 事件,當 IO 多路復用程序監聽到其准備就緒后,將該事件壓入隊列中,由 文件事件分派器 獲取隊列中的事件交於 連接應答處理器工作處理,應答客戶端建立連接成功,同時將客戶端 socket 的 AE_READABLE 事件壓入隊列由 文件事件分派器 獲取隊列中的事件交 命令請求處理器關聯
- 客戶端發送 set key value 請求,客戶端 socket 的 AE_READABLE 事件,當 IO 多路復用程序監聽到其准備就緒后,將該事件壓入隊列中,由 文件事件分派器 獲取隊列中的事件交於 命令請求處理器關聯 處理
- 命令請求處理器關聯 處理完成后,需要響應客戶端操作完成,此時將產生 socket 的 AE_WRITEABLE 事件壓入隊列,由 文件事件分派器 獲取隊列中的事件交於 命令恢復處理器 處理,返回操作結果,完成后將解除 AE_WRITEABLE 事件 與 命令恢復處理器 的關聯
reactor模式
大體上可以說 Redis 的工作模式是:reactor 模式 + 一個隊列,用一個 serverAccept 線程來處理建立請求的鏈接,並且通過 IO 多路復用模型,讓內核來監聽這些 socket,一旦某些 socket 的讀寫事件准備就緒后就對應的事件壓入隊列中,然后 worker 工作,由 文件事件分派器 從中獲取事件交於對應的 處理器去執行,當某個事件執行完成后 文件事件分派器 才會從隊列中獲取下一個事件進行處理。
