redis单线程原理___Redis为何那么快-----底层原理浅析
redis單線程原理
redis單線程問題
單線程指的是網(wǎng)絡(luò)請求模塊使用了一個線程(所以不需考慮并發(fā)安全性),即一個線程處理所有網(wǎng)絡(luò)請求,其他模塊仍用了多個線程。
1. 為什么說redis能夠快速執(zhí)行
(1) 絕大部分請求是純粹的內(nèi)存操作(非常快速)
(2) 采用單線程,避免了不必要的上下文切換和競爭條件
(3) 非阻塞IO - IO多路復(fù)用
2. redis的內(nèi)部實(shí)現(xiàn)
內(nèi)部實(shí)現(xiàn)采用epoll,采用了epoll+自己實(shí)現(xiàn)的簡單的事件框架。epoll中的讀、寫、關(guān)閉、連接都轉(zhuǎn)化成了事件,然后利用epoll的多路復(fù)用特性,絕不在io上浪費(fèi)一點(diǎn)時間 這3個條件不是相互獨(dú)立的,特別是第一條,如果請求都是耗時的,采用單線程吞吐量及性能可想而知了。應(yīng)該說redis為特殊的場景選擇了合適的技術(shù)方案。
3. Redis關(guān)于線程安全問題
redis實(shí)際上是采用了線程封閉的觀念,把任務(wù)封閉在一個線程,自然避免了線程安全問題,不過對于需要依賴多個redis操作的復(fù)合操作來說,依然需要鎖,而且有可能是分布式鎖。
4. IO多路復(fù)用
參考:https://www.zhihu.com/question/32163005
要弄清問題先要知道問題的出現(xiàn)原因
原因:
由于進(jìn)程的執(zhí)行過程是線性的(也就是順序執(zhí)行),當(dāng)我們調(diào)用低速系統(tǒng)I/O(read,write,accept等等),進(jìn)程可能阻塞,此時進(jìn)程就阻塞在這個調(diào)用上,不能執(zhí)行其他操作.阻塞很正常.
接下來考慮這么一個問題:一個服務(wù)器進(jìn)程和一個客戶端進(jìn)程通信,服務(wù)器端read(sockfd1,bud,bufsize),此時客戶端進(jìn)程沒有發(fā)送數(shù)據(jù),那么read(阻塞調(diào)用)將阻塞,直到客戶端調(diào)用write(sockfd,but,size)發(fā)來數(shù)據(jù).在一個客戶和服務(wù)器通信時這沒什么問題;
當(dāng)多個客戶與服務(wù)器通信時當(dāng)多個客戶與服務(wù)器通信時,若服務(wù)器阻塞于其中一個客戶sockfd1,當(dāng)另一個客戶的數(shù)據(jù)到達(dá)套接字sockfd2時,服務(wù)器不能處理,仍然阻塞在read(sockfd1,…)上;此時問題就出現(xiàn)了,不能及時處理另一個客戶的服務(wù),咋么辦?
I/O多路復(fù)用來解決!
I/O多路復(fù)用:
繼續(xù)上面的問題,有多個客戶連接,sockfd1,sockfd2,sockfd3…sockfdn同時監(jiān)聽這n個客戶,當(dāng)其中有一個發(fā)來消息時就從select的阻塞中返回,然后就調(diào)用read讀取收到消息的sockfd,然后又循環(huán)回select阻塞;這樣就不會因?yàn)樽枞谄渲幸粋€上而不能處理另一個客戶的消息
“I/O多路復(fù)用”的英文是“I/O multiplexing”,可以百度一下multiplexing,就能得到這個圖:
Q:
那這樣子,在讀取socket1的數(shù)據(jù)時,如果其它socket有數(shù)據(jù)來,那么也要等到socket1讀取完了才能繼續(xù)讀取其它socket的數(shù)據(jù)吧。那不是也阻塞住了嗎?而且讀取到的數(shù)據(jù)也要開啟線程處理吧,那這和多線程IO有什么區(qū)別呢?
A:
1.CPU本來就是線性的不論什么都需要順序處理并行只能是多核CPU
2.io多路復(fù)用本來就是用來解決對多個I/O監(jiān)聽時,一個I/O阻塞影響其他I/O的問題,跟多線程沒關(guān)系.
3.跟多線程相比較,線程切換需要切換到內(nèi)核進(jìn)行線程切換,需要消耗時間和資源.而I/O多路復(fù)用不需要切換線/進(jìn)程,效率相對較高,特別是對高并發(fā)的應(yīng)用nginx就是用I/O多路復(fù)用,故而性能極佳.但多線程編程邏輯和處理上比I/O多路復(fù)用簡單.而I/O多路復(fù)用處理起來較為復(fù)雜.
5. 使用Redis有哪些好處?
(1) 速度快,因?yàn)閿?shù)據(jù)存在內(nèi)存中,類似于HashMap,HashMap的優(yōu)勢就是查找和操作的時間復(fù)雜度都是O(1)
(2) 支持豐富數(shù)據(jù)類型,支持string,list,set,sorted set,hash
(3) 支持事務(wù),操作都是原子性,所謂的原子性就是對數(shù)據(jù)的更改要么全部執(zhí)行,要么全部不執(zhí)行
(4) 豐富的特性:可用于緩存,消息,按key設(shè)置過期時間,過期后將會自動刪除
6. Redis相比memcached有哪些優(yōu)勢?
(1) memcached所有的值均是簡單的字符串,redis作為其替代者,支持更為豐富的數(shù)據(jù)類型
(2) redis的速度比memcached快很多
(3) redis可以持久化其數(shù)據(jù)
(4)Redis支持?jǐn)?shù)據(jù)的備份,即master-slave模式的數(shù)據(jù)備份。
(5) 使用底層模型不同,它們之間底層實(shí)現(xiàn)方式 以及與客戶端之間通信的應(yīng)用協(xié)議不一樣。Redis直接自己構(gòu)建了VM 機(jī)制 ,因?yàn)橐话愕南到y(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會浪費(fèi)一定的時間去移動和請求。
(6)value大小:redis最大可以達(dá)到1GB,而memcache只有1MB
7. Redis常見性能問題和解決方案:
(1) Master最好不要做任何持久化工作,如RDB內(nèi)存快照和AOF日志文件;(Master寫內(nèi)存快照,save命令調(diào)度rdbSave函數(shù),會阻塞主線程的工作,當(dāng)快照比較大時對性能影響是非常大的,會間斷性暫停服務(wù),所以Master最好不要寫內(nèi)存快照;AOF文件過大會影響Master重啟的恢復(fù)速度)
(2) 如果數(shù)據(jù)比較重要,某個Slave開啟AOF備份數(shù)據(jù),策略設(shè)置為每秒同步一次
(3) 為了主從復(fù)制的速度和連接的穩(wěn)定性,Master和Slave最好在同一個局域網(wǎng)內(nèi)
(4) 盡量避免在壓力很大的主庫上增加從庫
(5) 主從復(fù)制不要用圖狀結(jié)構(gòu),用單向鏈表結(jié)構(gòu)更為穩(wěn)定,即:Master <- Slave1 <- Slave2 <- Slave3…;這樣的結(jié)構(gòu)方便解決單點(diǎn)故障問題,實(shí)現(xiàn)Slave對Master的替換。如果Master掛了,可以立刻啟用Slave1做Master,其他不變。
8. Redis的回收策略
volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選最近最少使用的數(shù)據(jù)淘汰
allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù)
注意這里的6種機(jī)制,volatile和allkeys規(guī)定了是對已設(shè)置過期時間的數(shù)據(jù)集淘汰數(shù)據(jù)還是從全部數(shù)據(jù)集淘汰數(shù)據(jù),后面的lru、ttl以及random是三種不同的淘汰策略,再加上一種no-enviction永不回收的策略。
使用策略規(guī)則:
1、如果數(shù)據(jù)呈現(xiàn)冪律分布,也就是一部分?jǐn)?shù)據(jù)訪問頻率高,一部分?jǐn)?shù)據(jù)訪問頻率低,則使用allkeys-lru
2、如果數(shù)據(jù)呈現(xiàn)平等分布,也就是所有的數(shù)據(jù)訪問頻率都相同,則使用allkeys-random
9. 五種I/O模型介紹
IO 多路復(fù)用是5種I/O模型中的第3種,對各種模型講個故事,描述下區(qū)別:
故事情節(jié)為:老李去買火車票,三天后買到一張退票。參演人員(老李,黃牛,售票員,快遞員),往返車站耗費(fèi)1小時。
1.阻塞I/O模型
老李去火車站買票,排隊(duì)三天買到一張退票。
耗費(fèi):在車站吃喝拉撒睡 3天,其他事一件沒干。
2.非阻塞I/O模型
老李去火車站買票,隔12小時去火車站問有沒有退票,三天后買到一張票。
耗費(fèi):往返車站6次,路上6小時,其他時間做了好多事。
3.I/O復(fù)用模型
1.select/poll
老李去火車站買票,委托黃牛,然后每隔6小時電話黃牛詢問,黃牛三天內(nèi)買到票,然后老李去火車站交錢領(lǐng)票。
耗費(fèi):往返車站2次,路上2小時,黃牛手續(xù)費(fèi)100元,打電話17次
2.epoll
老李去火車站買票,委托黃牛,黃牛買到后即通知老李去領(lǐng),然后老李去火車站交錢領(lǐng)票。
耗費(fèi):往返車站2次,路上2小時,黃牛手續(xù)費(fèi)100元,無需打電話
4.信號驅(qū)動I/O模型
老李去火車站買票,給售票員留下電話,有票后,售票員電話通知老李,然后老李去火車站交錢領(lǐng)票。
耗費(fèi):往返車站2次,路上2小時,免黃牛費(fèi)100元,無需打電話
5.異步I/O模型
老李去火車站買票,給售票員留下電話,有票后,售票員電話通知老李并快遞送票上門。
耗費(fèi):往返車站1次,路上1小時,免黃牛費(fèi)100元,無需打電話
1同2的區(qū)別是:自己輪詢
2同3的區(qū)別是:委托黃牛
3同4的區(qū)別是:電話代替黃牛
4同5的區(qū)別是:電話通知是自取還是送票上門
Redis為何那么快-----底層原理淺析
Redis的快速很多人都知道是因?yàn)榛趦?nèi)存,但這只是一方面,其實(shí)redis在底層是一套很完善的多路復(fù)用事件處理機(jī)制來保證執(zhí)行的高效的
線程模型
redis內(nèi)部使用文件事件處理器file event handler,它包含如下幾個部分
- 多個socket
- IO多路復(fù)用程序
- 文件事件分派器
- 事件處理器(連接應(yīng)答處理器,命令請求處理器,命令回復(fù)處理器)
之所以說redis是單線程其實(shí)是指這個文件事件處理器是單線程的,它采用多路復(fù)用的方式監(jiān)聽系統(tǒng)上多個socket,將socket上產(chǎn)生的事件壓入隊(duì)列中,由文件事件分派器從隊(duì)列中取出一個socket根據(jù)事件類型發(fā)給相應(yīng)的事件處理器
整個處理過程如圖:
處理過程可以分為以下幾個步驟:
- 客戶端向redis發(fā)起一個socket請求,向redis的server socket請求連接,這里命名為socket01
- server socket產(chǎn)生一個AE_READABLE事件,IO多路復(fù)用程序監(jiān)聽到事件后將這個socket01壓入隊(duì)列
- 文件事件分派器從隊(duì)列中取出socket01,交給連接應(yīng)答處理器
- 連接應(yīng)答處理器會將socket01的AE_READABLE事件與命令請求處理器相關(guān)聯(lián)
- 假設(shè)客戶端執(zhí)行set操作,這時命令請求處理器會從socket01讀取key value,在內(nèi)存中完成key value的設(shè)置
- 在內(nèi)存中完成設(shè)置后,會將socket01的AE_WRITEABLE事件與命令回復(fù)處理器相關(guān)聯(lián),然后壓入隊(duì)列中
- 事件分派器拿到socket01后,交給命令回復(fù)處理器,由命令回復(fù)處理器向socket01寫入本次操作的結(jié)果,比如OK,之后解除關(guān)聯(lián)
以上就是一個命令在redis中執(zhí)行的過程
總結(jié)一下效率高的原因
Redis內(nèi)部原理簡介
知道了Redis的各種數(shù)據(jù)結(jié)構(gòu),對象結(jié)構(gòu),那么Redis是如何保存數(shù)據(jù)的,又是如何操作數(shù)據(jù)的呢,Redis里面的命令是怎么實(shí)現(xiàn)的呢?這一系列問題值得我們思考
一.Redis維護(hù)多個數(shù)據(jù)庫
Redis內(nèi)部維護(hù)一個db數(shù)組,每個db都是一個數(shù)據(jù)庫,默認(rèn)情況下Redis會創(chuàng)建16個數(shù)據(jù)庫。我們可以通過select命令來切換數(shù)據(jù)庫,如select1切換到數(shù)據(jù)庫號為1的數(shù)據(jù)庫。select實(shí)現(xiàn)是通過修改客戶端的db指針,通過指針指向不同的數(shù)據(jù)庫來實(shí)現(xiàn)數(shù)據(jù)庫的切換操作的。
需要注意的是,為了不造成操作數(shù)據(jù)庫號錯誤,最好執(zhí)行命令之前,手動select一下數(shù)據(jù)庫。
二.數(shù)據(jù)庫鍵空間
Redis是一個鍵值對數(shù)據(jù)庫服務(wù)器,Redis通過字典保存了數(shù)據(jù)庫中的所有鍵值對,我們將這個字典稱為鍵空間。鍵空間的每個鍵都是一個字符串對象,鍵空間的值也就是數(shù)據(jù)庫的值,可以是字符串對象,列表對象,哈希表對象,集合對象,有序集合對象中的任何一種。
1.添加新鍵
每次添加一個新鍵就是將一個新鍵值對添加到鍵空間里面,其中鍵為字符串對象,值為任意一種類型的Redis對象。
2.刪除鍵
刪除鍵就是在鍵空間里刪除鍵所對應(yīng)的鍵值對對象。
3.更新鍵
更新鍵就是對鍵空間里面鍵所對應(yīng)的值對象進(jìn)行更新。
4.查找鍵
查找鍵就是在鍵空間中取出鍵所對應(yīng)的值對象。
每次在鍵空間讀取一個鍵之后,服務(wù)器會更新鍵的LRU時間,用于計(jì)算鍵的閑置時間。如果服務(wù)器在讀取一個鍵時發(fā)現(xiàn)該鍵已經(jīng)過期,那么服務(wù)器會先刪除這個過期鍵,然后才執(zhí)行后續(xù)操作。如果有客戶端使用watch命令監(jiān)視了某個鍵,那么服務(wù)器在對被監(jiān)視的鍵進(jìn)行修改之后,會將這個鍵標(biāo)記為dirty,從而讓事務(wù)注意到這個鍵被修改過。服務(wù)器每次修改一個鍵之后,都會對鍵計(jì)數(shù)器的值+1,這個計(jì)數(shù)器用來觸發(fā)服務(wù)器的持久化操作。如果服務(wù)器開啟了數(shù)據(jù)庫通知功能,那么在對鍵進(jìn)行修改之后,服務(wù)器將按配置發(fā)送相應(yīng)的數(shù)據(jù)庫通知。
三.設(shè)置鍵的生存時間和過期時間
我們知道expire命令或者pexpire命令可以對一個鍵設(shè)置生存時間,經(jīng)過指定的時間之后,服務(wù)器會自動刪除生存時間為0的鍵。那么Redis是如何實(shí)現(xiàn)刪除過期鍵的操作的呢?
Redis有四個命令可以設(shè)置鍵的過期時間,包括expire,pexpire,expireat,pexpireat,不過這四個命令最后都會轉(zhuǎn)化成pexpireat命令來實(shí)現(xiàn)。
Redis使用一個過期字典記錄所有帶過期時間的鍵,字典的鍵指向鍵空間中的某個鍵對象,字典的值是一個longlong類型的整數(shù),這個證書保存了鍵空間所指向的數(shù)據(jù)庫鍵的過期時間。通過過期字典,程序可以檢查一個給定鍵是否過期,檢查給定鍵是否存在于過期字典,如果存在,取得鍵的過期時間,檢查當(dāng)前時間戳是否大于鍵的過期時間,如果是的話,鍵已經(jīng)過期,否則鍵未過期。
四.過期鍵的刪除策略
如果一個鍵過期了,那么它什么時候被刪除呢?通常我們可以用三種刪除策略刪除過期鍵
1.定時刪除:在設(shè)置鍵過期時間的同時,創(chuàng)建一個定時器,讓定時器在鍵的過期時間來臨時,刪除鍵
2.惰性刪除:放任鍵過期不管,但是每次動鍵空間獲取鍵時,都會檢查鍵是否過期,如果過期,則刪除。
3.定期刪除:每隔一段時間,程序就對數(shù)據(jù)庫進(jìn)行一次檢查,刪除里面的過期鍵。
這幾種方式各有利有弊,首先定時刪除對內(nèi)存最友好,當(dāng)一個鍵過期時,一定會刪除這個鍵,釋放內(nèi)存。不過定時刪除對CPU最不友好,在過期鍵比較多的情況下,刪除過期鍵這一行為可能會占用相當(dāng)一部分CPU時間。此外,創(chuàng)建定時器需要用到Redis服務(wù)器中的時間時間,而當(dāng)前時間時間的實(shí)現(xiàn)方式-無序鏈表查找一個事件的時間復(fù)雜度為O(N),不能高效地處理大量時間事件。
惰性刪除策略對CPU是最友好的,但是對內(nèi)存最不友好。如果一個鍵已經(jīng)過期,這個鍵又保留在數(shù)據(jù)庫中,那么內(nèi)存就會一直占用不釋放。
定期刪除算是前兩種策略的一種整合和折中,定期策略每隔一段時間執(zhí)行一次刪除過期鍵操作,并通過限制刪除操作執(zhí)行的時長和頻率減少刪除操作對CPU時間的影響。定期刪除過期鍵可以有效地減少因?yàn)檫^期鍵帶來的內(nèi)存浪費(fèi)。
Redis過期鍵的刪除使用惰性刪除和定期刪除兩種策略配合使用。惰性策略比較好理解,所有讀寫數(shù)據(jù)庫的命令執(zhí)行之前都會對輸入鍵進(jìn)行檢查,如果鍵過期,那么從數(shù)據(jù)庫中刪除鍵。定期刪除策略的實(shí)現(xiàn)由Redis的serverCron函數(shù)來執(zhí)行,這個函數(shù)每100ms執(zhí)行一次,它在規(guī)定的時間內(nèi),分多次遍歷服務(wù)器中的各個數(shù)據(jù)庫,從數(shù)據(jù)庫的expires字典中隨機(jī)檢查一部分鍵的過期時間,刪除其中的過期鍵。
五.復(fù)制功能對過期鍵的處理
Redis復(fù)制主要包括RDB復(fù)制和AOF復(fù)制,在RDB復(fù)制中,每次執(zhí)行SAVE或BGSAVE命令創(chuàng)建一個新的RDB文件時,程序會對數(shù)據(jù)庫中的鍵進(jìn)行檢查,已過期的鍵不會被保存到新創(chuàng)建的RDB文件中。載入RDB文件時,服務(wù)器也會對保存的鍵進(jìn)行檢查,如果鍵已過期,則不會載入。當(dāng)使用AOF持久化模式運(yùn)行時,當(dāng)過期鍵被惰性刪除或者定期刪除之后,程序會向AOF文件追加一條刪除命令,記錄鍵已被刪除。
總結(jié)
以上是生活随笔為你收集整理的redis单线程原理___Redis为何那么快-----底层原理浅析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 直播报名 | 科技赋能零售金融业务转型
- 下一篇: @SuppressWarnings使用的