Redis爲何這麼快

前言

本篇博客已被收錄GitHub:https://zhouwenxing.github.io/

在平常開發中,爲了保證數據的一致性,咱們通常都選擇關係型數據庫來存儲數據,如 MySQLOracle 等,由於關係型數據庫有着事務的特性。然而在併發量比較大的業務場景,關係型數據庫卻又每每會成爲系統瓶頸,沒法徹底知足咱們的需求,因此就須要使用到緩存,而非關係型數據庫,即 NoSQL 數據庫每每又會成爲最佳選擇。git

NoSQL 數據庫最多見的解釋是 non-relational,也有人解釋爲 Not Only SQL。非關係型數據庫不保證事務,也就是不具有事務 ACID 特性,這也是非關係型數據庫和關係型數據庫最大的區別,而咱們即將介紹的 Redis 就屬於 NoSQL 數據庫的一種。github

什麼是 Redis

Redis 全稱是:REmote DIctionary Service,即遠程字典服務。Redis 是一個開源的(遵照 BSD 協議)、支持網絡、可基於內存亦可持久化的日誌型、Key-Value 數據庫。
Redis 具備如下特性:redis

  • 一、支持豐富的數據類型:字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets),位圖等。
  • 二、功能豐富:提供了持久化機制,過時策略,訂閱/發佈等功能。
  • 三、高性能,高可用且支持集羣。
  • 四、提供了多種語言的 API

Redis 的安裝

  • 一、下載對應版本的安裝包,如:Redis 5.0.5 版本,其餘版本也能夠點擊這裏進行下載。
  • 二、下載好以後傳到服務器指定目錄,執行命令 tar -zxvf redis-5.0.5.tar.gz 進行解壓。
  • 三、解壓成功以後,進入 Redis 主目錄,執行命令 make && make install PREFIX=/xxx/xxx/redis-5.0.5 進行安裝,若是不指定目錄,則默認是安裝在 /usr/local 目錄下。
  • 四、安裝成功以後能夠看到 Redis 主目錄下多了一個 bin 目錄,bin 目錄內包含了一些可執行腳本。
  • 五、回到 Redis 主目錄下,找到 redis.conf 配置文件,將其中的配置 daemonize no 修改成 daemonize yes,表示在後臺啓動服務。
  • 六、而後就能夠執行命令 /xxx/xxx/redis-5.0.5/bin/redis-server /xxx/xxx/redis-5.0.5/redis.conf 啓動 Redis 服務。

Redis 到底有多快

你們可能都知道 Redis 很快,但是 Redis 到底能有多快呢,好比 Redis 的吞吐量能達到多少?我想這就不是每個人都能說的上來一個具體的數字了。數據庫

Redis 官方提供了一個測試腳本,能夠供咱們測試 Redis 的 吞吐量。編程

  • redis-benchmark -q -n 100000 能夠測試經常使用命令的吞吐量。
  • redis-benchmark -t set,lpush -n 100000 -q 測試 Redis 處理 setlpush 命令的吞吐量。
  • redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')" 測試 Redis 處理 Lua 腳本等吞吐量。

下圖就是我這邊執行第一條命令的自測結果,能夠看到大部分命令的吞吐量均可以達到 4 萬以上,也就是說每秒鐘能夠處理 4 萬次以上請求:緩存

可是若是你覺得這就是 Redis 的真實吞吐量,那就錯了。實際上,Redis 官方的測試結果是能夠達到 10 萬的吞吐量,下圖就是官方提供的一個基準測試結果(縱座標就是吞吐量,橫座標是鏈接數):安全

Redis 是單線程仍是多線程

這個問題比較經典,由於在不少人的認知裏,Redis 就是單線程的。然而 Redis4.0 版本開始就有了多線程的概念,雖然處理命令請求的核心模塊確實是保證了單線程執行,然而在其餘許多地方已經有了多線程,好比:在後臺刪除對象,經過 Redis 模塊實現阻塞命令,生成 dump 文件,以及 6.0 版本中網絡 I/O 實現了多線程等,並且在將來 Redis 應該會有愈來愈多的模塊實現多線程。服務器

所謂的單線程,只是說 Redis 的處理客戶端的請求(即執行命令)時,是單線程去執行的,並非說整個 Redis 都是單線程。網絡

Redis 爲何選擇使用單線程來執行請求

Redis 爲何會選擇使用單線程呢?這是由於 CPU 成爲 Redis 瓶頸的狀況並不常見,成爲 Redis 瓶頸的一般是內存或網絡帶寬。例如,在一個普通的 Linux 系統上使用 pipelining 命令,Redis 能夠每秒完成 100 萬個請求,因此若是咱們的應用程序主要使用 O(N)O(log(N)) 複雜度的命令,它幾乎不會使用太多的 CPU多線程

那麼既然 CPU 不會成爲瓶頸,理所固然的就不必去使用多線程來執行命令,咱們須要明確的一個問題就是多線程必定比單線程快嗎?答案是不必定。由於多線程也是有代價的,最直接的兩個代價就是線程的建立和銷燬線程(固然能夠經過線程池來必定程度的減小頻繁的建立線程和銷燬線程)以及線程的上下文切換。

