在日常開發(fā)中,為了保證數(shù)據(jù)的一致性,我們一般都選擇關(guān)系型數(shù)據(jù)庫來存儲數(shù)據(jù),如 MySQL
,Oracle
等,因為關(guān)系型數(shù)據(jù)庫有著事務的特性。然而在并發(fā)量比較大的業(yè)務場景,關(guān)系型數(shù)據(jù)庫卻又往往會成為系統(tǒng)瓶頸,無法完全滿足我們的需求,所以就需要使用到緩存,而非關(guān)系型數(shù)據(jù)庫,即 NoSQL
數(shù)據(jù)庫往往又會成為最佳選擇。
NoSQL
數(shù)據(jù)庫最常見的解釋是 non-relational
,也有人解釋為 Not Only SQL
。非關(guān)系型數(shù)據(jù)庫不保證事務,也就是不具備事務 ACID
特性,這也是非關(guān)系型數(shù)據(jù)庫和關(guān)系型數(shù)據(jù)庫最大的區(qū)別,而我們即將介紹的 Redis
就屬于 NoSQL
數(shù)據(jù)庫的一種。
什么是 Redis
Redis
全稱是:REmote DIctionary Service
,即遠程字典服務。Redis
是一個開源的(遵守 BSD
協(xié)議)、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value
數(shù)據(jù)庫。
Redis
具有以下特性:
1、支持豐富的數(shù)據(jù)類型:字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets),位圖等。
2、功能豐富:提供了持久化機制,過期策略,訂閱/發(fā)布等功能。
3、高性能,高可用且支持集群。
4、提供了多種語言的 API
。
Redis 的安裝
1、下載對應版本的安裝包,如:Redis 5.0.5 版本,其他版本也可以點擊這里進行下載。
2、下載好之后傳到服務器指定目錄,執(zhí)行命令 tar -zxvf redis-5.0.5.tar.gz
進行解壓。
3、解壓成功之后,進入 Redis
主目錄,執(zhí)行命令 make make install PREFIX=/xxx/xxx/redis-5.0.5
進行安裝,如果不指定目錄,則默認是安裝在 /usr/local
目錄下。
4、安裝成功之后可以看到 Redis
主目錄下多了一個 bin
目錄,bin
目錄內(nèi)包含了一些可執(zhí)行腳本。
5、回到 Redis
主目錄下,找到 redis.conf
配置文件,將其中的配置 daemonize no
修改為 daemonize yes
,表示在后臺啟動服務。
6、然后就可以執(zhí)行命令 /xxx/xxx/redis-5.0.5/bin/redis-server /xxx/xxx/redis-5.0.5/redis.conf
啟動 Redis
服務。 Redis 到底有多快
大家可能都知道 Redis
很快,可是 Redis
到底能有多快呢,比如 Redis
的吞吐量能達到多少?我想這就不是每一個人都能說的上來一個具體的數(shù)字了。
Redis
官方提供了一個測試腳本,可以供我們測試 Redis
的 吞吐量。
redis-benchmark -q -n 100000
可以測試常用命令的吞吐量。 redis-benchmark -t set,lpush -n 100000 -q
測試 Redis
處理 set
和 lpush
命令的吞吐量。 redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')"
測試 Redis
處理 Lua
腳本等吞吐量。
下圖就是我這邊執(zhí)行第一條命令的自測結(jié)果,可以看到大部分命令的吞吐量都可以達到 4
萬以上,也就是說每秒鐘可以處理 4
萬次以上請求:
![](/d/20211018/5a7242b2528783e693a80b517bf9ba02.gif)
但是如果你以為這就是 Redis
的真實吞吐量,那就錯了。實際上,Redis
官方的測試結(jié)果是可以達到 10
萬的吞吐量,下圖就是官方提供的一個基準測試結(jié)果(縱坐標就是吞吐量,橫坐標是連接數(shù)):
![](/d/20211018/eb66da6f61179aa39a4d1ffbb95784b8.gif)
Redis 是單線程還是多線程
這個問題比較經(jīng)典,因為在很多人的認知里,Redis
就是單線程的。然而 Redis
從 4.0
版本開始就有了多線程的概念,雖然處理命令請求的核心模塊確實是保證了單線程執(zhí)行,然而在其他許多地方已經(jīng)有了多線程,比如:在后臺刪除對象,通過 Redis
模塊實現(xiàn)阻塞命令,生成 dump
文件,以及 6.0
版本中網(wǎng)絡(luò) I/O
實現(xiàn)了多線程等,而且在未來 Redis
應該會有越來越多的模塊實現(xiàn)多線程。
所謂的單線程,只是說 Redis
的處理客戶端的請求(即執(zhí)行命令)時,是單線程去執(zhí)行的,并不是說整個 Redis
都是單線程。
Redis 為什么選擇使用單線程來執(zhí)行請求
Redis
為什么會選擇使用單線程呢?這是因為 CPU
成為 Redis
瓶頸的情況并不常見,成為 Redis
瓶頸的通常是內(nèi)存或網(wǎng)絡(luò)帶寬。例如,在一個普通的 Linux
系統(tǒng)上使用 pipelining
命令,Redis
可以每秒完成 100
萬個請求,所以如果我們的應用程序主要使用 O(N)
或 O(log(N))
復雜度的命令,它幾乎不會使用太多的 CPU
。
那么既然 CPU
不會成為瓶頸,理所當然的就沒必要去使用多線程來執(zhí)行命令,我們需要明確的一個問題就是多線程一定比單線程快嗎?答案是不一定。因為多線程也是有代價的,最直接的兩個代價就是線程的創(chuàng)建和銷毀線程(當然可以通過線程池來一定程度的減少頻繁的創(chuàng)建線程和銷毀線程)以及線程的上下文切換。
在我們的日常系統(tǒng)中,主要可以區(qū)分為兩種:CPU
密集型 和 IO
密集型。
CPU 密集型:
這種系統(tǒng)就說明 CPU
的利用率很高,那么使用多線程反而會增加上下文切換而帶來額外的開銷,所以使用多線程效率可能會不升反降。舉個例子:假如你現(xiàn)在在干活,你一直不停的在做一件事,需要 1
分鐘可以做完,但是你中途總是被人打斷,需要花 1
秒鐘時間步行到旁邊去做另一件事,假如這件事也需要 1
分鐘,那么你因為反復切換做兩件事,每切換一次就要花 1
秒鐘,最后做完這 2
件事的時間肯定大于 2
分鐘(取決于中途切換的次數(shù)),但是如果中途不被打斷,你做完一件事再去做另一件事,那么你最多只需要切換 1
次,也就是 2
分 1
秒就能做完。
IO 密集型:
IO
操作也可以分為磁盤 IO
和網(wǎng)絡(luò) IO
等操作。大部分 IO
操作的特點是比較耗時且 CPU
利用率不高,所以 Redis 6.0
版本網(wǎng)絡(luò) IO
會改進為多線程。至于磁盤 IO
,因為 Redis
中的數(shù)據(jù)都存儲在內(nèi)存(也可以持久化),所以并不會過多的涉及到磁盤操作。舉個例子:假如你現(xiàn)在給樹苗澆水,你每澆完一次水之后就需要等別人給你加水之后你才能繼續(xù)澆,那么假如這個等待過程需要 5
秒鐘,也就是說你澆完一次水就可以休息 5
秒鐘,而你切換去做另一件事來回只需要 2
秒,那么你完全可以先去做另一件事,做完之后再回來,這樣就可以充分利用你空閑的 5
秒鐘時間,從而提升了效率。
使用多線程還會帶來一個問題就是數(shù)據(jù)的安全性,所以多線程編程都會涉及到鎖競爭,由此也會帶來額外的開銷。
什么是 I/O 多路復用
I/O
指的是網(wǎng)絡(luò) I/O
, 多路指的是多個 TCP
連接(如 Socket
),復用指的是復用一個或多個線程。I/O
多路復用的核心原理就是不再由應用程序自己來監(jiān)聽連接,而是由服務器內(nèi)核替應用程序監(jiān)聽。
在 Redis
中,其多路復用有多種實現(xiàn),如:select
,epoll
,evport
,kqueue
等。
我們用去餐廳吃飯為的例子來解釋一下 I/O
多路復用機制(點餐人相當于客戶端,餐廳的廚房相當于服務器,廚師就是線程)。
阻塞 IO
:張三去餐廳吃飯,點了一道菜,這時候他啥事也不干了,就是一直等,等到廚師炒好菜,他就把菜端走開始吃飯了。也就是在菜被炒好之前,張三被阻塞了,這就是 BIO
(阻塞 IO
),效率會非常低下。
非阻塞 IO
:張三去餐廳吃飯,點了一道菜,這時候張三他不會一直等,找了個位置坐下,刷刷抖音,打打電話,做點其他事,然后每隔一段時間就去廚房問一下自己的菜好了沒有。這種就屬于非阻塞 IO
,這種方式雖然可以提高性能,但是如果有大量 IO
都來定期輪詢,也會給服務器造成非常大的負擔。
事件驅(qū)動機制:張三去餐廳吃飯,點了一道菜,這時候他找了個位置坐下來等: 廚房那邊菜做好了就會把菜端出來了,但是并不知道這道菜是誰的,于是就挨個詢問顧客,這就是多路復用中的 select
模型,不過 select
模型最多只能監(jiān)聽 1024
個 socket
(poll
模型解決了這個限制問題)。廚房做好了菜直接把菜放在窗口上,大喊一聲,某某菜做好了,是誰的快過來拿,這時候聽到通知的人就會自己去拿,這就是多路復用中的 epoll
模型。
需要注意的是在 IO
多路復用機制下,客戶端可以阻塞也可以選擇不阻塞(大部分場景下是阻塞 IO
),這個要具體情況具體分析,但是在多路復用機制下,服務端就可以通過多線程(上面示例中可以多幾個廚師同時炒菜)來提升并發(fā)效率。
Redis 中 I/O 多路復用的應用
Redis
服務器是一個事件驅(qū)動程序,服務器需要處理兩類事件:文件事件和時間事件。
文件事件:Redis
服務器和客戶端(或其他服務器)進行通信會產(chǎn)生相應的文件事件,然后服務器通過監(jiān)聽并處理這些事件來完成一系列的通信操作。
時間事件:Redis
內(nèi)部的一些在給定時間之內(nèi)需要進行的操作。
Redis
的文件事件處理器以單線程的方式運行,其內(nèi)部使用了 I/O
多路復用程序來同時監(jiān)聽多個套接字(Socket
)連接,提升了性能的同時又保持了內(nèi)部單線程設(shè)計的簡單性。下圖就是文件事件處理器的示意圖:
![](/d/20211018/a53123855b66e2c1d40431b4d1f593a7.gif)
I/O
多路復用程序雖然會同時監(jiān)聽多個 Socket
連接,但是其會將監(jiān)聽的 Socket
都放到一個隊列里面,然后通過這個隊列有序的,同步的將每個 Socket
對應的事件傳送給文件事件分派器,再由文件事件分派器分派給對應的事件處理器進行處理,只有當一個 Socket
所對應的事件被處理完畢之后,I/O
多路復用程序才會繼續(xù)向文件事件分派器傳送下一個 Socket
所對應的事件,這也可以驗證上面的結(jié)論,處理客戶端的命令請求是單線程的方式逐個處理,但是事件處理器內(nèi)并不是只有一個線程。
Redis 為什么這么快
Redis
為什么這么快的原因前面已經(jīng)基本提到了,現(xiàn)在我們再進行總結(jié)一下:
1、Redis
是一款純內(nèi)存結(jié)構(gòu),避免了磁盤 I/O
等耗時操作。
2、Redis
命令處理的核心模塊為單線程,減少了鎖競爭,以及頻繁創(chuàng)建線程和銷毀線程的代價,減少了線程上下文切換的消耗。
3、采用了 I/O
多路復用機制,大大提升了并發(fā)效率。
到此這篇關(guān)于Redis憑啥可以這么快的文章就介紹到這了,更多相關(guān)Redis為什么快內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- Redis不是一直號稱單線程效率也很高嗎,為什么又采用多線程了?
- redis單線程快的原因和原理
- Redis為什么快如何實現(xiàn)高可用及持久化
- 為啥Redis使用pipelining會更快
- Redis高效率原因及數(shù)據(jù)結(jié)構(gòu)分析