Redis爲什麼這麼快?
序言
作爲企業級的存儲組件, Redis被用到很多的業務場景。
Redis經常被用作做緩存, 一致性要求不高場景,還可以當做存儲使用。
另外, Redis還提供了消息訂閱、事務、索引等特性。
我們還可以利用集羣特性搭建分佈式存儲服務,實現非強一致性的分佈式鎖服務。
Redis用到上述場景, 都有一個共同的優勢, 就是處理速度快(高性能)。
面試中,面試官經常會問到單線程的Redis爲什麼這麼快?
爲了闡明這個問題, 下面將分三部分講解:
(1) 第一部分: Redis到底有多快
(2) 第二部分: 詳細講解Redis高性能原因
(3) 第三部分: 影響Redis性能的因素
👁 關注微信公衆號:非典型理科男 回覆:redis獲取redis三本經典著作
Redis到底有多快
要了解Reids的到底有多麼快, 首先需要有相應的評估工具。 其次,需要Redis
在一些平臺經驗數據,來評估Redis性能數量級。 幸運的是Redis提供了這樣的工具,並給出了常用的硬件平臺一些經驗數據。
下面篇幅比較長,核心觀點如下:
- 可以使用redis-benchmark對Redis的性能進行評估,命令行提供了普通/流水線方式、不同壓力評估特定命令的性能的功能。
- redis性能卓越,作爲key-value系統最大負載數量級爲10W/s, set和get耗時數量級爲10ms和5ms。使用流水線的方式可以提升redis操作的性能。
不關心具體數據的小夥伴,可以直接跳到第二部分,直接瞭解redis性能卓越的原因。
Redis性能評估工具
Redis包含的redis-benchmark實用程序可模擬N個客戶端同時發送M個總查詢的運行命令(類似於Apache的ab實用程序)。可以使用redis-benchmark對redis的性能進行評估。
支持以下選項:
Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]
-h <hostname> 服務器 hostname (默認 127.0.0.1)
-p <port> 服務器 port (默認 6379)
-s <socket> 服務器 socket (覆蓋host和port)
-a <password> 服務器鑑權密鑰
-c <clients> 啓動的客戶端數量(並行度) (默認 50)
-n <requests> 總請求量(默認 100000)
-d <size> GET和SET請求數據大小(默認 2個字節)
--dbnum <db> 選擇的db編號 (默認 0)
-k <boolean> 1=keep alive 0=reconnect (默認 1)
-r <keyspacelen> 在SET/GET/INCR使用隨機的key值, 在SADD使用隨機的va
-P <numreq> 一個Pipeline包含的請求數. 默認值1 (不使用Pipeline).
-q 安靜模式. 僅僅展示QPS值
--csv 以csv格式輸出
-l 生成循環 永久執行測試
-t <tests> 制定測試命令的命令, 命令列表以逗號分隔
-I Idle模式,僅打開N個idle連接並等待
啓動基準之前,您需要具有運行中的Redis實例。我在自己工作的筆記本上, 使用默認參數跑了一個例子:
D:\data\soft\redis-windows>redis-benchmark.exe
.....
====== SET ======
100000 requests completed in 0.81 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.90% <= 1 milliseconds
99.93% <= 2 milliseconds
99.95% <= 78 milliseconds
99.96% <= 79 milliseconds
100.00% <= 79 milliseconds
123609.39 requests per second
====== GET ======
100000 requests completed in 0.70 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
142045.45 requests per second
====== INCR ======
100000 requests completed in 0.71 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.95% <= 1 milliseconds
99.95% <= 2 milliseconds
100.00% <= 2 milliseconds
140252.45 requests per second
.....
上面的例子中,截取了SET/GET/INCR的測試結果。
測試結果包括測試的環境參數(請求量、client數量、有效載荷)以及請求耗時的TP值。
redis-benchmark默認使用10萬請求量, 50個clinet,有效載荷爲3字節進行測試。
返回結果可以看出SET/GET/INCR命令在10萬的請求量下,總的請求耗時均低於0.1s以內。 以QPS=10W爲例, 計算出來的平均耗時爲2ms左右(1/(10W/50))。
Reids基準測試經驗數據
Redis的性能跟很多因素相關, 在第三部分會詳細介紹。比如客戶端網絡狀況、是否使用流水,鏈接的客戶端。爲了說明Redis到底有多快,我們使用Reidis官網使用redis-benchmark測試的一組數據。
警告:請注意,以下大多數基準測試已有數年曆史,並且是與今天的標準相比使用舊硬件獲得的。該頁面應該進行更新,但是在很多情況下,使用硬硬件狀態,您會期望看到的數字是此數字的兩倍。此外,在許多工作負載中,Redis 4.0比2.6快
硬件環境和軟件配置
測試是由50個同時執行200萬個請求的客戶端完成的。
所有測試在Redis 2.6.14上運行。
使用迴環地址(127.0.0.1)執行了測試。
使用一百萬個鍵的鍵空間執行測試。
使用和不使用流水線(16條命令流水線)執行測試。
Intel(R) Xeon(R) CPU E5520 @ 2.27GHz
Redis系統負載
- 不使用流水線測試結果
$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q
SET: 122556.53 requests per second
GET: 123601.76 requests per second
LPUSH: 136752.14 requests per second
LPOP: 132424.03 requests per second
- 使用流水線測試結果
$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q -P 16
SET: 195503.42 requests per second
GET: 250187.64 requests per second
LPUSH: 230547.55 requests per second
LPOP: 250815.16 requests per second
從以上可以看出Redis作爲key-value系統讀寫負載大致在10W+QPS, 使用流水線技術能夠顯著提升讀寫性能。
耗時情況
- 不使用流水線測試結果
$ redis-benchmark -n 100000
====== SET ======
100007 requests completed in 0.88 seconds
50 parallel clients
3 bytes payload
keep alive: 1
58.50% <= 0 milliseconds
99.17% <= 1 milliseconds
99.58% <= 2 milliseconds
99.85% <= 3 milliseconds
99.90% <= 6 milliseconds
100.00% <= 9 milliseconds
114293.71 requests per second
====== GET ======
100000 requests completed in 1.23 seconds
50 parallel clients
3 bytes payload
keep alive: 1
43.12% <= 0 milliseconds
96.82% <= 1 milliseconds
98.62% <= 2 milliseconds
100.00% <= 3 milliseconds
81234.77 requests per second
....
所有set操作均在10ms內完成, get操作均在5ms以下。
Redis爲什麼那麼快
Redis是一個單線程應用,所說的單線程指的是Redis使用單個線程處理客戶端的請求。
雖然Redis是單線程的應用,但是即便不通過部署多個Redis實例和集羣的方式提升系統吞吐, 從官網給出的數據可以看出,Redis處理速度非常快。
Redis性能非常高的原因主要有以下幾點:
- 內存存儲:Redis是使用內存(in-memeroy)存儲,沒有磁盤IO上的開銷
- 單線程實現:Redis使用單個線程處理請求,避免了多個線程之間線程切換和鎖資源爭用的開銷
- 非阻塞IO:Redis使用多路複用IO技術,在poll,epool,kqueue選擇最優IO實現
- 優化的數據結構:Redis有諸多可以直接應用的優化數據結構的實現,應用層可以直接使用原生的數據結構提升性能
下面詳細介紹非阻塞IO和優化的數據結構
多路複用IO
在《unix網絡編程 卷I》中詳細講解了unix服務器中的5種IO模型。
一個IO操作一般分爲兩個步驟:
- 等待數據從網絡到達, 數據到達後加載到內核空間緩衝區
- 數據從內核空間緩衝區複製到用戶空間緩衝區
按照兩個步驟是否阻塞線程,分爲阻塞/非阻塞, 同步/異步。
五種IO模型分類:
阻塞 | 非阻塞 | |
---|---|---|
同步 | 阻塞IO | 非阻塞IO,IO多路複用,信號驅動IO |
異步IO | 異步IO |
阻塞IO
在linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:
非阻塞IO
Linux下,可以通過設置socket使其變爲non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:
IO多路複用
IO multiplexing這個詞可能有點陌生,但是如果我說select/epoll,大概就都能明白了。有些地方也稱這種IO方式爲事件驅動IO(event driven IO)。我們都知道,select/epoll的好處就在於單個process就可以同時處理多個網絡連接的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有數據到達了,就通知用戶進程。它的流程如圖:
信號驅動IO
異步IO
Linux下的asynchronous IO其實用得不多,從內核2.6版本纔開始引入。先看一下它的流程:
介紹完unix或者類unix系統IO模型之後, 我們看下redis怎麼處理客戶端連接的?
Reids的IO處理
總的來說Redis使用一種封裝多種(select,epoll, kqueue等)實現的Reactor設計模式多路複用IO處理客戶端的請求。
Reactor設計模式常常用來實現事件驅動。除此之外, Redis還封裝了不同平臺多路複用IO的不同的庫。處理過程如下:
因爲 Redis 需要在多個平臺上運行,同時爲了最大化執行的效率與性能,所以會根據編譯平臺的不同選擇不同的 I/O 多路複用函數作爲子模塊。
具體選擇過程如下:
Redis 會優先選擇時間複雜度爲 O(1) 的 I/O 多路複用函數作爲底層實現,包括 Solaries 10 中的 evport、Linux 中的 epoll 和 macOS/FreeBSD 中的 kqueue,上述的這些函數都使用了內核內部的結構,並且能夠服務幾十萬的文件描述符。
但是如果當前編譯環境沒有上述函數,就會選擇 select 作爲備選方案,由於其在使用時會掃描全部監聽的描述符,所以其時間複雜度較差 O(n),並且只能同時服務 1024 個文件描述符,所以一般並不會以 select 作爲第一方案使用。
豐富高效的數據結構
Redis提供了豐富的數據結構,並且不同場景下提供不同實現。
Redis作爲key-value系統,不同類型的key對應不同的操作或者操作對應不同的實現,相同的key也會有不同的實現。Redis對key進行操作時,會進行類型檢查,調用不同的實現。
爲了解決以上問題, Redis 構建了自己的類型系統, 這個系統的主要功能包括:
redisObject 對象。
基於 redisObject 對象的類型檢查。
基於 redisObject 對象的顯式多態函數。
對 redisObject 進行分配、共享和銷燬的機制。
redisObject定義:
/*
* Redis 對象
*/
typedef struct redisObject {
// 類型
unsigned type:4;
// 對齊位
unsigned notused:2;
// 編碼方式
unsigned encoding:4;
// LRU 時間(相對於 server.lruclock)
unsigned lru:22;
// 引用計數
int refcount;
// 指向對象的值
void *ptr;
} robj;
type 、 encoding 和 ptr 是最重要的三個屬性。
Redis支持4種type, 8種編碼, 分別爲:
有了redisObject之後, 對於特定key的操作過程就可以很容易的實現:
Redis除了提供豐富的高效的數據結構外, 還提供瞭如HyperLogLog, Geo索引這樣高效的算法。
篇幅的原因,影響Redis性能的因素將在另外一篇文章中介紹。
參考文檔:
👁 關注微信公衆號:非典型理科男 回覆:redis獲取redis三本經典著作