Redis,一文帶你領(lǐng)略它的方方面面

1. 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)及其應(yīng)用場景

Redis的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)分別為:String、Hash、List、Set、Zset。

  • String: 字符串

  • Hash: 散列

  • List: 列表

  • Set: 集合

  • Sorted Set(Zset): 有序集合

1.1 String

key都是字符串,value可以是五種數(shù)據(jù)類型。

使用場景:

  • 緩存;

  • 計數(shù)器:incr key,對應(yīng)鍵值自增1,如果key不存在,自增后get(key)=1,由于是單線程無競爭,為此不會出錯??梢詰?yīng)用于網(wǎng)站記錄每個用戶個人主頁的訪問量,一定時間后再將訪問量持久到數(shù)據(jù)庫中,這樣就不用每次多一個人訪問就修改一次數(shù)據(jù)庫中的訪問量值,提高性能;

  • 分布式鎖:setnx key value,key不存在時,才生效,設(shè)置成功返回1,失敗則返回0。根據(jù)返回值確定當(dāng)前線程是否獲得鎖;

  • 簡單分布式id生成:利用incr的自增實現(xiàn)分布式應(yīng)用id唯一。

1.2 Hash

key為字符串,值分為兩部分field和value,視為屬性和值??梢园裬ey當(dāng)作一張表的一行,Key就代表一個id,每個屬性可以看作關(guān)系型數(shù)據(jù)庫的一個字段。fields不能相同,value可以。

image.png

1.3 List

key是字符串,value是一個有序的list。特點是有序、可以重復(fù)。Redis列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)一個列表最多可以包含 2^32 - 1 個元素 (4294967295, 每個列表超過40億個元素)。

為了說明其應(yīng)用場景,我們先了解下主要的幾個命令:

命令格式 描述
LTRIM key start stop 對一個列表進行修剪(trim),就是說,讓列表只保留指定區(qū)間內(nèi)的元素,不在指定區(qū)間之內(nèi)的元素都將被刪除。
LPOP key 移出并獲取列表的第一個元素
RPOP key 移除列表的最后一個元素,返回值為移除的元素。
LPUSH key 將一個或多個值插入到列表頭部
RPUSH key 將一個或多個值插入到列表尾部
RPOPLPUSH source des 從源列表中彈出最后一個元素,將彈出的元素插入到目標列表頭部并返回它;
BRPOP key timeout 移出并獲取列表的最后一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止。前面加的B其實為Blocking的首字母。

應(yīng)用場景:

  • LPUSH + LPOP = Stack (棧);

  • LPUSH + RPOP = Queue(隊列);

  • LPUSH + LTRIM = Capped Collection(固定集合。對于大小固定,我們可以想象其就像一個環(huán)形隊列,當(dāng)集合空間用完后,再插入的元素就會覆蓋最初始的頭部的元素);

  • LPUSH + BRPOP = Message Quene(消息隊列,利用BRPOP的阻塞性,實現(xiàn)阻塞消息隊列);

  • RPOPLPUSH可應(yīng)用于物流,假如某派送流程為:發(fā)貨->中轉(zhuǎn)A->中轉(zhuǎn)B->送達目的地,當(dāng)商家發(fā)貨后,并送達了中轉(zhuǎn)A,用戶應(yīng)該可以看到已發(fā)貨(即完成了發(fā)貨流程)。此時,派送列表流程下一步應(yīng)該為:中轉(zhuǎn)B->送達目的地,用戶查看列表為發(fā)貨-> 中轉(zhuǎn)A。

1.4 Set

Redis 的 Set 是 String 類型的無序集合。集合成員是唯一的,這就意味著集合中不能出現(xiàn)重復(fù)的數(shù)據(jù)。Redis 中集合是通過哈希表實現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是 O(1)。集合中最大的成員數(shù)為 2^32 - 1 (4294967295, 每個集合可存儲40多億個成員)。

為了說明應(yīng)用場景,我們也先了解以下主要幾個命令:

命令格式 描述
SADD key member1 [member2] 向集合添加一個或多個成員
SPOP key 移除并返回集合中的一個隨機元素
SRANDMEMBER key [count] 返回集合中一個或多個隨機數(shù)
SINTER key1 [key2] 返回給定所有集合的交集

