Jedis 實現簡單的分佈式鎖(基於jdk的Lock接口)
redis在高併發場景中的使用比較流行,雖然其內部IO處理使用單線程,但是依然能夠快速處理,支撐比較高的併發。基於這個特點,redis在互聯網應用中作爲分佈式鎖的中間件被廣泛應用,例如搶購,秒殺等業務場景。redis的分佈式鎖的實現原理在其官方文檔上面已經寫得十分詳細(https://redis.io/topics/distlock),此文章只是簡單地實現jedis的分佈式鎖,保證其互斥性(Mutual exclusion),無死鎖(Deadlock free),容錯性(Fault tolerance)。
代碼
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.apache.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisDistributedLock implements Lock {
Logger logger = Logger.getLogger(RedisDistributedLock.class);
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 LOCK_TIMEOUT = 5 * 1000L;
private static final long DEFAULT_EXPIRE_TIME = 1000L;
private static final long RELEASE_SUCCESS = 1L;
private static final String UNLOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
private JedisPool jedisPool;
/**
* redis store key
*/
private String lockKey;
/**
* redis store value
*/
private String lockUserId;
/**
* @param jedisPool
* @param lockKey
* @param lockUserId (ensure uniqueness)
*/
public RedisDistributedLock(JedisPool jedisPool, String lockKey, String lockUserId) {
this.jedisPool = jedisPool;
this.lockKey = lockKey;
this.lockUserId = lockUserId;
}
@Override
public void lock() {
if (tryLock()) {
if (logger.isDebugEnabled()) {
logger.debug(lockUserId +
" get the redis lock(" + lockKey + ") successfully.");
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
tryLock(DEFAULT_EXPIRE_TIME, TimeUnit.MILLISECONDS);
}
@Override
public boolean tryLock() {
try {
return tryLock(LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
/**
* @param time redis key timeout
* @param unit time unit
* @return
* @throws InterruptedException
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
String result = null;
Jedis jedis = null;
while (!LOCK_SUCCESS.equals(result)) {
if (Thread.currentThread().isInterrupted()) {
evalUnLock(jedis);
throw new InterruptedException();
}
try {
jedis = jedisPool.getResource();
result = jedis.set(lockKey, lockUserId,
SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, unit.toMillis(time));
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close();
}
if (!LOCK_SUCCESS.equals(result)) {
Thread.sleep(getRandomAquireTime());
} else {
return true;
}
}
return false;
}
@Override
public void unlock() {
Long resultCode = null;
Jedis jedis = jedisPool.getResource();
resultCode = evalUnLock(jedis);
if (RELEASE_SUCCESS == resultCode) {
if (logger.isDebugEnabled()) {
logger.debug(lockUserId +
" release the redis lock(" + lockKey + ") successfully.");
}
} else {
if (logger.isDebugEnabled()) {
logger.debug(lockUserId +
" release the redis lock(" + lockKey + ") unsuccessfully.");
}
}
}
@Override
public Condition newCondition() {
// TODO Auto-generated method stub
return null;
}
private Long evalUnLock(Jedis jedis) {
Long resultCode = null;
try {
resultCode = (Long) jedis.eval(UNLOCK_SCRIPT, 1, lockKey, lockUserId);
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close();
}
return resultCode;
}
/**
* generate the sleep time
* @return
*/
private static long getRandomAquireTime() {
return new Random().nextInt(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
提示
如果獲取鎖不成功,那麼線程會隨機sleep一段時間,這樣做的目的是爲了避免同一時間內過大的併發量。