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