應(yīng)用場景:

  • SADD = Tagging(給用戶添加標簽);

  • SPOP/SRANDMEMBER = Random item(隨機元素,可用于抽獎平臺抽獎等)

  • SADD+ SINTER= social graph (社交相關(guān)應(yīng)用,如微博的共同關(guān)注,QQ的共同好友等)。

1.5 Zset

Redis 有序集合和集合一樣也是string類型元素的集合,且不允許重復(fù)的成員。不同的是每個元素都會關(guān)聯(lián)一個double類型的分數(shù)。redis正是通過分數(shù)來為集合中的成員進行從小到大的排序。有序集合的成員是唯一的,但分數(shù)(score)卻可以重復(fù)。集合是通過哈希表實現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是O(1)。 集合中最大的成員數(shù)為 2^32 - 1 (4294967295, 每個集合可存儲40多億個成員)。

image.png

應(yīng)用場景:

  • 排行榜;

  • 分數(shù)添加和更新;

2. 過期策略

2.1 定時刪除

在設(shè)置key的過期時間的同時,為該key創(chuàng)建一個定時器,讓定時器在key的過期時間來臨時,對key進行刪除;

優(yōu)點:保證內(nèi)存被盡快釋放;

缺點

  • 若過期key很多,刪除這些key會占用很多的CPU時間;

  • 定時器的創(chuàng)建耗時,若為每一個設(shè)置過期時間的key創(chuàng)建一個定時器(將會有大量的定時器產(chǎn)生),性能影響嚴重。

2.2 惰性刪除

key過期的時候不刪除,每次從數(shù)據(jù)庫獲取key的時候去檢查是否過期,若過期,則刪除,返回null。

優(yōu)點:刪除操作只發(fā)生在從數(shù)據(jù)庫取出key的時候發(fā)生,而且只刪除當(dāng)前key,所以對CPU時間的占用是比較少的,而且此時的刪除是已經(jīng)到了非做不可的地步(如果此時還不刪除的話,我們就會獲取到了已經(jīng)過期的key了);

缺點:若大量的key在超出超時時間后,很久一段時間內(nèi),都沒有被獲取過,那么可能發(fā)生內(nèi)存泄露(無用的垃圾占用了大量的內(nèi)存)。

2.3 定期刪除

每隔一段時間執(zhí)行一次刪除(在redis.conf配置文件設(shè)置hz,1s刷新的頻率)過期key操作。需要合理設(shè)置刪除操作的執(zhí)行時長(每次刪除執(zhí)行多長時間)和執(zhí)行頻率(每隔多長時間做一次刪除)。

優(yōu)點

  • 通過限制刪除操作的時長和頻率,來減少刪除操作對CPU時間的占用--處理"定時刪除"的缺點;

  • 定期刪除過期key--處理"惰性刪除"的缺點。

缺點

  • 在內(nèi)存友好方面,不如"定時刪除";

  • 在CPU時間友好方面,不如"惰性刪除"。

2.4 Redis采用的過期策略

redis采用的過期策略為惰性刪除+定期刪除,其兩大流程如下:

  • 惰性刪除流程:

    • 在進行g(shù)et或setnx等操作時,先檢查key是否過期;

    • 若過期,則刪除key,然后執(zhí)行相應(yīng)的操作;

    • 若沒過期,則直接執(zhí)行相應(yīng)操作。

  • 定期刪除流程:

    • 對指定的n個數(shù)據(jù)庫(redis默認的n為16),每一個庫隨機刪除小于等于指定的m個過期key;

    • 遍歷每個數(shù)據(jù)庫,并檢查當(dāng)前庫中的m個key(默認m是20個,即每個庫檢查20個key,相當(dāng)于循環(huán)執(zhí)行20次);

    • 如果當(dāng)前庫沒有一個key設(shè)置了過期時間,則直接執(zhí)行一下個庫的遍歷;

    • 隨機獲取一個設(shè)置了過期時間的key,檢查該key是否過期,如果過期,刪除key;

    • 判斷定期刪除操作是否已經(jīng)達到指定時長,若已經(jīng)達到,直接退出定期刪除。

3. 持久化策略

由于redis是基于內(nèi)存的數(shù)據(jù)庫,其運行時數(shù)據(jù)都保存在內(nèi)存中,在沒有進行持久化數(shù)據(jù)的情況下,一旦redis服務(wù)器關(guān)閉,則會丟失內(nèi)存中的數(shù)據(jù)。為此Redis為我們提供了兩種持久化機制,分別是RDB(Redis DataBase)和AOF(Append Only File)。

