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