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一段时间,这样做的目的是为了避免同一时间内过大的并发量。