3.1 RDB機制

RDB持久化是指在指定的時間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤。也是默認的持久化方式,這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫入到二進制文件中,默認的文件名為dump.rdb。

既然RDB機制是通過把某個時刻的所有數(shù)據(jù)生成一個快照來保存,那么就應(yīng)該有一種觸發(fā)機制,是實現(xiàn)這個過程。對于RDB來說,提供了三種機制:save、bgsave、自動化。我們分別來看一下:

3.1.1 save觸發(fā)方式

該命令會阻塞當(dāng)前Redis服務(wù)器,執(zhí)行save命令期間,Redis不能處理其他命令,直到RDB過程完成為止。具體流程如下:

image.png

執(zhí)行完成時候如果存在老的RDB文件,就用新的替代掉舊的。我們的客戶端可能都是幾萬或者是幾十萬,這種方式顯然不可取。

3.1.2 bgsave觸發(fā)方式

執(zhí)行該命令時,Redis會在后臺異步進行快照操作,快照同時還可以響應(yīng)客戶端請求。具體流程如下:

image.png

具體操作是Redis進程執(zhí)行fork操作創(chuàng)建子進程,RDB持久化過程由子進程負責(zé),完成后自動結(jié)束。阻塞只發(fā)生在fork階段,一般時間很短。基本上 Redis 內(nèi)部所有的RDB操作都是采用 bgsave 命令。

3.1.3 自動觸發(fā)

自動觸發(fā)是由我們的配置文件來完成的。在redis.conf配置文件中,里面有如下配置,我們可以去設(shè)置:

①save:這里是用來配置觸發(fā) Redis的 RDB 持久化條件,也就是什么時候?qū)?nèi)存中的數(shù)據(jù)保存到硬盤。比如“save m n”。表示m秒內(nèi)數(shù)據(jù)集存在n次修改時,自動觸發(fā)bgsave。

默認如下配置:

#表示900 秒內(nèi)如果至少有 1 個 key 的值變化,則保存
save 900 1
#表示300 秒內(nèi)如果至少有 10 個 key 的值變化,則保存
save 300 10
#表示60 秒內(nèi)如果至少有 10000 個 key 的值變化,則保存save 60 10000

不需要持久化,那么你可以注釋掉所有的 save 行來停用保存功能。
②stop-writes-on-bgsave-error :默認值為yes。當(dāng)啟用了RDB且最后一次后臺保存數(shù)據(jù)失敗,Redis是否停止接收數(shù)據(jù)。這會讓用戶意識到數(shù)據(jù)沒有正確持久化到磁盤上,否則沒有人會注意到災(zāi)難(disaster)發(fā)生了。如果Redis重啟了,那么又可以重新開始接收數(shù)據(jù)了
③rdbcompression ;默認值是yes。對于存儲到磁盤中的快照,可以設(shè)置是否進行壓縮存儲。
④rdbchecksum :默認值是yes。在存儲快照后,我們還可以讓redis使用CRC64算法來進行數(shù)據(jù)校驗,但是這樣做會增加大約10%的性能消耗,如果希望獲取到最大的性能提升,可以關(guān)閉此功能。
⑤dbfilename :設(shè)置快照的文件名,默認是 dump.rdb
⑥dir:設(shè)置快照文件的存放路徑,這個配置項一定是個目錄,而不能是文件名。

我們可以修改這些配置來實現(xiàn)我們想要的效果。因為第三種方式是配置的,所以我們對前兩種進行一個對比:

image.png

3.1.4 RDB的優(yōu)劣

①優(yōu)勢
(1)RDB文件緊湊,全量備份,非常適合用于進行備份和災(zāi)難恢復(fù)。
(2)生成RDB文件的時候,redis主進程會fork()一個子進程來處理所有保存工作,主進程不需要進行任何磁盤IO操作。
(3)RDB 在恢復(fù)大數(shù)據(jù)集時的速度比 AOF 的恢復(fù)速度要快。
②劣勢
RDB快照是一次全量備份,存儲的是內(nèi)存數(shù)據(jù)的二進制序列化形式,存儲上非常緊湊。當(dāng)進行快照持久化時,會開啟一個子進程專門負責(zé)快照持久化,子進程會擁有父進程的內(nèi)存數(shù)據(jù),父進程修改內(nèi)存子進程不會反應(yīng)出來,所以在快照持久化期間修改的數(shù)據(jù)不會被保存,可能丟失數(shù)據(jù)。

