Redis快的原因
- 內(nèi)存結(jié)構(gòu)
- 單線程
- IO多路復(fù)用
內(nèi)存結(jié)構(gòu)
Redis是KV結(jié)構(gòu)的內(nèi)存數(shù)據(jù)庫够傍,在內(nèi)存上操作數(shù)據(jù)才睹,而非磁盤亮垫。
單線程
Redis為什么是單線程的杆兵?
It's not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU.
However, to maximize CPU usage you can start multiple instances of Redis in the same box and treat them as different servers. At some point a single box may not be enough anyway, so if you want to use multiple CPUs you can start thinking of some way to shard earlier.
因?yàn)閱尉€程已經(jīng)夠用了揪阿,CPU 不是 redis 的瓶頸疗我。Redis 的瓶頸最有可能是機(jī)器內(nèi)存 或者網(wǎng)絡(luò)帶寬咆畏。既然單線程容易實(shí)現(xiàn),而且 CPU 不會(huì)成為瓶頸吴裤,那就順理成章地采用單線程的方案了旧找。
這里的單線程指的只是在處理我們的網(wǎng)絡(luò)請(qǐng)求的時(shí)候只有一個(gè)線程來處理,一個(gè)正式的Redis Server運(yùn)行的時(shí)候肯定是不止一個(gè)線程的麦牺。而且我們使用單線程的方式是無法發(fā)揮多核CPU 性能钮蛛,不過我們可以通過在單機(jī)開多個(gè)Redis 實(shí)例來完善!
單線程為什么這么快?
首先要講一下內(nèi)存模型:
計(jì)算機(jī)主存(內(nèi)存)可看作一個(gè)由 M 個(gè)連續(xù)的字節(jié)大小的單元組成的數(shù)組剖膳,每個(gè)字 節(jié)有一個(gè)唯一的地址魏颓,這個(gè)地址叫做物理地址(PA)。早期的計(jì)算機(jī)中潮秘,如果 CPU 需要內(nèi)存琼开,使用物理尋址,直接訪問主存儲(chǔ)器枕荞。
這種方式有幾個(gè)弊端:
- 在多用戶多任務(wù)操作系統(tǒng)中柜候,所有的進(jìn)程共享主存,如果每個(gè)進(jìn)程都獨(dú)占一塊物理地址空間躏精,主存很快就會(huì)被用完渣刷。我們希望在不同的時(shí)刻,不同的進(jìn)程可以共用同一塊物理地址空間矗烛。
- 如果所有進(jìn)程都是直接訪問物理內(nèi)存辅柴,那么一個(gè)進(jìn)程就可以修改其他進(jìn)程的內(nèi)存數(shù)據(jù),導(dǎo)致物理地址空間被破壞瞭吃,程序運(yùn)行就會(huì)出現(xiàn)異常碌嘀。
為了解決這些問題,就出現(xiàn)了在 CPU 和主存之間增加一個(gè)中間層歪架。CPU 不再使用物理地址訪問股冗,而是訪問一個(gè)虛擬地址,由這個(gè)中間層把地址轉(zhuǎn)換成物理地址和蚪, 最終獲得數(shù)據(jù)止状。這個(gè)中間層就是虛擬內(nèi)存。
在每一個(gè)進(jìn)程開始創(chuàng)建的時(shí)候攒霹,都會(huì)分配一段虛擬地址怯疤,然后通過虛擬地址和物理
地址的映射來獲取真實(shí)數(shù)據(jù),這樣進(jìn)程就不會(huì)直接接觸到物理地址催束,甚至不知道自己調(diào)用的哪塊物理地址的數(shù)據(jù)集峦。
進(jìn)程切換
多任務(wù)操作系統(tǒng)是怎么實(shí)現(xiàn)運(yùn)行遠(yuǎn)大于 CPU 數(shù)量的任務(wù)個(gè)數(shù)的?當(dāng)然,這些任務(wù)實(shí)際上并不是真的在同時(shí)運(yùn)行,而是因?yàn)橄到y(tǒng)通過時(shí)間片分片算法少梁,在很短的時(shí)間內(nèi)洛口,將 CPU 輪流分配給它們,造成多任務(wù)同時(shí)運(yùn)行的錯(cuò)覺凯沪。為了控制進(jìn)程的執(zhí)行第焰,內(nèi)核必須有能力掛起正在 CPU 上運(yùn)行的進(jìn)程,并恢復(fù)以前掛起的某個(gè)進(jìn)程的執(zhí)行妨马。這種行為被稱為進(jìn)程切換挺举。
在每個(gè)任務(wù)運(yùn)行前,CPU 都需要知道任務(wù)從哪里加載烘跺、又從哪里開始運(yùn)行湘纵,也就是 說,需要系統(tǒng)事先幫它設(shè)置好 CPU 寄存器和程序計(jì)數(shù)器(ProgramCounter)滤淳,這個(gè)叫做 CPU 的上下文梧喷。
而這些保存下來的上下文,會(huì)存儲(chǔ)在系統(tǒng)內(nèi)核中脖咐,并在任務(wù)重新調(diào)度執(zhí)行時(shí)再次加 載進(jìn)來铺敌。這樣就能保證任務(wù)原來的狀態(tài)不受影響,讓任務(wù)看起來還是連續(xù)運(yùn)行屁擅。
在切換上下文的時(shí)候偿凭,需要完成一系列的工作,這是一個(gè)很消耗資源的操作派歌。
單線程的好處
- 沒有創(chuàng)建線程弯囊、銷毀線程帶來的消耗。
- 避免了上線文切換導(dǎo)致的 CPU 消耗胶果。
- 避免了線程之間帶來的競(jìng)爭(zhēng)問題匾嘱,例如加鎖釋放鎖死鎖等等。
IO多路復(fù)用
傳統(tǒng)IO模型
以讀操作為例:當(dāng)應(yīng)用程序執(zhí)行 read 系統(tǒng)調(diào)用讀取文件描述符(FD)的時(shí)候早抠,如果這塊數(shù)據(jù)已經(jīng)存在于用戶進(jìn)程的頁內(nèi)存中奄毡,就直接從內(nèi)存中讀取數(shù)據(jù)。如果數(shù)據(jù)不存在贝或,則先將數(shù)據(jù)從磁盤加載數(shù)據(jù)到內(nèi)核緩沖區(qū)中,再從內(nèi)核緩沖區(qū)拷貝到用戶進(jìn)程的頁內(nèi)存中锐秦。(兩次拷貝咪奖,兩次 user 和 kernel 的上下文切換)。
BlockingIO
當(dāng)使用 read 或 write 對(duì)某個(gè)文件描述符進(jìn)行過讀寫時(shí)酱床,如果當(dāng)前 FD 不可讀羊赵,系統(tǒng)就不會(huì)對(duì)其他的操作做出響應(yīng)。從設(shè)備復(fù)制數(shù)據(jù)到內(nèi)核緩沖區(qū)是阻塞的,從內(nèi)核緩沖區(qū) 拷貝到用戶空間昧捷,也是阻塞的闲昭,直到 copy complete,內(nèi)核返回結(jié)果靡挥,用戶進(jìn)程才解除 block 的狀態(tài)序矩。
IO多路復(fù)用
I/O 指的是網(wǎng)絡(luò) I/O。
多路指的是多個(gè) TCP 連接(Socket 或 Channel)跋破。 復(fù)用指的是復(fù)用一個(gè)或多個(gè)線程簸淀。 它的基本原理就是不再由應(yīng)用程序自己監(jiān)視連接,而是由內(nèi)核替應(yīng)用程序監(jiān)視文件
描述符毒返。
客戶端在操作的時(shí)候租幕,會(huì)產(chǎn)生具有不同事件類型的 socket。在服務(wù)端拧簸,I/O 多路復(fù) 用程序(I/O Multiplexing Module)會(huì)把消息放入隊(duì)列中劲绪,然后通過文件事件分派器(File event Dispatcher),轉(zhuǎn)發(fā)到不同的事件處理器中盆赤。
多路復(fù)用有很多的實(shí)現(xiàn)贾富,以 select 為例,當(dāng)用戶進(jìn)程調(diào)用了多路復(fù)用器弟劲,進(jìn)程會(huì)被 阻塞祷安。內(nèi)核會(huì)監(jiān)視多路復(fù)用器負(fù)責(zé)的所有 socket,當(dāng)任何一個(gè) socket 的數(shù)據(jù)準(zhǔn)備好了兔乞, 多路復(fù)用器就會(huì)返回汇鞭。這時(shí)候用戶進(jìn)程再調(diào)用 read 操作,把數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶空間庸追。
I/O 多路復(fù)用的特點(diǎn)是通過一種機(jī)制一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符霍骄, 而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒(readable)狀態(tài),select() 函數(shù)就可以返回淡溯。
采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò) IO 的時(shí)間消耗)读整,且 Redis 在內(nèi)存中操作數(shù)據(jù)的速度非常快咱娶,也就是說內(nèi)存內(nèi)的操作不會(huì)成為影響Redis性能的瓶頸米间,主要由以上幾點(diǎn)造就了 Redis 具有很高的吞吐量。