public class RedisTool {
private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";/** * 嘗試擷取分布式鎖 * @param jedis Redis用戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @param expireTime 超期時間 * @return 是否擷取成功 */public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false;}private static final Long RELEASE_SUCCESS = 1L;/** * 釋放分布式鎖 * @param jedis Redis用戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @return 是否釋放成功 */public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false;}
}
可以看到,我們解鎖只需要兩行代碼就搞定了。第一行代碼,我們寫了一個簡單的Lua指令碼代碼,上一次見到這個程式設計語言還是在《駭客與畫家》裡,沒想到這次居然用上了。
第二行代碼,我們將Lua代碼傳到jedis.eval()方法裡,並使參數KEYS[1]賦值為lockKey,ARGV[1]賦值為requestId。eval()方法是將Lua代碼交給Redis服務端執行。
那麼這段Lua代碼的功能是什麼呢。其實很簡單,首先擷取鎖對應的value值,檢查是否與requestId相等,
如果相等則刪除鎖(解鎖)。那麼為什麼要使用Lua語言來實現呢。因為要確保上述操作是原子性的。關於非原子性會帶來什麼問題,可以閱讀【解鎖代碼-錯誤樣本2】 。
那麼為什麼執行eval()方法可以確保原子性,源於Redis的特性:
簡單來說,就是在eval命令執行Lua代碼的時候,Lua代碼將被當成一個命令去執行,並且直到eval命令執行完成,Redis才會執行其他命令。