3.2 AOF機制

全量備份總是耗時的,有時候我們提供一種更加高效的方式AOF,工作機制很簡單,redis會將每一個收到的寫命令都通過write函數(shù)追加到文件中。通俗的理解就是日志記錄。

3.2.1 持久化原理

原理如下圖:

image.png

每當(dāng)有一個寫命令過來時,就直接保存在我們的AOF文件中。

3.2.2 文件重寫原理

AOF的方式也同時帶來了另一個問題。持久化文件會變的越來越大。為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。將內(nèi)存中的數(shù)據(jù)以命令的方式保存到臨時文件中,同時會fork出一條新進程來將文件重寫。

image.png

重寫aof文件的操作,并沒有讀取舊的aof文件,而是將整個內(nèi)存中的數(shù)據(jù)庫內(nèi)容用命令的方式重寫了一個新的aof文件,這點和快照有點類似。

3.2.3 AOF三種觸發(fā)機制

(1)每修改同步(always):同步持久化,每次發(fā)生數(shù)據(jù)變更會被立即記錄到磁盤 性能較差但數(shù)據(jù)完整性比較好;
(2)每秒同步(everysec):異步操作,每秒記錄 如果一秒內(nèi)宕機,有數(shù)據(jù)丟失;
(3)不同(no):從不同步。
[圖片上傳失敗...(image-dabc24-1597293704284)]

3.2.4 AOF優(yōu)劣

①優(yōu)點
(1)AOF可以更好的保護數(shù)據(jù)不丟失,一般AOF會每隔1秒,通過一個后臺線程執(zhí)行一次fsync操作,最多丟失1秒鐘的數(shù)據(jù);
(2)AOF日志文件沒有任何磁盤尋址的開銷,寫入性能非常高,文件不容易破損;
(3)AOF日志文件即使過大的時候,出現(xiàn)后臺重寫操作,也不會影響客戶端的讀寫;
(4)AOF日志文件的命令通過非??勺x的方式進行記錄,這個特性非常適合做災(zāi)難性的誤刪除的緊急恢復(fù)。比如某人不小心用flushall命令清空了所有數(shù)據(jù),只要這個時候后臺rewrite還沒有發(fā)生,那么就可以立即拷貝AOF文件,將最后一條flushall命令給刪了,然后再將該AOF文件放回去,就可以通過恢復(fù)機制,自動恢復(fù)所有數(shù)據(jù)。
②劣勢
(1)對于同一份數(shù)據(jù)來說,AOF日志文件通常比RDB數(shù)據(jù)快照文件更大;
(2)AOF開啟后,支持的寫QPS會比RDB支持的寫QPS低,因為AOF一般會配置成每秒fsync一次日志文件,當(dāng)然,每秒一次fsync,性能也還是很高的
(3)以前AOF發(fā)生過bug,就是通過AOF記錄的日志,進行數(shù)據(jù)恢復(fù)的時候,沒有恢復(fù)一模一樣的數(shù)據(jù)出來。

3.3 兩種機制對比以及兩種機制對過期key的處理

image.png

3.3.1 RDB對過期key的處理

過期key對RDB沒有任何影響。

  • 在從內(nèi)存數(shù)據(jù)庫持久化數(shù)據(jù)到RDB文件:持久化key之前,會檢查是否過期,過期的key不進入RDB文件;

  • 從RDB文件恢復(fù)數(shù)據(jù)到內(nèi)存數(shù)據(jù)庫:數(shù)據(jù)載入數(shù)據(jù)庫之前,會對key先進行過期檢查,如果過期,不導(dǎo)入數(shù)據(jù)庫(主庫情況)。

3.3.2 AOF對過期key的處理

過期key對AOF也沒有任何影響。

  • 從內(nèi)存數(shù)據(jù)庫持久化數(shù)據(jù)到AOF文件:若key過期后,還沒有被刪除,此時進行執(zhí)行持久化操作(該key是不會進入aof文件的,因為沒有發(fā)生修改命令)。若key過期后,在發(fā)生刪除操作時,程序會向aof文件追加一條del命令(在將來的以aof文件恢復(fù)數(shù)據(jù)的時候該過期的鍵就會被刪掉);

  • AOF重寫:重寫時,會先判斷key是否過期,已過期的key不會重寫到aof文件 。

