Redis爲何這麼快?

Redis爲何這麼快?

頭像
👁 關注微信公衆號:非典型理科男 回覆:redis獲取redis三本經典著做

序言

做爲企業級的存儲組件, Redis被用到不少的業務場景。linux

Redis常常被用做作緩存, 一致性要求不高場景,還能夠當作存儲使用。面試

另外, Redis還提供了消息訂閱、事務、索引等特性。 咱們還能夠利用集羣特性搭建分佈式存儲服務,實現非強一致性的分佈式鎖服務。redis

Redis用到上述場景, 都有一個共同的優點, 就是處理速度快(高性能)。算法

面試中,面試官常常會問到單線程的Redis爲何這麼快? 爲了闡明這個問題, 下面將分三部分講解: (1) 第一部分: Redis到底有多快 (2) 第二部分: 詳細講解Redis高性能緣由 (3) 第三部分: 影響Redis性能的因素編程

Redis到底有多快

要了解Reids的到底有多麼快, 首先須要有相應的評估工具。 其次,須要Redis 在一些平臺經驗數據,來評估Redis性能數量級。 幸運的是Redis提供了這樣的工具,並給出了經常使用的硬件平臺一些經驗數據。windows

下面篇幅比較長,核心觀點以下:設計模式

  1. 可使用redis-benchmark對Redis的性能進行評估,命令行提供了普通/流水線方式、不一樣壓力評估特定命令的性能的功能。
  2. redis性能卓越,做爲key-value系統最大負載數量級爲10W/s, set和get耗時數量級爲10ms和5ms。使用流水線的方式能夠提高redis操做的性能。

不關心具體數據的小夥伴,能夠直接跳到第二部分,直接瞭解redis性能卓越的緣由。緩存

Redis性能評估工具

Redis包含的redis-benchmark實用程序可模擬N個客戶端同時發送M個總查詢的運行命令(相似於Apache的ab實用程序)。可使用redis-benchmark對redis的性能進行評估。bash

支持如下選項:服務器

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系統負載

  1. 不使用流水線測試結果
$ ./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
複製代碼
  1. 使用流水線測試結果
$ ./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, 使用流水線技術可以顯著提高讀寫性能。

耗時狀況

  1. 不使用流水線測試結果
$ 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操做通常分爲兩個步驟:

  1. 等待數據從網絡到達, 數據到達後加載到內核空間緩衝區
  2. 數據從內核空間緩衝區複製到用戶空間緩衝區

按照兩個步驟是否阻塞線程,分爲阻塞/非阻塞, 同步/異步。

五種IO模型分類:

阻塞 非阻塞
同步 阻塞IO 非阻塞IO,IO多路複用,信號驅動IO
異步IO 異步IO

阻塞IO

在linux中,默認狀況下全部的socket都是blocking,一個典型的讀操做流程大概是這樣:

阻塞IO

非阻塞IO

Linux下,能夠經過設置socket使其變爲non-blocking。當對一個non-blocking socket執行讀操做時,流程是這個樣子:

非阻塞IO

IO多路複用

IO multiplexing這個詞可能有點陌生,可是若是我說select/epoll,大概就都能明白了。有些地方也稱這種IO方式爲事件驅動IO(event driven IO)。咱們都知道,select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程。它的流程如圖:

IO多路複用

信號驅動IO

信號驅動IO

異步IO

Linux下的asynchronous IO其實用得很少,從內核2.6版本纔開始引入。先看一下它的流程:

異步IO

介紹完unix或者類unix系統IO模型以後, 咱們看下redis怎麼處理客戶端鏈接的?

Reids的IO處理

總的來講Redis使用一種封裝多種(select,epoll, kqueue等)實現的Reactor設計模式多路複用IO處理客戶端的請求。

Reactor設計模式

Reactor設計模式經常用來實現事件驅動。除此以外, Redis還封裝了不一樣平臺多路複用IO的不一樣的庫。處理過程以下:

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命令的調用過程

Redis除了提供豐富的高效的數據結構外, 還提供瞭如HyperLogLog, Geo索引這樣高效的算法。

篇幅的緣由,影響Redis性能的因素將在另一篇文章中介紹。

參考文檔:

  1. Redis和IO多路複用
  2. 5種網絡IO模型(有圖,很清楚)

頭像
👁 關注微信公衆號:非典型理科男 回覆:redis獲取redis三本經典著做
相關文章
相關標籤/搜索