分佈式鎖
分佈式CAP
任何一個分佈式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多隻能同時滿足兩項。
1.基於數據庫排他鎖做分佈式鎖
在查詢語句後面增加for update
來獲取鎖, 數據庫會在查詢過程中給數據庫表增加排他鎖
InnoDB 引擎在加鎖的時候,只有通過索引進行檢索的時候纔會使用行級鎖,否則會使用表級鎖。
通過connection.commit()
操作來釋放鎖
問題: 線程數量上限受數據庫連接池大小限制;
2.基於 Redis 做分佈式鎖
- setnx(lockkey, 當前時間+過期超時時間),如果返回 1,則獲取鎖成功;如果返回 0 則沒有獲取到鎖,轉向 2。
- get(lockkey) 獲取值 oldExpireTime ,並將這個 value 值與當前的系統時間進行比較,如果小於當前系統時間,則認爲這個鎖已經超時,可以允許別的請求重新獲取,轉向 3。
- 計算 newExpireTime = 當前時間+過期超時時間,然後 getset(lockkey, newExpireTime) 會返回當前 lockkey 的值currentExpireTime。
- 判斷 currentExpireTime 與 oldExpireTime 是否相等,如果相等,說明當前 getset 設置成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那麼當前請求可以直接返回失敗,或者繼續重試。
- 在獲取到鎖之後,當前線程可以開始自己的業務處理,當處理完畢後,比較自己的處理時間和對於鎖設置的超時時間,如果小於鎖設置的超時時間,則直接執行 delete 釋放鎖;如果大於鎖設置的超時時間,則不需要再鎖進行處理。
RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key, "1");
if(status == 1) {
redisService.expire(key, expire);
return true;
}
if(oldExpireTime > System.currentTimeMillis()) {
redisService.del(key);
}
基於 redisson 做分佈式鎖
失效時間設置多長時間爲好?
redisson 的做法是:每獲得一個鎖時,只設置一個很短的超時時間,同時起一個線程在每次快要到超時時間時去刷新鎖的超時時間。在釋放鎖的同時結束這個線程。