在Redis 2.6.12之前,由于setnx 不支持设置过期时间,所以加锁的过期相对比较复杂,通常会为以下几步
- 通过setnx获取锁,如果成功,再设置过期时间。 如果还没来得及执行expire操作,客户端就宕机了,将导致该锁永久有效,所以有下面这一步。
- 如果客户端setnx获取锁失败,则检查一下该锁是否设置了过期时间,如果未设置过期时间则设置。这一步是对上面问题的弥补,如果某个创建锁的客户端在未执行expire时就宕机了,其它客户端可以通过修改key的过期时间,来保证锁最终会被Redis清除,自己有获取锁的机会。不过同样有一个问题,就是客户端之间的时间有可能会被覆盖,但实际业务中,通常这种覆盖是可以被接受的。如果情况特殊,可以用2.6.12之后的方式,关于这点在文末有代码介绍。
private final static String LOCK_PRE_FIX = "lock_";
public boolean tryLock(String lockName, String id,int expretime,int timeout,) {
long currentMillis = System.currentTimeMillis();
long overMillis = currentMillis + timeout;
final String fullLockName = LOCK_PRE_FIX + lockName;
while(System.currentTimeMillis() < overMillis) {
if(jedis.setnx(fullLockName, id) == 1) {
jedis.expire(fullLockName, expretime);
return true;
}else if (jedis.ttl(fullLockName) == -1 ) {
jedis.expire(fullLockName, expretime);
}
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
//...
}
}
return false;
}
而删除锁的过程有几种方式:一种通过LUA脚本的方式;一种通过客户端开事务来保证。
lua脚本删除代码如下:关于lua相关语法在其它博客中已经有过介绍,这里不多描述
private static final Long RELEASE_SUCCESS = 1L;
public boolean unLockByLua(String lockKey, String id) {
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(id));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
通过客户端事务的方式如下:获取锁的值,判断是不是自己创建的,如果是则删除。之所有要加事务,是因为在判断和del之间,锁有可能突然过期,然后其它线程突然获取到了锁,此时执行del操作就会把其它客户端创建的锁给释放掉。所以通常会用watch和事务来处理。如下
public void unLokc(String lockName,String id) {
final String fullLockName = LOCK_PRE_FIX + lockName;
jedis.watch(fullLockName);
if(jedis.get(fullLockName).equals(id)) {
Transaction multi = jedis.multi();
try {
multi.del(fullLockName);
multi.exec();
}finally {
multi.discard();
jedis.unwatch();
}
}
}
Redis2.6.12之后,加锁变得相当简单,原因在于Redis对set命令进行了扩展
set key value [EX seconds] [PX milliseconds] [NX|XX]
代码如下,其中NX表示SET IF NOT EXIST,PX表示设置过期时间,值由expiretime指定
public static boolean tryLock(String lockName, String id, int expiretime) {
String result = jedis.set(lockName, id, "NX", "PX", expiretime);
if ("OK".equals(result)) {
return true;
}
return false;
}
释放锁的方式仍然源用上面两种,目前并没有更简化的方式,当然你可以客户端对功能封装,实用业务中调用简化。