@Slf4j
@Component
public class RedisLock {
public static final int LOCK_EXPIRE = 5000;
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 分布式鎖
*
* @param key key值
* @return 是否獲取到
*/
public boolean lock(String key) {
String lock = key;
try {
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE;
Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
log.info("用戶 [{}]加鎖成功",key);
return true;
} else {
log.info("用戶 [{}]加鎖失敗",key);
//判斷該key上的值是否過期了
byte[] value = connection.get(lock.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
if (expireTime < System.currentTimeMillis()) {
// 如果鎖已經過期
byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE).getBytes());
// 防止死鎖
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
} finally {
RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
}
}
/**
* 刪除鎖
*
* @param key
*/
public void delete(String key) {
try {
redisTemplate.delete(key);
} finally {
RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
}
}
//實現業務
try{
// 判斷是否獲取了鎖
boolean getLock = redisLock(lockKey);
if(getLock){
// 此處可以開始寫需要實現的代碼
}
}catch(Exception e){
e.printStackTrace();
}finally {
// 判斷是否超時了,如果未超時,則釋放鎖。 超時了,鎖有可能被其他線程拿走了,就不做任何操作
redisLock.delete(String.valueOf(caronwerId));
}
}
知識點: redis Setnx(SET if Not eXists) 命令在指定的 key 不存在時,為 key 設置指定的值。
設置成功,返回 1 。 設置失敗,返回 0 。
redis> EXISTS job # job 不存在 (integer) 0 redis> SETNX job "programmer" # job 設置成功 (integer) 1 redis> SETNX job "code-farmer" # 嘗試覆蓋 job ,失敗 (integer) 0 redis> GET job # 沒有被覆蓋 "programmer"
一.redis命令講解: setnx()命令: setnx的含義就是SET if Not Exists,其主要有兩個參數 setnx(key, value)。 該方法是原子的,如果key不存在,則設置當前key成功,返回1;如果當前key已經存在,則設置當前key失敗,返回0。 get()命令: get(key) 獲取key的值,如果存在,則返回;如果不存在,則返回nil; getset()命令: 這個命令主要有兩個參數 getset(key, newValue)。該方法是原子的,對key設置newValue這個值,並且返回key原來的舊值。 假設key原來是不存在的,那么多次執行這個命令,會出現下邊的效果: 1. getset(key, “value1”) 返回nil 此時key的值會被設置為value1 2. getset(key, “value2”) 返回value1 此時key的值會被設置為value2 3. 依次類推! 二.具體的使用步驟如下: 1. setnx(lockkey, 當前時間+過期超時時間) ,如果返回1,則獲取鎖成功;如果返回0則沒有獲取到鎖,轉向2。 2. get(lockkey)獲取值oldExpireTime ,並將這個value值與當前的系統時間進行比較,如果小於當前系統時間,則認為這個鎖已經超時,可以允許別的請求重新獲取,轉向3。 3. 計算newExpireTime=當前時間+過期超時時間,然后getset(lockkey, newExpireTime) 會返回當前lockkey的值currentExpireTime。 4. 判斷currentExpireTime與oldExpireTime 是否相等,如果相等,說明當前getset設置成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那么當前請求可以直接返回失敗,或者繼續重試。 5. 在獲取到鎖之后,當前線程可以開始自己的業務處理,當處理完畢后,比較自己的處理時間和對於鎖設置的超時時間,如果小於鎖設置的超時時間,則直接執行delete釋放鎖;如果大於鎖設置的超時時間,則不需要再鎖進行處理