4. Redis的緩存穿透、緩存擊穿、緩存雪崩及其解決方案

4.1 概念

  • 緩存穿透:key對應(yīng)的數(shù)據(jù)在數(shù)據(jù)源并不存在,每次針對此key的請求從緩存獲取不到,請求都會到數(shù)據(jù)源,從而可能壓垮數(shù)據(jù)源。比如用一個不存在的用戶id獲取用戶信息,不論緩存還是數(shù)據(jù)庫都沒有,若黑客利用此漏洞進行攻擊可能壓垮數(shù)據(jù)庫。

  • 緩存擊穿:key對應(yīng)的數(shù)據(jù)存在,但在redis中過期,此時若有大量并發(fā)請求過來,這些請求發(fā)現(xiàn)緩存過期一般都會從后端DB加載數(shù)據(jù)并回設(shè)到緩存,這個時候大并發(fā)的請求可能會瞬間把后端DB壓垮。

  • 緩存雪崩:當(dāng)緩存服務(wù)器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給后端系統(tǒng)(比如DB)帶來很大壓力

4.2 緩存穿透解決方案

一個一定不存在緩存及查詢不到的數(shù)據(jù),由于緩存是不命中時被動寫的,并且出于容錯考慮,如果從存儲層查不到數(shù)據(jù)則不寫入緩存,這將導(dǎo)致這個不存在的數(shù)據(jù)每次請求都要到存儲層去查詢,失去了緩存的意義。

有很多種方法可以有效地解決緩存穿透問題,最常見的則是采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個足夠大的bitmap中,一個一定不存在的數(shù)據(jù)會被 這個bitmap攔截掉,從而避免了對底層存儲系統(tǒng)的查詢壓力。另外也有一個更為簡單粗暴的方法(我們采用的就是這種),如果一個查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀话堰@個空結(jié)果進行緩存,但它的過期時間會很短,最長不超過五分鐘。

4.2.1 布隆過濾器

布隆過濾器是一種數(shù)據(jù)結(jié)構(gòu),垃圾網(wǎng)站和正常網(wǎng)站加起來全世界據(jù)統(tǒng)計也有幾十億個。網(wǎng)警要過濾這些垃圾網(wǎng)站,總不能到數(shù)據(jù)庫里面一個一個去比較吧,這就可以使用布隆過濾器。假設(shè)我們存儲一億個垃圾網(wǎng)站地址。

可以先有一億個二進制比特,然后網(wǎng)警用八個不同的隨機數(shù)產(chǎn)生器(F1,F2, …,F8) 產(chǎn)生八個信息指紋(f1, f2, …, f8)。接下來用一個隨機數(shù)產(chǎn)生器 G 把這八個信息指紋映射到 1 到1億中的八個自然數(shù) g1, g2, …,g8。最后把這八個位置的二進制全部設(shè)置為一。過程如下:

image.png

有一天網(wǎng)警查到了一個可疑的網(wǎng)站,想判斷一下是否是XX網(wǎng)站,首先將可疑網(wǎng)站通過哈希映射到1億個比特數(shù)組上的8個點。如果8個點的其中有一個點不為1,則可以判斷該元素一定不存在集合中。

那這個布隆過濾器是如何解決redis中的緩存穿透呢?很簡單首先也是對所有可能查詢的參數(shù)以hash形式存儲,當(dāng)用戶想要查詢的時候,使用布隆過濾器發(fā)現(xiàn)不在集合中,就直接丟棄,不再對持久層查詢。

image.png

4.2.2 緩存空對象

當(dāng)存儲層不命中后,即使返回的空對象也將其緩存起來,同時會設(shè)置一個過期時間,之后再訪問這個數(shù)據(jù)將會從緩存中獲取,保護了后端數(shù)據(jù)源;

image.png

但是這種方法會存在兩個問題:

  • 如果空值能夠被緩存起來,這就意味著緩存需要更多的空間存儲更多的鍵,因為這當(dāng)中可能會有很多的空值的鍵;

  • 即使對空值設(shè)置了過期時間,還是會存在緩存層和存儲層的數(shù)據(jù)會有一段時間窗口的不一致,這對于需要保持一致性的業(yè)務(wù)會有影響。

4.3 緩存擊穿解決方案

key可能會在某些時間點被超高并發(fā)地訪問,是一種非?!盁狳c”的數(shù)據(jù)。這個時候,需要考慮一個問題:緩存被“擊穿”的問題。

