为什么用redis?
為什么用redis?
簡(jiǎn)單說(shuō):提升用戶體驗(yàn)和應(yīng)對(duì)更多的用戶
下面我們主要從“高性能”和“高并發(fā)”這兩點(diǎn)來(lái)看待這個(gè)問(wèn)題。
高性能 :
對(duì)照上面 👆 我畫的圖。我們?cè)O(shè)想這樣的場(chǎng)景:
假如用戶第一次訪問(wèn)數(shù)據(jù)庫(kù)中的某些數(shù)據(jù)的話,這個(gè)過(guò)程是比較慢,畢竟是從硬盤中讀取的。但是,如果說(shuō),用戶訪問(wèn)的數(shù)據(jù)屬于高頻數(shù)據(jù)并且不會(huì)經(jīng)常改變的話,那么我們就可以很放心地將該用戶訪問(wèn)的數(shù)據(jù)存在緩存中。
這樣有什么好處呢? 那就是保證用戶下一次再訪問(wèn)這些數(shù)據(jù)的時(shí)候就可以直接從緩存中獲取了。操作緩存就是直接操作內(nèi)存,所以速度相當(dāng)快。
不過(guò),要保持?jǐn)?shù)據(jù)庫(kù)和緩存中的數(shù)據(jù)的一致性。 如果數(shù)據(jù)庫(kù)中的對(duì)應(yīng)數(shù)據(jù)改變的之后,同步改變緩存中相應(yīng)的數(shù)據(jù)即可!
高并發(fā):
一般像 MySQL 這類的數(shù)據(jù)庫(kù)的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 緩存之后很容易達(dá)到 10w+,甚至最高能達(dá)到 30w+(就單機(jī) redis 的情況,redis 集群的話會(huì)更高)。
ps:QPS(Query Per Second):服務(wù)器每秒可以執(zhí)行的查詢次數(shù);
所以,直接操作緩存能夠承受的數(shù)據(jù)庫(kù)請(qǐng)求數(shù)量是遠(yuǎn)遠(yuǎn)大于直接訪問(wèn)數(shù)據(jù)庫(kù)的,所以我們可以考慮把數(shù)據(jù)庫(kù)中的部分?jǐn)?shù)據(jù)轉(zhuǎn)移到緩存中去,這樣用戶的一部分請(qǐng)求會(huì)直接到緩存這里而不用經(jīng)過(guò)數(shù)據(jù)庫(kù)。進(jìn)而,我們也就提高的系統(tǒng)整體的并發(fā)。
redis線程模型(單 -> 多線程)
Redis 基于 Reactor 模式來(lái)設(shè)計(jì)開(kāi)發(fā)了自己的一套高效的事件處理模型 (Netty 的線程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),這套事件處理模型對(duì)應(yīng)的是 Redis 中的文件事件處理器(file event handler)。由于文件事件處理器(file event handler)是單線程方式運(yùn)行的,所以我們一般都說(shuō) Redis 是單線程模型。
既然是單線程,那怎么監(jiān)聽(tīng)大量的客戶端連接呢?
Redis 通過(guò)IO 多路復(fù)用程序 來(lái)監(jiān)聽(tīng)來(lái)自客戶端的大量連接(或者說(shuō)是監(jiān)聽(tīng)多個(gè) socket),它會(huì)將感興趣的事件及類型(讀、寫)注冊(cè)到內(nèi)核中并監(jiān)聽(tīng)每個(gè)事件是否發(fā)生。
這樣的好處非常明顯: I/O 多路復(fù)用技術(shù)的使用讓 Redis 不需要額外創(chuàng)建多余的線程來(lái)監(jiān)聽(tīng)客戶端的大量連接,降低了資源的消耗(和 NIO 中的 Selector 組件很像)。
另外, Redis 服務(wù)器是一個(gè)事件驅(qū)動(dòng)程序,服務(wù)器需要處理兩類事件: 1. 文件事件; 2. 時(shí)間事件。
時(shí)間事件不需要多花時(shí)間了解,我們接觸最多的還是 文件事件(客戶端進(jìn)行讀取寫入等操作,涉及一系列網(wǎng)絡(luò)通信)。
《Redis 設(shè)計(jì)與實(shí)現(xiàn)》有一段話是如是介紹文件事件的,我覺(jué)得寫得挺不錯(cuò)。
Redis 基于 Reactor 模式開(kāi)發(fā)了自己的網(wǎng)絡(luò)事件處理器:這個(gè)處理器被稱為文件事件處理器(file event handler)。文件事件處理器使用 I/O 多路復(fù)用(multiplexing)程序來(lái)同時(shí)監(jiān)聽(tīng)多個(gè)套接字,并根據(jù) 套接字目前執(zhí)行的任務(wù)來(lái)為套接字關(guān)聯(lián)不同的事件處理器。
當(dāng)被監(jiān)聽(tīng)的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)、讀取(read)、寫入(write)、關(guān) 閉(close)等操作時(shí),與操作相對(duì)應(yīng)的文件事件就會(huì)產(chǎn)生,這時(shí)文件事件處理器就會(huì)調(diào)用套接字之前關(guān)聯(lián)好的事件處理器來(lái)處理這些事件。
雖然文件事件處理器以單線程方式運(yùn)行,但通過(guò)使用 I/O 多路復(fù)用程序來(lái)監(jiān)聽(tīng)多個(gè)套接字,文件事件處理器既實(shí)現(xiàn)了高性能的網(wǎng)絡(luò)通信模型,又可以很好地與 Redis 服務(wù)器中其他同樣以單線程方式運(yùn)行的模塊進(jìn)行對(duì)接,這保持了 Redis 內(nèi)部單線程設(shè)計(jì)的簡(jiǎn)單性。
文件事件處理器,主要包括四個(gè)部分:
多個(gè) socket(客戶端連接)
IO 多路復(fù)用程序(支持多個(gè)客戶端連接的關(guān)鍵)
文件事件分派器(將 socket 關(guān)聯(lián)到相應(yīng)的事件處理器)
事件處理器(連接應(yīng)答處理器、命令請(qǐng)求處理器、命令回復(fù)處理器)
雖然說(shuō) Redis 是單線程模型,但是,實(shí)際上,Redis 在 4.0 之后的版本中就已經(jīng)加入了對(duì)多線程的支持。
不過(guò),Redis 4.0 增加的多線程主要是針對(duì)一些大鍵值對(duì)的刪除操作的命令,使用這些命令就會(huì)使用主處理之外的其他線程來(lái)“異步處理”。
Redis在處理客戶端的請(qǐng)求時(shí),包括獲取 (socket 讀)、解析、執(zhí)行、內(nèi)容返回 (socket 寫) 等都由一個(gè)順序串行的主線程處理,這就是所謂的“單線程”。但如果嚴(yán)格來(lái)講從Redis4.0之后并不是單線程,除了主線程外,它也有后臺(tái)線程在處理一些較為緩慢的操作,例如清理臟數(shù)據(jù)、無(wú)用連接的釋放、大 key 的刪除等等。
大體上來(lái)說(shuō),Redis 6.0 之前主要還是單線程處理。那,Redis6.0 之前 為什么不使用多線程?
我覺(jué)得主要原因有下面 3 個(gè):
單線程編程容易并且更容易維護(hù);
Redis 的性能瓶頸不在 CPU ,主要在內(nèi)存和網(wǎng)絡(luò);
多線程就造成執(zhí)行順序不確定,會(huì)存在死鎖、線程上下文切換,加解鎖等問(wèn)題,甚至?xí)绊懶阅堋?br /> Redis6.0 引入多線程主要是為了充分利用cpu資源,提高網(wǎng)絡(luò) IO 讀寫性能,因?yàn)檫@個(gè)算是 Redis 中的一個(gè)性能瓶頸(Redis 的瓶頸主要受限于內(nèi)存和網(wǎng)絡(luò))。
雖然,Redis6.0 引入了多線程,但是 Redis 的多線程只是在網(wǎng)絡(luò)數(shù)據(jù)的讀寫這類耗時(shí)操作上使用了, 執(zhí)行命令仍然是單線程順序執(zhí)行。因此,你也不需要擔(dān)心線程安全問(wèn)題。
# Redis6.0 的多線程默認(rèn)是禁用的,只使用主線程。如需開(kāi)啟需要修改 redis 配置文件 redis.conf : io-threads-do-reads yes# 開(kāi)啟多線程后,還需要設(shè)置線程數(shù),否則是不生效的。同樣需要修改 redis 配置文件 redis.conf : io-threads 4 #官網(wǎng)建議4核的機(jī)器建議設(shè)置為2或3個(gè)線程,8核的建議設(shè)置為6個(gè)線程ps:Redis6.0與Memcached多線程模型對(duì)比
相同點(diǎn):都采用了 master線程-worker 線程的模型
不同點(diǎn):Memcached 執(zhí)行主邏輯也是在 worker 線程里,模型更加簡(jiǎn)單,實(shí)現(xiàn)了真正的線程隔離,符合我們對(duì)線程隔離的常規(guī)理解。而 Redis 把處理邏輯交還給 master 線程,雖然一定程度上增加了模型復(fù)雜度,但也解決了線程并發(fā)安全等問(wèn)題。
ps:Redis線程中經(jīng)常提到IO多路復(fù)用,如何理解?
這是IO模型的一種,即經(jīng)典的Reactor設(shè)計(jì)模式,有時(shí)也稱為異步阻塞IO。
多路指的是多個(gè)socket連接,復(fù)用指的是復(fù)用一個(gè)線程。多路復(fù)用主要有三種技術(shù):select,poll,epoll。epoll是最新的也是目前最好的多路復(fù)用技術(shù)。采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò)IO的時(shí)間消耗),且Redis在內(nèi)存中操作數(shù)據(jù)的速度非常快(內(nèi)存內(nèi)的操作不會(huì)成為這里的性能瓶頸),主要以上兩點(diǎn)造就了Redis具有很高的吞吐量。
拓展:高性能IO模型
服務(wù)器端編程經(jīng)常需要構(gòu)造高性能的IO模型,常見(jiàn)的IO模型有四種:
同步阻塞IO(Blocking IO):最簡(jiǎn)單,用戶線程在內(nèi)核進(jìn)行IO操作時(shí)被阻塞。用戶線程通過(guò)系統(tǒng)調(diào)用read發(fā)起IO讀操作,由用戶空間轉(zhuǎn)到內(nèi)核空間。內(nèi)核等到數(shù)據(jù)包到達(dá)后,然后將接收的數(shù)據(jù)拷貝到用戶空間,完成read操作。
缺點(diǎn):整個(gè)IO請(qǐng)求的過(guò)程中,用戶線程是被阻塞的,這導(dǎo)致用戶在發(fā)起IO請(qǐng)求時(shí),不能做任何事情,對(duì)CPU的資源利用率不夠。
同步非阻塞IO(Non-blocking IO):同步非阻塞IO是在同步阻塞IO的基礎(chǔ)上,將socket設(shè)置為NONBLOCK。由于socket是非阻塞的方式,因此用戶線程發(fā)起IO請(qǐng)求時(shí)立即返回。但并未讀取到任何數(shù)據(jù),用戶線程需要不斷地發(fā)起IO請(qǐng)求,直到數(shù)據(jù)到達(dá)后,才真正讀取到數(shù)據(jù),繼續(xù)執(zhí)行。
缺點(diǎn):雖然用戶線程每次發(fā)起IO請(qǐng)求后可以立即返回,但是為了等到數(shù)據(jù),仍需要不斷地輪詢、重復(fù)請(qǐng)求,消耗了大量的CPU的資源。一般很少直接使用這種模型,而是在其他IO模型中使用非阻塞IO這一特性。
O多路復(fù)用(IO Multiplexing):即經(jīng)典的Reactor設(shè)計(jì)模式,有時(shí)也稱為異步阻塞IO,Java中的Selector和Linux中的epoll都是這種模型。IO多路復(fù)用模型是建立在內(nèi)核提供的多路分離函數(shù)select基礎(chǔ)之上的,使用select函數(shù)可以避免同步非阻塞IO模型中輪詢等待的問(wèn)題。
基本流程:用戶首先將需要進(jìn)行IO操作的socket添加到select中,然后阻塞等待select系統(tǒng)調(diào)用返回。當(dāng)數(shù)據(jù)到達(dá)時(shí),socket被激活,select函數(shù)返回。用戶線程正式發(fā)起read請(qǐng)求,讀取數(shù)據(jù)并繼續(xù)執(zhí)行。
從流程上來(lái)看,使用select函數(shù)進(jìn)行IO請(qǐng)求和同步阻塞模型沒(méi)有太大的區(qū)別,甚至還多了添加監(jiān)視socket,以及調(diào)用select函數(shù)的額外操作,效率更差。但是,使用select以后最大的優(yōu)勢(shì)是用戶可以在一個(gè)線程內(nèi)同時(shí)處理多個(gè)socket的IO請(qǐng)求。用戶可以注冊(cè)多個(gè)socket,然后不斷地調(diào)用select讀取被激活的socket,即可達(dá)到在同一個(gè)線程內(nèi)同時(shí)處理多個(gè)IO請(qǐng)求的目的。而在同步阻塞模型中,必須通過(guò)多線程的方式才能達(dá)到這個(gè)目的。
然而,使用select函數(shù)的優(yōu)點(diǎn)并不僅限于此。雖然上述方式允許單線程內(nèi)處理多個(gè)IO請(qǐng)求,但是每個(gè)IO請(qǐng)求的過(guò)程還是阻塞的(在select函數(shù)上阻塞),平均時(shí)間甚至比同步阻塞IO模型還要長(zhǎng)。如果用戶線程只注冊(cè)自己感興趣的socket或者IO請(qǐng)求,然后去做自己的事情,等到數(shù)據(jù)到來(lái)時(shí)再進(jìn)行處理,則可以提高CPU的利用率。
異步IO(Asynchronous IO):即經(jīng)典的Proactor設(shè)計(jì)模式,也稱為異步非阻塞IO。“真正”的異步IO需要操作系統(tǒng)更強(qiáng)的支持。在IO多路復(fù)用模型中,事件循環(huán)將文件句柄的狀態(tài)事件通知給用戶線程,由用戶線程自行讀取數(shù)據(jù)、處理數(shù)據(jù)。而在異步IO模型中,當(dāng)用戶線程收到通知時(shí),數(shù)據(jù)已經(jīng)被內(nèi)核讀取完畢,并放在了用戶線程指定的緩沖區(qū)內(nèi),內(nèi)核在IO完成后通知用戶線程直接使用即可。
拓展:同步、異步和阻塞、非阻塞
同步和異步的概念描述的是用戶線程與內(nèi)核的交互方式:同步是指用戶線程發(fā)起IO請(qǐng)求后需要等待或者輪詢,內(nèi)核IO操作完成后才能繼續(xù)執(zhí)行;而異步是指用戶線程發(fā)起IO請(qǐng)求后仍繼續(xù)執(zhí)行,當(dāng)內(nèi)核IO操作完成后會(huì)通知用戶線程,或者調(diào)用用戶線程注冊(cè)的回調(diào)函數(shù)。
阻塞和非阻塞的概念描述的是用戶線程調(diào)用內(nèi)核IO操作的方式:阻塞是指IO操作需要徹底完成后才返回到用戶空間;而非阻塞是指IO操作被調(diào)用后立即返回給用戶一個(gè)狀態(tài)值,無(wú)需等到IO操作徹底完成。
拓展:Reactor設(shè)計(jì)模式
EventHandler抽象類表示IO事件處理器,它擁有IO文件句柄Handle(通過(guò)get_handle獲取),以及對(duì)Handle的操作handle_event(讀/寫等)。
繼承于EventHandler的子類可以對(duì)事件處理器的行為進(jìn)行定制。
Reactor類用于管理EventHandler(注冊(cè)、刪除等),并使用handle_events實(shí)現(xiàn)事件循環(huán),不斷調(diào)用同步事件多路分離器(一般是內(nèi)核)的多路分離函數(shù)select,只要某個(gè)文件句柄被激活(可讀/寫等),select就返回(阻塞),handle_events就會(huì)調(diào)用與文件句柄關(guān)聯(lián)的事件處理器的handle_event進(jìn)行相關(guān)操作。
通過(guò)Reactor的方式,可以將用戶線程輪詢IO操作狀態(tài)的工作統(tǒng)一交給handle_events事件循環(huán)進(jìn)行處理。用戶線程注冊(cè)事件處理器之后可以繼續(xù)執(zhí)行做其他的工作(異步),而Reactor線程負(fù)責(zé)調(diào)用內(nèi)核的select函數(shù)檢查socket狀態(tài)。當(dāng)有socket被激活時(shí),則通知相應(yīng)的用戶線程(或執(zhí)行用戶線程的回調(diào)函數(shù)),執(zhí)行handle_event進(jìn)行數(shù)據(jù)讀取、處理的工作。由于select函數(shù)是阻塞的,因此多路IO復(fù)用模型也被稱為異步阻塞IO模型。注意,這里的所說(shuō)的阻塞是指select函數(shù)執(zhí)行時(shí)線程被阻塞,而不是指socket。一般在使用IO多路復(fù)用模型時(shí),socket都是設(shè)置為NONBLOCK的,不過(guò)這并不會(huì)產(chǎn)生影響,因?yàn)橛脩舭l(fā)起IO請(qǐng)求時(shí),數(shù)據(jù)已經(jīng)到達(dá)了,用戶線程一定不會(huì)被阻塞。
IO多路復(fù)用是最常使用的IO模型,但是其異步程度還不夠“徹底”,因?yàn)樗褂昧藭?huì)阻塞線程的select系統(tǒng)調(diào)用。因此IO多路復(fù)用只能稱為異步阻塞IO,而非真正的異步IO。
https://snailclimb.gitee.io/javaguide/#/docs/database/Redis/redis-all
https://mp.weixin.qq.com/s/CMu7oXVIKp2s-PXTdMlimA
https://www.cnblogs.com/yanguhung/p/10145755.html
https://www.jianshu.com/p/2b523fbee36f
總結(jié)
以上是生活随笔為你收集整理的为什么用redis?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Redis与其他缓存框架的对比
- 下一篇: 小米算法题判断直线相交