在咱們的平常系統中,主要能夠區分爲兩種:CPU 密集型 和 IO 密集型。

  • CPU 密集型:這種系統就說明 CPU 的利用率很高,那麼使用多線程反而會增長上下文切換而帶來額外的開銷,因此使用多線程效率可能會不升反降。舉個例子:假如你如今在幹活,你一直不停的在作一件事,須要 1 分鐘能夠作完,可是你中途老是被人打斷,須要花 1 秒鐘時間步行到旁邊去作另外一件事,假如這件事也須要 1 分鐘,那麼你由於反覆切換作兩件事,每切換一次就要花 1 秒鐘,最後作完這 2 件事的時間確定大於 2 分鐘(取決於中途切換的次數),可是若是中途不被打斷,你作完一件事再去作另外一件事,那麼你最多隻須要切換 1 次,也就是 21 秒就能作完。
  • IO 密集型:IO 操做也能夠分爲磁盤 IO 和網絡 IO 等操做。大部分 IO 操做的特色是比較耗時且 CPU 利用率不高,因此 Redis 6.0 版本網絡 IO 會改進爲多線程。至於磁盤 IO,由於 Redis 中的數據都存儲在內存(也能夠持久化),因此並不會過多的涉及到磁盤操做。舉個例子:假如你如今給樹苗澆水,你每澆完一次水以後就須要等別人給你加水以後你才能繼續澆,那麼假如這個等待過程須要 5 秒鐘,也就是說你澆完一次水就能夠休息 5 秒鐘,而你切換去作另外一件事來回只須要 2 秒,那麼你徹底能夠先去作另外一件事,作完以後再回來,這樣就能夠充分利用你空閒的 5 秒鐘時間,從而提高了效率。

使用多線程還會帶來一個問題就是數據的安全性,因此多線程編程都會涉及到鎖競爭,由此也會帶來額外的開銷。

什麼是 I/O 多路複用

I/O 指的是網絡 I/O, 多路指的是多個 TCP 鏈接(如 Socket),複用指的是複用一個或多個線程。I/O 多路複用的核心原理就是再也不由應用程序本身來監聽鏈接,而是由服務器內核替應用程序監聽。

Redis 中,其多路複用有多種實現,如:selectepollevportkqueue 等。

咱們用去餐廳吃飯爲的例子來解釋一下 I/O 多路複用機制(點餐人至關於客戶端,餐廳的廚房至關於服務器,廚師就是線程)。

  • 阻塞 IO:張三去餐廳吃飯,點了一道菜,這時候他啥事也不幹了,就是一直等,等到廚師炒好菜,他就把菜端走開始吃飯了。也就是在菜被炒好以前,張三被阻塞了,這就是 BIO(阻塞 IO),效率會很是低下。
  • 非阻塞 IO:張三去餐廳吃飯,點了一道菜,這時候張三他不會一直等,找了個位置坐下,刷刷抖音,打打電話,作點其餘事,而後每隔一段時間就去廚房問一下本身的菜好了沒有。這種就屬於非阻塞 IO,這種方式雖然能夠提升性能,可是若是有大量 IO 都來按期輪詢,也會給服務器形成很是大的負擔。
  • 事件驅動機制:張三去餐廳吃飯,點了一道菜,這時候他找了個位置坐下來等:
    • 廚房那邊菜作好了就會把菜端出來了,可是並不知道這道菜是誰的,因而就挨個詢問顧客,這就是多路複用中的 select 模型,不過 select 模型最多隻能監聽 1024socketpoll 模型解決了這個限制問題)。
    • 廚房作好了菜直接把菜放在窗口上,大喊一聲,某某菜作好了,是誰的快過來拿,這時候聽到通知的人就會本身去拿,這就是多路複用中的 epoll 模型。

須要注意的是在 IO 多路複用機制下,客戶端能夠阻塞也能夠選擇不阻塞(大部分場景下是阻塞 IO),這個要具體狀況具體分析,可是在多路複用機制下,服務端就能夠經過多線程(上面示例中能夠多幾個廚師同時炒菜)來提高併發效率。

Redis 中 I/O 多路複用的應用

Redis 服務器是一個事件驅動程序,服務器須要處理兩類事件:文件事件和時間事件。

  • 文件事件:Redis 服務器和客戶端(或其餘服務器)進行通訊會產生相應的文件事件,而後服務器經過監聽並處理這些事件來完成一系列的通訊操做。
  • 時間事件:Redis 內部的一些在給定時間以內須要進行的操做。

Redis 的文件事件處理器以單線程的方式運行,其內部使用了 I/O 多路複用程序來同時監聽多個套接字(Socket)鏈接,提高了性能的同時又保持了內部單線程設計的簡單性。下圖就是文件事件處理器的示意圖:

I/O 多路複用程序雖然會同時監聽多個 Socket 鏈接,可是其會將監聽的 Socket 都放到一個隊列裏面,而後經過這個隊列有序的,同步的將每一個 Socket 對應的事件傳送給文件事件分派器,再由文件事件分派器分派給對應的事件處理器進行處理,只有當一個 Socket 所對應的事件被處理完畢以後,I/O多路複用程序纔會繼續向文件事件分派器傳送下一個 Socket 所對應的事件,這也能夠驗證上面的結論,處理客戶端的命令請求是單線程的方式逐個處理,可是事件處理器內並非只有一個線程。

Redis 爲何這麼快

Redis 爲何這麼快的緣由前面已經基本提到了,如今咱們再進行總結一下:

  • 一、Redis 是一款純內存結構,避免了磁盤 I/O 等耗時操做。
  • 二、Redis 命令處理的核心模塊爲單線程,減小了鎖競爭,以及頻繁建立線程和銷燬線程的代價,減小了線程上下文切換的消耗。
  • 三、採用了 I/O 多路複用機制,大大提高了併發效率。
相關文章
相關標籤/搜索