作者:在江湖中coding
https://juejin.im/post/5e6097846fb9a07c9f3fe744
性能測驗報告
查看了下阿里 Redis 的性能測驗報告如下,能夠達到數十萬、百萬級別的 QPS(暫時忽略阿里對 Redis 所做的優化),我們從 Redis 的設計和實作來分析一下 Redis 是怎么做的,

Redis的設計與實作
其實 Redis 主要是通過三個方面來滿足這樣高效吞吐量的性能需求
-
高效的資料結構
-
多路復用 IO 模型
-
事件機制
1、高效的資料結構
Redis 支持的幾種高效的資料結構 string(字串)、hash(哈希)、list(串列)、set(集合)、zset(有序集合)
以上幾種對外暴露的資料結構它們的底層編碼方式都是做了不同的優化的,不細說了,不是本文重點,
2、多路復用 IO 模型
假設某一時刻與 Redis 服務器建立了 1 萬個長連接,對于阻塞式 IO 的做法就是,對每一條連接都建立一個執行緒來處理,那么就需要 1萬個執行緒,同時根據我們的經驗對于 IO 密集型的操作我們一般設定,執行緒數 = 2 * CPU 數量 + 1,對于 CPU 密集型的操作一般設定執行緒 = CPU 數量 + 1,
當然各種書籍或者網上也有一個詳細的計算公式可以算出更加合適準確的執行緒數量,但是得到的結果往往是一個比較小的值,像阻塞式 IO 這也動則創建成千上萬的執行緒,系統是無法承載這樣的負荷的更加彈不上高效的吞吐量和服務了,
而多路復用 IO 模型的做法是,用一個執行緒將這一萬個建立成功的鏈接陸續的放入 event_poll,event_poll 會為這一萬個長連接注冊回呼函式,當某一個長連接準備就緒后(建立建立成功、資料讀取完成等),就會通過回呼函式寫入到 event_poll 的就緒佇列 rdlist 中,這樣這個單執行緒就可以通過讀取 rdlist 獲取到需要的資料,為什么 Redis 是單執行緒,點擊這里看下這篇文章,
另外,大家可以關注微信公眾號:Java技術堆疊,在后臺回復:redis,可以獲取我整理的 N 篇 Redis 教程,都是干貨,
需要注意的是,除了異步 IO 外,其它的 I/O 模型其實都可以歸類為阻塞式 I/O 模型,不同的是像阻塞式 I/O 模型在第一階段讀取資料的時候,如果此時資料未準備就緒需要阻塞,在第二階段資料準備就緒后需要將資料從內核態復制到用戶態這一步也是阻塞的,而多路復用 IO 模型在第一階段是不阻塞的,只會在第二階段阻塞,
通過這種方式,就可以用 1 個或者幾個執行緒來處理大量的連接了,極大的提升了吐吞量

3、事件機制
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 作業,由檔案事件分派器從中獲取事件交于對應的處理器去執行,當某個事件執行完成后檔案事件分派器才會從佇列中獲取下一個事件進行處理,
可以類比在 netty 中,我們一般會設定 bossGroup 和 workerGroup 默認情況下 bossGroup 為 1,workerGroup = 2 * cpu 數量,這樣可以由多個執行緒來處理讀寫就緒的事件,但是其中不能有比較耗時的操作如果有的話需要將其放入執行緒池中,不然會降低其吐吞量,在 Redis 中我們可以看做這二者的值都是 1,
為什么說存盤的值不宜過大
比如一個 string key = a,存盤了 500MB,首先讀取事件壓入佇列中,檔案事件分派器從中獲取到后,交于命令請求處理器處理,此處就涉及到從磁盤中加載 500MB,
比如是普通的 SSD 硬碟,讀取速度 200MB/S,那么需要 2.5S 的讀取時間,在記憶體中讀取資料比較快比如 DDR4 中 50G/秒,讀取 500MB 需要 100 毫秒左右,
執行緒的庫一般默認 10 毫秒就算慢查詢了,大部分的指令執行時間都是微秒級別,此時其它 socket 所有的請求都將處于等待程序中,就會導致阻塞了 100 毫秒,同時又會占用較大的帶寬導致吞吐量進一步下降,
關注公眾號Java技術堆疊回復"面試"獲取我整理的2020最全面試題及答案,
推薦去我的博客閱讀更多:
1.Java JVM、集合、多執行緒、新特性系列教程
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4.Java、后端、架構、阿里巴巴等大廠最新面試題
覺得不錯,別忘了點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/158870.html
標籤:Java
上一篇:驗證碼生成,包含有效期
