Java 正確實現 redis 分散式鎖
阿新 • • 發佈:2018-11-19
Java 正確實現 redis 分散式鎖
1 源起
因為專案中有需要一個需求 ,就是在分散式的環境下 ,要根據一個 key,去獲取一把分散式鎖。所以。。。。。
2 我想要的效果
我想要的其實很簡單, 就是根據一個 key 獲取一個 分散式鎖就行了 比如這樣
// 獲取分散式鎖物件
RedisDistributeLock locker = new DefaultRedisDistributeLock();
// 鎖定
locker.lock (jedis, "test1");
// TODO 業務邏輯
// 解鎖
locker.release(jedis, "test1");
3 擼起袖子開幹
3.1 匯入 jedis 依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
3.2 RedisDistributeLock 介面
RedisDistributeLock 定義了一些分散式鎖的介面 實現, 本人能力有限,只實現了 非公平鎖
package org.study.distributed_look.redis.way2;
import redis.clients.jedis.Jedis;
/**
* @Author: huangwenjun
* @Description:
* @Date: Created in 13:45 2018/5/22
**/
public interface RedisDistributeLock {
/**
* 公平鎖, 只能說 本機的公平
* @param jedis
* @param key
* @param uuid
*/
void fairLock(Jedis jedis, String key, String uuid);
/**
* 非公平鎖
* @param jedis
* @param key
* @param uuid
*/
void unfairLock(Jedis jedis, String key, String uuid);
/**
* 鎖 預設非公平
* @param jedis
* @param key
* @param uuid
*/
void lock(Jedis jedis, String key, String uuid);
/**
* 解鎖
* @param jedis
* @param key
* @param uuid
*/
void release(Jedis jedis, String key, String uuid);
}
3.3 DefaultRedisDistributeLock
DefaultRedisDistributeLock 是我封裝的 預設的分散式鎖的實現
package org.study.distributed_look.redis.way2;
import org.study.distributed_look.redis.RedisTool;
import redis.clients.jedis.Jedis;
/**
* @Author: huangwenjun
* @Description:
* @Date: Created in 13:48 2018/5/22
**/
public class DefaultRedisDistributeLock implements RedisDistributeLock {
/**
* 預設 非公平鎖
*/
private static final Boolean DEFALUT_FAIR = false;
/**
* 過期時間 預設 10 秒, 太短會導致鎖不住, 如果業務無法在指定過期時間內 完成, 則必須加長過期時間
*/
private static final Integer DEFAULT_EXPIRE_TIME = 10000;
private Boolean isFair;
private Integer expireTime;
public DefaultRedisDistributeLock() {
isFair = DEFALUT_FAIR;
expireTime = DEFAULT_EXPIRE_TIME;
}
public DefaultRedisDistributeLock(boolean isFair, Integer expireTime) {
isFair = isFair;
expireTime = expireTime;
}
@Override
public void lock(Jedis jedis, String key, String uuid) {
if (isFair) {
fairLock(jedis, key, uuid);
} else {
unfairLock(jedis, key, uuid);
}
}
@Override
public void fairLock(Jedis jedis, String key, String uuid) {
// 通過一個佇列維護, 參照 AQS 實現
}
@Override
public void release(Jedis jedis, String key, String uuid) {
try {
while (true) {
boolean released = RedisTool.releaseDistributedLock(jedis, key, uuid);
if (released) {
break;
}
}
} finally {
jedis.close();
}
}
@Override
public void unfairLock(Jedis jedis, String key, String uuid) {
while (true) {
boolean locked = RedisTool.tryGetDistributedLock(jedis, key, uuid, expireTime);
if (locked) {
break;
}
}
}
}
3.4 RedisTool
RedisTool 是我參照網上一個大神的程式碼, 正確的實現了 redis 分散式鎖的 獲取 和 釋放
package org.study.distributed_look.redis;
import redis.clients.jedis.Jedis;
import java.util.Collections;
/**
*
* redis 分散式鎖實現
*
* @Author: huangwenjun
* @Description:
* @Date: Created in 15:00 2018/5/21
**/
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";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 嘗試獲取分散式鎖
*
* @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;
}
/**
* 釋放分散式鎖
* @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;
}
}
4 必須有測試啊!!!
4.1 測試程式碼
package org.study.distributed_look.redis.way2;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* @Author: huangwenjun
* @Description:
* @Date: Created in 14:10 2018/5/22
**/
public class TestRedisDistributeLock {
public static void main(String[] args) {
for (int i = 1; i < 10; i ++) {
new TestLock().start();
}
}
static class TestLock extends Thread {
static RedisDistributeLock locker = new DefaultRedisDistributeLock();
JedisPool jedisPool = new JedisPool();
@Override
public void run() {
Jedis jedis = jedisPool.getResource();
locker.lock(jedis, "test1", "qwerqwer");
// TODO 模擬業務
System.out.println(Thread.currentThread().getName() + " get lock.....");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " release lock.....");
locker.release(jedis, "test1", "qwerqwer");
}
}
}
4.2 輸出
Thread-4 get lock.....
Thread-4 release lock.....
Thread-2 get lock.....
Thread-2 release lock.....
Thread-7 get lock.....
Thread-7 release lock.....
Thread-9 get lock.....
Thread-9 release lock.....
Thread-0 get lock.....
Thread-0 release lock.....
Thread-8 get lock.....
Thread-8 release lock.....
Thread-5 get lock.....
Thread-5 release lock.....
Thread-6 get lock.....
Thread-6 release lock.....
Thread-3 get lock.....
Thread-3 release lock.....
4.3 優化策略
一些優化策略
- 1 這裡為了簡單起見, 沒有優化 jedis 的連線池, 生產環境 必須手動設定 jedis 連線池
- 2 locker.lock(jedis, “test1”, “qwerqwer”); 第三個 引數可以用 uuid, 至於為什麼要有第三個引數 參見
5 整合到業務中
// 獲取分散式鎖物件
RedisDistributeLock locker = new DefaultRedisDistributeLock();
// 鎖定
locker.lock(jedis, "test1", "uuid");
// TODO 業務邏輯
// 解鎖
locker.release(jedis, "test1", "uuid");
和之前設想的基本一致, 如果有人發現問題 歡迎指正