1、Redis緩存穿透,緩存擊穿,緩存雪崩原因+解決方案
- 緩存穿透:key對應的數據在數據源並不存在,每次針對此key的請求從緩存獲取不到,請求都會到數據源,從而可能壓垮數據源。比如用一個不存在的用户id獲取用户信息,不論緩存還是數據庫都沒有,若黑客利用此漏洞進行攻擊可能壓垮數據庫;
- 緩存擊穿:key對應的數據存在,但在redis中過期,此時若有大量併發請求過來,這些請求發現緩存過期一般都會從後端DB加載數據並回設到緩存,這個時候大併發的請求可能會瞬間把後端DB壓垮;
- 緩存雪崩:當緩存服務器重啓或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(比如DB)帶來很大壓力。
緩存穿透解決方案:
有很多種方法可以有效地解決緩存穿透問題,最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更為簡單粗暴的方法(我們採用的就是這種),如果一個查詢返回的數據為空(不管是數據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。緩存擊穿解決方案 :
業界比較常用的做法,是使用互斥鎖(mutex)。簡單地來説,就是在緩存失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設緩存;否則,就重試整個get緩存的方法。緩存雪崩解決方案
緩存失效時的雪崩效應對底層系統的衝擊非常可怕!大多數系統設計者考慮用加鎖或者隊列的方式保證來保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量的併發請求落到底層存儲系統上。還有一個簡單方案就時講緩存失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重複率就會降低,就很難引發集體失效的事件。
2、redis的單點故障主從切換帶來的兩個客户端同時持有鎖的問題
生產中redis一般是主從模式,主節點掛掉時,從節點會取而代之,客户端上卻並沒有明顯感知。原先第一個客户端在主節點中申請成功了一把鎖,但是這把鎖還沒有來得及同步到從節點,主節點突然掛掉了。然後從節點變成了主節點,這個新的節點內部沒有這個鎖,所以當另一個客户端過來請求加鎖時,立即就批准了。這樣就會導致系統中同樣一把鎖被兩個客户端同時持有,不安全性由此產生。
不過這種不安全也僅僅是在主從發生 failover 的情況下才會產生,而且持續時間極短,業務系統多數情況下可以容忍。
如果你很在乎高可用性,希望掛了一台 redis 完全不受影響,可以考慮 redlock。 Redlock 算法是由Antirez
發明的,它的流程比較複雜,不過已經有了很多開源的 library 做了良好的封裝,用户可以拿來即用,比如 redlock-py。RedLock算法的核心原理: 使用N個完全獨立、沒有主從關係的Redis master節點以保證他們大多數情況下都不會同時宕機,N一般為奇數。一個客户端需要做如下操作來獲取鎖:
1.獲取當前時間(單位是毫秒)。
2.輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裏,客户端在每個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10秒鐘,那每個節點鎖請求的超時時間可能是5-50毫秒的範圍,這個可以防止一個客户端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該儘快嘗試下一個master節點。
3.客户端計算第二步中獲取鎖所花的時間,只有當客户端在大多數master節點上成功獲取了鎖((N/2) +1),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認為是獲取成功了。
4.如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間。
5.如果鎖獲取失敗了,不管是因為獲取成功的鎖不超過一半(N/2+1)還是因為總消耗時間超過了鎖釋放時間,客户端都會到每個master節點上釋放鎖,即便是那些他認為沒有獲取成功的鎖。
3、為什麼lua腳本結合redis命令可以實現原子性
Redis 提供了非常豐富的指令集,但是用户依然不滿足,希望可以自定義擴充若干指令來完成一些特定領域的問題。Redis 為這樣的用户場景提供了 lua 腳本支持,用户可以向服務器發送 lua 腳本來執行自定義動作,獲取腳本的響應數據。Redis 服務器會單線程原子性執行 lua 腳本,保證 lua 腳本在處理的過程中不會被任意其它請求打斷。