您好,登錄后才能下訂單哦!
Redis基礎(chǔ)結(jié)構(gòu)和緩存策略以及常見(jiàn)緩存問(wèn)題是什么,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
一、常見(jiàn)的緩存策略有哪些
由于不同系統(tǒng)的數(shù)據(jù)訪問(wèn)模式不同,同一種緩存策略很難在不同的數(shù)據(jù)訪問(wèn)模式下取得滿意的性能 緩存策略的分類:
1)、基于公平原則 FIFO(先進(jìn)先出 queue)
2)、基于訪問(wèn)的時(shí)間 LRU (最近最少使用 鏈表)
3)、基于訪問(wèn)頻率 如LFU(最近最不常用)、LRU2、2Q、LIRS
4)、訪問(wèn)時(shí)間與頻率兼顧:通過(guò)兼顧訪問(wèn)時(shí)間和頻率。使得數(shù)據(jù)模式在變化時(shí)緩存策略仍有較好性能。如FBR、LRUF、ALRFU。多數(shù)此類算法具有一個(gè)可調(diào)或自適應(yīng)參數(shù),通過(guò)該參數(shù)的調(diào)節(jié)使緩存策略在基于訪問(wèn)時(shí)間與頻率間取得一個(gè)平衡
5)、基于訪問(wèn)模式:某些應(yīng)用有較明確的數(shù)據(jù)訪問(wèn)特點(diǎn),進(jìn)而產(chǎn)生與其相適應(yīng)的緩存策略。如專用的VoD系統(tǒng)設(shè)計(jì)的A&L緩存策略,同時(shí)適應(yīng)隨機(jī)、順序兩種訪問(wèn)模式的SARC策略
二、Redis緩存淘汰策略
當(dāng)maxmemory限制達(dá)到的時(shí)候Redis會(huì)使用的行為由 Redis的maxmemory-policy配置指令來(lái)進(jìn)行配置。
以下的策略是可用的:
noeviction:返回錯(cuò)誤,當(dāng)內(nèi)存限制達(dá)到并且客戶端嘗試執(zhí)行分配更多內(nèi)存的命令時(shí)(大部分的寫(xiě)入指令,但DEL和幾個(gè)例外)
allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的數(shù)據(jù)有空間存放。
volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限于在過(guò)期集合的鍵,使得新添加的數(shù)據(jù)有空間存放。
allkeys-random: 回收隨機(jī)的鍵使得新添加的數(shù)據(jù)有空間存放。
volatile-random: 回收隨機(jī)的鍵使得新添加的數(shù)據(jù)有空間存放,但僅限于在過(guò)期集合的鍵。
volatile-ttl: 回收在過(guò)期集合的鍵,并且優(yōu)先回收存活時(shí)間(TTL)較短的鍵,使得新添加的數(shù)據(jù)有空間存放。
如果沒(méi)有鍵滿足回收的前提條件的話,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
選擇正確的回收策略是非常重要的,這取決于你的應(yīng)用的訪問(wèn)模式,不過(guò)你可以在運(yùn)行時(shí)進(jìn)行相關(guān)的策略調(diào)整,并且監(jiān)控緩存命中率和沒(méi)命中的次數(shù),通過(guò)RedisINFO命令輸出以便調(diào)優(yōu)。
一般的經(jīng)驗(yàn)規(guī)則:
使用allkeys-lru策略:當(dāng)你希望你的請(qǐng)求符合一個(gè)冪定律分布,也就是說(shuō),你希望部分的子集元素將比其它其它元素被訪問(wèn)的更多。如果你不確定選擇什么,這是個(gè)很好的選擇。.
使用allkeys-random:如果你是循環(huán)訪問(wèn),所有的鍵被連續(xù)的掃描,或者你希望請(qǐng)求分布正常(所有元素被訪問(wèn)的概率都差不多)。
使用volatile-ttl:如果你想要通過(guò)創(chuàng)建緩存對(duì)象時(shí)設(shè)置TTL值,來(lái)決定哪些對(duì)象應(yīng)該被過(guò)期。
allkeys-lru 和 volatile-random策略對(duì)于當(dāng)你想要單一的實(shí)例實(shí)現(xiàn)緩存及持久化一些鍵時(shí)很有用。不過(guò)一般運(yùn)行兩個(gè)實(shí)例是解決這個(gè)問(wèn)題的更好方法。
為了鍵設(shè)置過(guò)期時(shí)間也是需要消耗內(nèi)存的,所以使用allkeys-lru這種策略更加高效,因?yàn)闆](méi)有必要為鍵取設(shè)置過(guò)期時(shí)間當(dāng)內(nèi)存有壓力時(shí)。
三、如何做到緩存數(shù)據(jù)一致性
數(shù)據(jù)不一致性產(chǎn)生的原因
【1】、先操作刪除緩存,再寫(xiě)數(shù)據(jù)庫(kù)成功之前,如果有讀請(qǐng)求發(fā)生,可能導(dǎo)致舊數(shù)據(jù)入緩存,引發(fā)數(shù)據(jù)不一致。如果不采用給緩存設(shè)置過(guò)期時(shí)間策略,該數(shù)據(jù)永遠(yuǎn)都是臟數(shù)據(jù)。
【解決辦法】:
1)、可采用更新前后雙刪除緩存策略。
2)、可以通過(guò)“串行化”解決,保證同一個(gè)數(shù)據(jù)的讀寫(xiě)落在同一個(gè)后端服務(wù)上。
【2】、先操作數(shù)據(jù)庫(kù),再清除緩存。如果刪緩存失敗了,就會(huì)出現(xiàn)數(shù)據(jù)不一致問(wèn)題。
【解決辦法】:
1)、將刪除失敗的key值存入隊(duì)列中重復(fù)刪除。
2)、方案二:通過(guò)訂閱binlog獲取需要重新刪除的Key值數(shù)據(jù)。在應(yīng)用程序中,另起一段程序,獲得這個(gè)訂閱程序傳來(lái)的消息,進(jìn)行刪除緩存操作。
四、防止緩存穿透、擊穿、雪崩和刷新
【1】、緩存穿透:緩存穿透是說(shuō)收到一個(gè)請(qǐng)求,但是該請(qǐng)求緩存中不存在,只能去數(shù)據(jù)庫(kù)中查詢,然后放進(jìn)緩存。但當(dāng)有好多請(qǐng)求同時(shí)訪問(wèn)同一個(gè)數(shù)據(jù)時(shí),業(yè)務(wù)系統(tǒng)把這些請(qǐng)求全發(fā)到了數(shù)據(jù)庫(kù);或者惡意構(gòu)造一個(gè)邏輯上不存在的數(shù)據(jù),然后大量發(fā)送這個(gè)請(qǐng)求,這樣每次都會(huì)被發(fā)送到數(shù)據(jù)庫(kù),最總導(dǎo)致數(shù)據(jù)庫(kù)掛掉。 解決的辦法:對(duì)于惡意訪問(wèn),一種思路是先做校驗(yàn),對(duì)惡意數(shù)據(jù)直接過(guò)濾掉,不要發(fā)送至數(shù)據(jù)庫(kù)層;第二種思路是緩存空結(jié)果,就是對(duì)查詢不存在的數(shù)據(jù)也記錄在緩存中,這樣就可以有效的減少查詢數(shù)據(jù)庫(kù)的次數(shù)。非惡意訪問(wèn),結(jié)合緩存擊穿說(shuō)明。
【2】、緩存擊穿:上面提到的某個(gè)數(shù)據(jù)沒(méi)有,然后好多請(qǐng)求查詢數(shù)據(jù)庫(kù),可以歸為緩存擊穿的范疇:對(duì)于熱點(diǎn)數(shù)據(jù),當(dāng)緩存失效的一瞬間,所有的請(qǐng)求都被下放到數(shù)據(jù)庫(kù)去請(qǐng)求更新緩存 解決的辦法:防范此類問(wèn)題,一種思路是加全局鎖,就是所有訪問(wèn)某個(gè)數(shù)據(jù)的請(qǐng)求都共享一個(gè)鎖,獲得鎖的那個(gè)才有資格去訪問(wèn)數(shù)據(jù)庫(kù),其他線程必須等待。另一種思想是對(duì)即將過(guò)期的數(shù)據(jù)進(jìn)行主動(dòng)刷新,比如新起一個(gè)線程輪詢數(shù)據(jù),或者比如把所有的數(shù)據(jù)劃分為不同的緩存區(qū)間,定期分區(qū)間刷新數(shù)據(jù)。第二個(gè)思路與緩存雪崩有點(diǎn)關(guān)系。
【3】、緩存雪崩:緩存雪崩是指當(dāng)我們給所有的緩存設(shè)置了同樣的過(guò)期時(shí)間,當(dāng)某一時(shí)刻,整個(gè)緩存的數(shù)據(jù)全部過(guò)期了,然后瞬間所有的請(qǐng)求都被拋向了數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)就崩掉了。 解決的辦法:解決思路要么是分治,劃分更小的緩存區(qū)間,按區(qū)間過(guò)期;要么給每個(gè)key的過(guò)期時(shí)間加一個(gè)隨機(jī)值,避免同時(shí)過(guò)期,達(dá)到錯(cuò)峰刷新緩存的目的。
【4】、緩存刷新:既清空緩存 ,一般在insert、update、delete操作后就需要刷新緩存,如果不執(zhí)行就會(huì)出現(xiàn)臟數(shù)據(jù)。但當(dāng)緩存請(qǐng)求的系統(tǒng)蹦掉后,返回給緩存的值為null。
五、redis與memcached區(qū)別
【1】、工作原理:
redis是單進(jìn)程操作命令,memcached為多進(jìn)程
【2】、效率差異:
redis對(duì)于小數(shù)據(jù)(<100K)處理較高,而memcached由于多進(jìn)程對(duì)于大數(shù)據(jù)處理更有優(yōu)勢(shì)
【3】、支持的結(jié)構(gòu):
redis支持事務(wù),Memcached僅支持簡(jiǎn)單的key-value結(jié)構(gòu)的數(shù)據(jù)記錄,Redis支持String、Hash、List、Set和Sorted Set
【4】、內(nèi)存管理機(jī)制:
Redis可以設(shè)置內(nèi)存滿時(shí)緩存到磁盤(pán),并有數(shù)據(jù)保存機(jī)制(RDB、AOF)
memcached把固定大?。?MB)的內(nèi)存分為n小塊,以1.25倍增大,存儲(chǔ)時(shí)選擇最小可放入的塊存放數(shù)據(jù),好處是不會(huì)頻繁申請(qǐng)內(nèi)存,提高IO效率,壞處是會(huì)有一定的內(nèi)存浪費(fèi)。
redis會(huì)按數(shù)據(jù)大小分配內(nèi)存塊,并將內(nèi)存塊大小記錄下來(lái),方便回收
六、Redis數(shù)據(jù)結(jié)構(gòu)
【1】、Redis對(duì)象底層數(shù)據(jù)結(jié)構(gòu)共有八種:
編碼常量 | 編碼所對(duì)應(yīng)的底層數(shù)據(jù)結(jié)構(gòu) |
REDIS_ENCODING_INT | long 類型的整數(shù) |
REDIS_ENCODING_EMBSTR | embstr 編碼的簡(jiǎn)單動(dòng)態(tài)字符串 |
REDIS_ENCODING_RAW | 簡(jiǎn)單動(dòng)態(tài)字符串 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 雙端鏈表 |
REDIS_ENCODING_ZIPLIST | 壓縮列表 |
REDIS_ENCODING_INTSET | 整數(shù)集合 |
REDIS_ENCODING_SKIPLIST | 跳躍表和字典 |
【2】、Redis對(duì)象和底層數(shù)據(jù)結(jié)構(gòu)的關(guān)系
1)、String對(duì)象:
字符串對(duì)象的編碼可以是int、raw 或者embstr (3.0新增) 一個(gè)字符串的內(nèi)容可以轉(zhuǎn)換為long就用 int結(jié)構(gòu)。
如果字符串對(duì)象的長(zhǎng)度小于39字節(jié),就用 embstr對(duì)象。否則用傳統(tǒng)的raw對(duì)象
embstr的好處有如下幾點(diǎn):
embstr的創(chuàng)建只需分配一次內(nèi)存,而raw為兩次(一次為sds分配對(duì)象,另一次為objet分配對(duì)象,embstr省去了第一次)。
相對(duì)地,釋放內(nèi)存的次數(shù)也由兩次變?yōu)橐淮巍?/p>
embstr的objet和sds放在一起,更好地利用緩存帶來(lái)的優(yōu)勢(shì)。
2)、列表對(duì)象:
列表對(duì)象的編碼可以是ziplist或者linkedlist
ziplist是一種壓縮鏈表,它的好處是更能節(jié)省內(nèi)存空間,因?yàn)樗鎯?chǔ)的內(nèi)容都是在連續(xù)的內(nèi)存區(qū)域當(dāng)中的。當(dāng)列表對(duì)象元素不大,每個(gè)元素也不大的時(shí)候,就采用ziplist存儲(chǔ)。但當(dāng)數(shù)據(jù)量過(guò)大時(shí)就ziplist就不是那么好用了。因?yàn)闉榱吮WC他存儲(chǔ)內(nèi)容在內(nèi)存中的連續(xù)性,插入的復(fù)雜度是O(N),即每次插入都會(huì)重新進(jìn)行realloc。
linkedlist是一種雙向鏈表。它的結(jié)構(gòu)比較簡(jiǎn)單,節(jié)點(diǎn)中存放pre和next兩個(gè)指針,還有節(jié)點(diǎn)相關(guān)的信息。當(dāng)每增加一個(gè)node的時(shí)候,就需要重新malloc一塊內(nèi)存。
3)、哈希對(duì)象:
哈希對(duì)象的底層實(shí)現(xiàn)可以是ziplist或者h(yuǎn)ashtable
ziplist中的哈希對(duì)象是按照key1,value1,key2,value2這樣的順序存放來(lái)存儲(chǔ)的。當(dāng)對(duì)象數(shù)目不多且內(nèi)容不大時(shí),這種方式效率是很高的。
hashtable的是由dict這個(gè)結(jié)構(gòu)來(lái)實(shí)現(xiàn)的
typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict;
dict是一個(gè)字典,其中的指針dicht ht[2] 指向了兩個(gè)哈希表
typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht;
dicht[0] 是用于真正存放數(shù)據(jù),dicht[1]一般在哈希表元素過(guò)多進(jìn)行rehash的時(shí)候用于中轉(zhuǎn)數(shù)據(jù)
dictht中的table用于真正存放元素了,每個(gè)key/value對(duì)用一個(gè)dictEntry表示,放在dictEntry數(shù)組中
4)、集合對(duì)象:
集合對(duì)象的編碼可以是intset或者h(yuǎn)ashtable
intset是一個(gè)整數(shù)集合,里面存的為某種同一類型的整數(shù)
intset是一個(gè)有序集合,查找元素的復(fù)雜度為O(logN),但插入時(shí)不一定為O(logN),因?yàn)橛锌赡苌婕暗缴?jí)操作。比如當(dāng)集合里全是int16_t型的整數(shù),這時(shí)要插入一個(gè)int32_t,那么為了維持集合中數(shù)據(jù)類型的一致,那么所有的數(shù)據(jù)都會(huì)被轉(zhuǎn)換成int32_t類型,涉及到內(nèi)存的重新分配,這時(shí)插入的復(fù)雜度就為O(N)了。是intset不支持降級(jí)操作。
5)、有序集合對(duì)象:
有序集合的編碼可能兩種,一種是ziplist,另一種是skiplist與dict的結(jié)合。
ziplist作為集合和作為哈希對(duì)象是一樣的,member和score順序存放。按照score從小到大順序排列。它的結(jié)構(gòu)不再?gòu)?fù)述。
skiplist是一種跳躍表,它實(shí)現(xiàn)了有序集合中的快速查找,在大多數(shù)情況下它的速度都可以和平衡樹(shù)差不多。但它的實(shí)現(xiàn)比較簡(jiǎn)單,可以作為平衡樹(shù)的替代品
關(guān)于Redis基礎(chǔ)結(jié)構(gòu)和緩存策略以及常見(jiàn)緩存問(wèn)題是什么問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。