目錄
一? 什么是緩存
緩存總結板書:
二? 使?Redis作為緩存
三??緩存的更新策略
1) 定期?成
?2) 實時?成
四? 面試重點:緩存預熱,緩存穿透,緩存雪崩 和緩存擊穿
1)緩存預熱
2)緩存穿透
3)緩存雪崩
4)緩存擊穿
五? 分布式鎖
板書:
1)什么是分布式鎖
2)分布式鎖的基礎實現
3)引?過期時間
4)引?校驗id
5)引?lua
6)引?watchdog(看?狗)
7) 引?Redlock算法
六???其他功能
一? 什么是緩存
緩存總結板書:
緩存(cache)是計算機中的?個經典的概念.在很多場景中都會涉及到.
核?思路就是把?些常?的數據放到觸?可及(訪問速度更快)的地?,?便隨時讀取.
對于計算機硬件來說,往往訪問速度越快的設備,成本越?,存儲空間越?.
緩存是更快,但是空間上往往是不?的.因此?部分的時候,緩存只放?些熱點數據(訪問頻繁的數據),
就?常有?了.
二? 使?Redis作為緩存
在?個?站中,我們經常會使?關系型數據庫(?如MySQL)來存儲數據.
關系型數據庫雖然功能強?,但是有?個很?的缺陷,就是性能不?.(換??之,進??次查詢操作消耗的系統資源較多).
因此,如果訪問數據庫的并發量?較?,對于數據庫的壓?是很?的,很容易就會使數據庫服務器宕機.
如何讓數據庫能夠承擔更?的并發量呢?核?思路主要是兩個:
?
- 開源:引?更多的機器,部署更多的數據庫實例,構成數據庫集群.(主從復制,分庫分表等...)
- 節流:引?緩存,使?其他的?式保存經常訪問的熱點數據,從?降低直接訪問數據庫的請求數量.
實際開發中,這兩種?案往往是會搭配使?的.
Redis 就是?個?來作為數據庫緩存的常??案.
就像?個"護盾"?樣,把MySQL給罩住了.
1) 客戶端訪問業務服務器,發起查詢請求.
2) 業務服務器先查詢Redis,看想要的數據是否在Redis中存在.
??????????如果已經在Redis中存在了,就直接返回.此時不必訪問MySQL了.
? ? ? ? ??如果在Redis中不存在,再查詢MySQL.
按照上述討論的"??定律",只需要在Redis中放20%的熱點數據,就可以使80%的請求不再真正查詢數據庫了.
當然,實踐中究竟是"??",還是"?九",還是"三七",這個情況可能會根據業務場景的不同,存在差
異.但是?少絕?多數情況下,使?緩存都能夠??提升整體的訪問效率,降低數據庫的壓?.
三??緩存的更新策略
接下來還有?個重要的問題,到底哪些數據才是"熱點數據"呢?
1) 定期?成
每隔?定的周期(?如?天/?周/?個?),對于訪問的數據頻次進?統計.挑選出訪問頻次最?的前N%的數據.
這種做法實時性較低.對于?些突然情況應對的并不好.
?如春節期間,"春晚"這樣的詞就會成為?常?頻的詞.?平時則很少會有?搜索"春晚".
?2) 實時?成
先給緩存設定容量上限(可以通過Redis配置?件的maxmemory?參數設定).
接下來把?戶每次查詢:
- ?如果在Redis查到了,就直接返回.
- ?如果Redis中不存在,就從數據庫查,把查到的結果同時也寫?Redis.
如果緩存已經滿了(達到上限),就觸發緩存淘汰策略,把?些"相對不那么熱?"的數據淘汰掉.
按照上述過程,持續?段時間之后Redis內部的數據?然就是"熱?數據"了.通?的淘汰策略主要有以下?種:
下列策略并?局限于Redis,其他緩存也可以按這些策略展開.
1)FIFO (First In First Out) 先進先出
把緩存中存在時間最久的(也就是先來的數據)淘汰掉.
2)LRU(LeastRecentlyUsed)淘汰最久未使?的
記錄每個key的最近訪問時間.把最近訪問時間最?的key淘汰掉.3)LFU(LeastFrequently Used)淘汰訪問次數最少的
記錄每個key最近?段時間的訪問次數.把訪問次數最少的淘汰掉.4)Random隨機淘汰
從所有的key中抽取幸運?被隨機淘汰掉.
這?的淘汰策略,我們可以??實現.當然Redis也提供了內置的淘汰策略,也可以供我們直接使?.
Redis 內置的淘汰策略如下:
- ?volatile-lru?當內存不?以容納新寫?數據時,從設置了過期時間的key中使?LRU(最近最少使?)算法進?淘汰
- ?allkeys-lru?當內存不?以容納新寫?數據時,從所有key中使?LRU(最近最少使?)算法進?淘汰.
- ?volatile-lfu?4.0版本新增,當內存不?以容納新寫?數據時,在過期的key中,使?LFU算法進?刪除key.
- allkeys-lfu?4.0版本新增,當內存不?以容納新寫?數據時,從所有key中使?LFU算法進?淘汰.
- ?volatile-random?當內存不?以容納新寫?數據時,從設置了過期時間的key中,隨機淘汰數據.
- ?allkeys-random?當內存不?以容納新寫?數據時,從所有key中隨機淘汰數據.
- ?volatile-ttl?在設置了過期時間的key中,根據過期時間進?淘汰,越早過期的優先被淘汰.
?(相當于FIFO,只不過是局限于過期的key)- noeviction?默認策略,當內存不?以容納新寫?數據時,新寫?操作會報錯
整體來說Redis提供的策略和我們上述介紹的通?策略是基本?致的.只不過Redis這?會針對"過期key" 和"全部key"做分別處理.
四? 面試重點:緩存預熱,緩存穿透,緩存雪崩 和緩存擊穿
1)緩存預熱
什么是緩存預熱?
使?Redis作為MySQL的緩存的時候,當Redis剛剛啟動,或者Redis?批key失效之后,此時由于
Redis ??相當于是空著的,沒啥緩存數據,那么MySQL就可能直接被訪問到,從?造成較?的壓?.
因此就需要提前把熱點數據準備好,直接寫?到Redis中.使Redis可以盡快為MySQL撐起保護傘.
熱點數據可以基于之前介紹的統計的?式?成即可.這份熱點數據不?定?得那么"準確",只要能幫助MySQL抵擋?部分請求即可.隨著程序運?的推移,緩存的熱點數據會逐漸?動調整,來更適應當前情況.
2)緩存穿透
什么是緩存穿透?
訪問的key在Redis和數據庫中都不存在.此時這樣的key不會被放到緩存上,后續如果仍然在訪問該
key, 依然會訪問到數據庫.這就會導致數據庫承擔的請求太多,壓?很?.
這種情況稱為 緩存穿透.
為何產??
原因可能有?種:
- 業務設計不合理.?如缺少必要的參數校驗環節,導致?法的key也被進?查詢了.
- 開發/運維誤操作.不??把部分數據從數據庫上誤刪了.
- ?客惡意攻擊
如何解決?
- 針對要查詢的參數進?嚴格的合法性校驗.?如要查詢的key是??的?機號,那么就需要校驗當前key 是否滿??個合法的?機號的格式.
- 針對數據庫上也不存在的key,也存儲到Redis中,?如value就隨便設成?個"".避免后續頻繁訪問數據庫.
- 使?布隆過濾器先判定key是否存在,再真正查詢.
3)緩存雪崩
什么是緩存雪崩?
短時間內?量的key在緩存上失效,導致數據庫壓?驟增,甚?直接宕機.
本來Redis是MySQL的?個護盾,幫MySQL抵擋了很多外部的壓?.?旦護盾突然失效了,MySQL
??承擔的壓?驟增,就可能直接崩潰.
為何產??
?規模key失效,可能性主要有兩種:
- Redis掛了.
- Redis上的?量的key同時過期
為啥會出現?量的key同時過期?
這種和可能是短時間內在Redis上緩存了?量的key,并且設定了相同的過期時間.如何解決?
- 部署?可?的Redis集群,并且完善監控報警體系.
- 不給key設置過期時間或者設置過期時間的時候添加隨機時間因?.
4)緩存擊穿
什么是緩存擊穿?
為何產生?
相當于緩存雪崩的特殊情況.針對熱點key,突然過期了,導致?量的請求直接訪問到數據庫上,甚?引
起數據庫宕機.如何解決:
- 基于統計的?式發現熱點key,并設置永不過期.
- 進?必要的服務降級.例如訪問數據庫的時候使?分布式鎖,限制同時請求數據庫的并發數.
五? 分布式鎖
板書:
1)什么是分布式鎖
在?個分布式的系統中,也會涉及到多個節點訪問同?個公共資源的情況.此時就需要通過鎖來做互斥控制,避免出現類似于"線程安全"的問題.
?java的synchronized或者C++的std::mutex,這樣的鎖都是只能在當前進程中?效,在分布式的這
種多個進程多個主機的場景下就?能為?了.
此時就需要使?到分布式鎖.
2)分布式鎖的基礎實現
思路?常簡單.本質上就是通過?個鍵值對來標識鎖的狀態.
舉個例?:考慮買票的場景,現在?站提供了若?個?次,每個?次的票數都是固定的.
現在存在多個服務器節點,都可能需要處理這個買票的邏輯:先查詢指定?次的余票,如果余票>0,則設置余票值-=1.
顯然上述的場景是存在"線程安全"問題的,需要使?鎖來控制.
否則就可能出現"超賣"的情況.
此時如何進?加鎖呢?我們可以在上述架構中引??個Redis,作為分布式鎖的管理器.
此時,如果買票服務器1嘗試買票,就需要先訪問Redis,在Redis上設置?個鍵值對.?如key就是?次,value隨便設置個值(?如1).
如果這個操作設置成功,就視為當前沒有節點對該001?次加鎖,就可以進?數據庫的讀寫操作.操作完成之后,再把Redis上剛才的這個鍵值對給刪除掉.
如果在買票服務器1操作數據庫的過程中,買票服務器2也想買票,也會嘗試給Redis上寫?個鍵值對,key 同樣是?次.但是此時設置的時候發現該?次的key已經存在了,則認為已經有其他服務器正在持有鎖,此時服務器2就需要等待或者暫時放棄.
但是上述?案并不完整.
3)引?過期時間
當服務器1加鎖之后,開始處理買票的過程中,如果服務器1意外宕機了,就會導致解鎖操作(刪除該
key) 不能執?.就可能引起其他服務器始終?法獲取到鎖的情況.
為了解決這個問題,可以在設置key的同時引?過期時間.即這個鎖最多持有多久,就應該被釋放.
注意!此處的過期時間只能使??個命令的?式設置.
如果分開多個操作,?如setnx之后,再來?個單獨的expire,由于Redis的多個指令之間不存在關
聯,并且即使使?了事務也不能保證這兩個操作都?定成功,因此就可能出現setnx成功,但是expire
失敗的情況.
此時仍然會出現?法正確釋放鎖的問題.
4)引?校驗id
對于Redis中寫?的加鎖鍵值對,其他的節點也是可以刪除的.
?????????如服務器1寫??個"001":1這樣的鍵值對,服務器2是完全可以把"001"給刪除掉的.
????????當然,服務器2不會進?這樣的"惡意刪除"操作,不過不能保證因為?些bug導致服務器2把鎖誤刪除.為了解決上述問題,我們可以引??個校驗id.
?如可以把設置的鍵值對的值,不再是簡單的設為?個1,?是設成服務器的編號.形如"001":"服務器1".
這樣就可以在刪除key(解鎖)的時候,先校驗當前刪除key的服務器是否是當初加鎖的服務器,如果是,才能真正刪除;不是,則不能刪除.邏輯?偽代碼描述如下:
但是很明顯,解鎖邏輯是兩步操作"get"和"del",這樣做并?是原?的.
5)引?lua
為了使解鎖操作原?,可以使?Redis的Lua腳本功能.
使?Lua腳本完成上述解鎖功能:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1])
elsereturn 0
end;
上述代碼可以編寫成?個.lua后綴的?件,由 redis-cli?或者redis-plus-plus?或者 jedis?等客戶端加載,并發送給Redis服務器,由Redis服務器來執?這段邏輯.
?個lua腳本會被Redis服務器以原?的?式來執?.
redis-plus-plus?和 jedis?如何調?lua,咱們此處不做過多介紹.具體api的寫法?家可以??研究.
6)引?watchdog(看?狗)
上述?案仍然存在?個重要問題.當我們設置了key過期時間之后(?如10s),仍然存在?定的可能性,當任務還沒執?完,key就先過期了.這就導致鎖提前失效.
把這個過期時間設置的?夠?,?如30s,是否能解決這個問題呢?很明顯,設置多?時間合適,是??境的.即使設置再?,也不能完全保證就沒有提前失效的情況.
?且如果設置的太?了,萬?對應的服務器掛了,此時其他服務器也不能及時的獲取到鎖.
因此相?于設置?個固定的?時間,不如動態的調整時間更合適.所謂watchdog,本質上是加鎖的服務器上的?個單獨的線程,通過這個線程來對鎖過期時間進?"續約".
????????注意,這個線程是業務服務器上的,不是Redis服務器的.
這樣就不擔?鎖提前失效的問題了.?且另???,如果該服務器掛了,看?狗線程也就隨之掛了,此時??續約,這個key?然就可以迅速過期,讓其他服務器能夠獲取到鎖了.
7) 引?Redlock算法
實踐中的Redis?般是以集群的?式部署的(?少是主從的形式,?不是單機).那么就可能出現以下?
較極端的?冤種情況:
為了解決這個問題,Redis的作者提出了Redlock算法.
Redlock算法:
????????我們引??組Redis節點.其中每?組Redis節點都包含?個主節點和若?從節點.并且組和組之間存儲的數據都是?致的,相互之間是"備份"關系(?并?是數據集合的?部分,這點有別于Rediscluster).
????????加鎖的時候,按照?定的順序,寫多個master節點.在寫鎖的時候需要設定操作的"超時時間".?如50ms. 即如果setnx操作超過了50ms還沒有成功,就視為加鎖失敗.
如果給某個節點加鎖失敗,就?即再嘗試下?個節點.
當加鎖成功的節點數超過總節點數的?半,才視為加鎖成功.????????如上圖,?共五個節點,三個加鎖成功,兩個失敗,此時視為加鎖成功.
這樣的話,即使有某些節點掛了,也不影響鎖的正確性.
同理,釋放鎖的時候,也需要把所有節點都進?解鎖操作.(即使是之前超時的節點,也要嘗試解鎖,盡量保證邏輯嚴密).
簡??之,Redlock算法的核?就是,加鎖操作不能只寫給?個Redis節點,?要寫個多個!!分布式系統中任何?個節點都是不可靠的.最終的加鎖成功結論是"少數服從多數的".
由于?個分布式系統不?于?部分節點都同時出現故障,因此這樣的可靠性要?單個節點來說靠譜不少.
六???其他功能
上述描述中我們解釋了基于Redis的分布式鎖的基本實現原理.
上述鎖只是?個簡單的互斥鎖.但是實際上我們在?些特定場景中,還有?些其他特殊的鎖,?如:
- 可重?鎖
- 公平鎖
- 讀寫鎖
- ......
基于Redis的分布式鎖,也可以實現上述特性.(當然了對應的實現邏輯也會更復雜).
此處我們不做過多討論了.
實際開發中,我們也并不會真的??實現?個分布式鎖.已經有很多現成的庫幫我們封裝好了,我們直接使?即可.
?如Java中的Redisson,C++中的redis-plus-plus.當然,有些??也會有??版本的分布式鎖的實
現.