Golang Redis分布式鎖
分布式鎖實現
// 獲取分布式鎖
func (h *Handler) AcquireLock(cuuid string) error {
// redis操作
redisClient := GetRedis() //連接Redis
// 嘗試多次加鎖
for i := 0; i < 3; i++ {
result := redisClient.SetNX(uuid, 1, 3*time.Second)
if result.Err() != nil || !result.Val() {
// 等待之后再次去加鎖
time.Sleep(1 * time.Millisecond)
continue
}
return nil
}
return errors.New("set distribute lock failed")
}
// 釋放分布式鎖
func (h *Handler) ReleaseLock(uuid string) {
redisClient := GetRedis() //連接Redis
if err := redisClient.Del(uuid).Err(); err != nil {
log.Error("redisClient.Del failed,err:%v",err)
}
}
Redis常見問題
(1.) Q:Redis緩存中的數據與DB存儲層中的數據不一致問題
采用延時雙刪策略
// 先刪除緩存
redis.delKey( key );
// 再寫數據庫
db.updateData( data );
// 休眠500毫秒
Thread.sleep( 500 );
// 再次刪除緩存
redis.delKey( key );
(2.)Q:redis數據持久化問題,防止意外重啟,斷電等數據丟失。
A:Redis有兩種持久化的方式:快照(RDB文件)和追加式文件(AOF文件):
RDB持久化方式會在一個特定的間隔保存那個時間點的一個數據快照。
AOF持久化方式則會記錄每一個服務器收到的寫操作。在服務啟動時,這些記錄的操作會逐條執行從而重建出原來的數據。寫操作命令記錄的格式跟Redis協議一致,以追加的方式進行保存。
(3.)緩存穿透
緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中時被動寫的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
解決方案
有很多種方法可以有效地解決緩存穿透問題,最常見的則是采用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更為簡單粗暴的方法(我們采用的就是這種),如果一個查詢返回的數據為空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鍾。
(4.)緩存雪崩
緩存雪崩是指在我們設置緩存時采用了相同的過期時間,導致緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。
解決方案
緩存失效時的雪崩效應對底層系統的沖擊非常可怕。大多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線 程(進程)寫,從而避免失效時大量的並發請求落到底層存儲系統上。一個簡單方案就時講緩存失效時間分散開,比如可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鍾隨機,這樣每一個緩存的過期時間的重復率就會降低,就很難引發集體失效的事件。
(5.)緩存擊穿
對於一些設置了過期時間的key,如果這些key可能會在某些時間點被超高並發地訪問,是一種非常“熱點”的數據。這個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的區別在於這里針對某一key緩存,前者則是很多key。
緩存在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的並發請求過來,這些請求發現緩存過期一般都會從后端DB加載數據並回設到緩存,這個時候大並發的請求可能會瞬間把后端DB壓垮。
解決方案:
使用互斥鎖(mutex key)
業界比較常用的做法,是使用mutex。簡單地來說,就是在緩存失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設緩存;否則,就重試整個get緩存的方法。
(6.)redis定時刪除,防止緩存過大
Reply: 經過研究, Redis過期刪除都三種不同策略:
(1):立即刪除。在設置鍵的過期時間時,創建一個回調事件,當過期時間達到時,由時間處理器自動執行鍵的刪除操作。
(2):惰性刪除。鍵過期了就過期了,不管。每次從dict字典中按key取值時,先檢查此key是否已經過期,如果過期了就刪除它,並返回nil,如果沒過期,就返回鍵值。
(3):定時刪除。每隔一段時間,對expires字典進行檢查,刪除里面的過期鍵。
可以看到,第二種為被動刪除,第一種和第三種為主動刪除,且第一種實時性更高。