使用互斥鎖(mutex key)

業(yè)界比較常用的做法,是使用mutex。簡單地來說,就是在緩存失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當(dāng)操作返回成功時,再進行l(wèi)oad db的操作并回設(shè)緩存;否則,就重試整個get緩存的方法。

4.4 緩存雪崩解決方案

與緩存擊穿的區(qū)別在于這里針對很多key緩存,前者則是某一個key。緩存雪崩原因:

  • 緩存層出現(xiàn)了錯誤,不能正常工作了。于是所有的請求都會達到存儲層,存儲層的調(diào)用量會暴增,造成存儲層也會掛掉的情況;

  • 大量緩存集中在某一個時間段失效。

這兩種情況都會造成緩存雪崩。其解決方案主要有以下幾種。我們選用哪種來解決需要我們針對我們具體的業(yè)務(wù)系統(tǒng),具體分析,選擇最合適的一種來使用。

4.4.1 redis高可用

部署redis集群,避免單機掛掉的風(fēng)險。

4.4.2 限流降級

這個解決方案的思想是,在緩存失效后,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待。某一條線程寫緩存成功后,其余線程則可以直接在緩存中查詢到數(shù)據(jù)。

  • 這種思路減輕了數(shù)據(jù)庫的壓力,避免了數(shù)據(jù)源的崩潰,但是在高并發(fā)下,緩存重建期間key是鎖著的,這是過來1000個請求999個都在阻塞的。同樣會導(dǎo)致用戶等待超時,這是個治標不治本的方法;

  • 加鎖排隊的解決方式分布式環(huán)境的并發(fā)問題,有可能還要解決分布式鎖的問題;線程還會被阻塞,用戶體驗很差!因此,在真正的高并發(fā)場景下很少使用。

4.4.3 緩存標記

偽代碼如下:

//偽代碼
public object GetProductListNew() {
 int cacheTime = 30;
 String cacheKey = "product_list";
 //緩存標記
 String cacheSign = cacheKey + "_sign";
?
 String sign = CacheHelper.Get(cacheSign);
 //獲取緩存值
 String cacheValue = CacheHelper.Get(cacheKey);
 if (sign != null) {
 return cacheValue; //未過期,直接返回
 } else {
 CacheHelper.Add(cacheSign, "1", cacheTime);
 ThreadPool.QueueUserWorkItem((arg) -> {
 //這里一般是 sql查詢數(shù)據(jù)
 cacheValue = GetProductListFromDB(); 
 //日期設(shè)緩存時間的2倍,用于臟讀
 CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2); 
 });
 return cacheValue;
 }
} 

說明:利用緩存標記比實際緩存數(shù)據(jù)失效快,去提前更新緩存的方式去解決緩存雪崩。但這樣也需要緩存的key為原來的兩倍,即每個緩存都有緩存本身以及緩存標記

  • 緩存標記:記錄緩存數(shù)據(jù)是否過期,如果過期會觸發(fā)通知另外的線程在后臺去更新實際key的緩存;

  • 緩存數(shù)據(jù):它的過期時間比緩存標記的時間延長1倍,例:標記緩存時間30分鐘,數(shù)據(jù)緩存設(shè)置為60分鐘。這樣,當(dāng)緩存標記key過期后,實際緩存還能把舊數(shù)據(jù)返回給調(diào)用端,直到另外的線程在后臺更新完成后,才會返回新緩存。

4.4.4 為key設(shè)置不同的緩存失效時間

將緩存失效時間分散開,比如我們可以在原有的失效時間基礎(chǔ)上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重復(fù)率就會降低,就很難引發(fā)集體失效的事件。

4.4.5 數(shù)據(jù)預(yù)熱

數(shù)據(jù)加熱的含義就是在高流量點到達之前,我先把可能的數(shù)據(jù)先預(yù)先訪問一遍,這樣部分可能大量訪問的數(shù)據(jù)就會加載到緩存中。在即將發(fā)生大并發(fā)訪問前手動觸發(fā)加載緩存不同的key,設(shè)置不同的過期時間,讓緩存失效的時間點盡量均勻。

5.總結(jié)

以上針對Redis的基本數(shù)據(jù)結(jié)構(gòu)及其應(yīng)用場景、過期策略、持久策略以及存在的問題進行了詳細介紹與講解,由于內(nèi)容過多,若有錯誤之處望指出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容