Redis鎖主要用到了Redis中的如下幾個API,這裏引用官方的API文檔介紹說明一下:redis
SETNX
Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for "SET if Not eXists".分佈式
Example:ide
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> this
GETSET
Example:線程
redis> SET mykey "Hello"
"OK"
redis> GETSET mykey "World"
"Hello"
redis> GET mykey
"World"
redis> orm
expire
Integer reply, specifically:
1 if the timeout was set.
0 if key does not exist.接口
Example:ip
redis> SET mykey "Hello"
"OK"
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10
redis> SET mykey "Hello World"
"OK"
redis> TTL mykey
(integer) -1
redis> ci
官方文檔介紹的也比較淺顯易懂,這裏不過多贅述。Redis可使用Redission,也可使用JedisCulster,而且以Api的方式提供給業務方
這裏實現方式以下,互斥key業務方本身定製
接口層:
/** * Redis分佈式鎖 */ public interface DistributeLock { /** * 默認鎖有效時間(單位毫秒) */ public static final long DEFAULT_LOCK_EXPIRE_TIME_MS = 60000; /** * 默認睡眠時間(單位毫秒) */ public static final long DEFAULT_SLEEP_TIME_MS = 100; /** * 嘗試鎖 * * @param lock 鎖的鍵 * @param requestTimeoutMS 請求超時 ms * @return 若是鎖成功,則返回true;不然返回false */ boolean tryLock(String lock, long requestTimeoutMS); /** * 嘗試鎖 * * @param lock 鎖的鍵 * @param lockExpireTimeMS 鎖有效期 ms * @param requestTimeoutMS 請求超時 ms */ boolean tryLock(String lock, long lockExpireTimeMS, long requestTimeoutMS); /** * 解鎖 * * @param lock 鎖的鍵 */ void unlock(String lock); }
具體實現層:
/** * Description:分佈式鎖 * Created by Jiyajie on 2017/12/15. */ @Service public class RedisDistributeLock implements DistributeLock { private static final Logger LOGGER = LoggerFactory.getLogger(RedisDistributeLock.class); /** * 每秒的毫秒數 */ private static final long MILLIS_PER_SECOND = 1000L; /** * 0值 */ private static final int INT_ZERO = 0; /** * 1值 */ private static final int INT_ONE = 1; /** * jedisCluster */ @Autowired private JedisCluster jedisCluster; @Override public boolean tryLock(String lock, long requestTimeoutMS) { return this.tryLock(lock, DEFAULT_LOCK_EXPIRE_TIME_MS, requestTimeoutMS); } @Override public boolean tryLock(String lock, long lockExpireTimeMS, long requestTimeoutMS) { Preconditions.checkArgument(StringUtils.isNotBlank(lock), "lock invalid"); Preconditions.checkArgument(lockExpireTimeMS > INT_ZERO, "lockExpireTimeMS invalid"); Preconditions.checkArgument(requestTimeoutMS > INT_ZERO, "requestTimeoutMS invalid"); while (requestTimeoutMS > INT_ZERO) { String expire = String.valueOf(System.currentTimeMillis() + lockExpireTimeMS + INT_ONE); Long result = jedisCluster.setnx(lock, expire); if (result > INT_ZERO) { //目前沒有線程佔用此鎖 jedisCluster.expire(lock, Long.valueOf(lockExpireTimeMS / MILLIS_PER_SECOND).intValue()); return true; } String currentValue = jedisCluster.get(lock); if (currentValue == null) { //鎖已經被其餘線程刪除立刻重試獲取鎖 continue; } else if (Long.parseLong(currentValue) < System.currentTimeMillis()) { //此處判斷出鎖已經超過了其有效的存活時間 String oldValue = jedisCluster.getSet(lock, expire); if (oldValue == null || oldValue.equals(currentValue)) { //1.若是拿到的舊值是空則說明在此線程作getSet以前已經有線程將鎖刪除,因爲此線程getSet操做以後已經對鎖設置了值,實際上至關於它已經佔有了鎖 //2.若是拿到的舊值不爲空且等於前面查到的值,則說明在此線程進行getSet操做以前沒有其餘線程對鎖設置了值,則此線程是第一個佔有鎖的 jedisCluster.expire(lock, Long.valueOf(lockExpireTimeMS / MILLIS_PER_SECOND).intValue()); return true; } } long sleepTime; if (requestTimeoutMS > DEFAULT_SLEEP_TIME_MS) { sleepTime = DEFAULT_SLEEP_TIME_MS; requestTimeoutMS -= DEFAULT_SLEEP_TIME_MS; } else { sleepTime = requestTimeoutMS; requestTimeoutMS = INT_ZERO; } try { TimeUnit.MILLISECONDS.sleep(sleepTime); } catch (InterruptedException e) { LOGGER.warn("分佈式鎖線程休眠異常{}", lock, e); } } return false; } @Override public void unlock(String lock) { String value = jedisCluster.get(lock); if (null != value && Long.parseLong(value) > System.currentTimeMillis()) { //若是鎖還存在而且還在有效時間則進行刪除 jedisCluster.del(lock); } } }
這裏對實現步驟,以及實現原理進行分析:
1:參數校驗,對傳入的互斥key,鎖超時時間,請求超時時間進行檢驗
2:在請求超時時間以內的請求,這裏以while死循環的方式不斷進行獲取鎖重試
3:設置鎖過時時間,並嘗試用setnx命令,redis以前不存在key的狀況下,設置key,同時把過時時間expire做爲value設置進去。若是獲取成功,這裏給鎖加上真正的過時時間,獲取鎖成功~
4:在第三步沒有成功的狀況下,咱們直接再次獲取鎖。若是爲空,則說明鎖已通過期,或者已經被其餘線程解鎖,那麼咱們馬上結束本次循環,嘗試從新獲取~
5:若是第四步獲取鎖成功,咱們須要進行一下判斷:1拿到鎖的過時時間(key對應的value),並判斷鎖是否在過時時間以內,若是在的話,用具備原子性操做的命令getset,取出以前的過時時間oldValue值,這裏會有兩種狀況:
//1.若是拿到的舊值是空則說明在此線程作getSet以前已經有線程將鎖刪除,因爲此線程getSet操做以後已經對鎖設置了值,實際上至關於它已經佔有了鎖
//2.若是拿到的舊值不爲空且等於前面查到的值,則說明在此線程進行getSet操做以前沒有其餘線程對鎖設置了值,則此線程是第一個佔有鎖的
兩種狀況都說明已經獲取鎖成功,結束循環
以上步驟都是創建在,請求超時時間以內的,這裏每次循環獲取,間隔100毫秒~,當獲取時間超過請求超時時間的話:也是鎖獲取失敗的一種狀況
至此分佈式鎖的獲取結束~
解鎖操做很簡單,這裏也再也不贅述~