redis缓存实现分布式锁
分布式锁的原则
1、相互排斥:任意时刻,只能有一个客户端持有锁。
2、无死锁:尺有所的客户端宕机或网络延迟下仍然额可以获得此锁。
3、有始有终:一个客户端加了锁只能自己解锁。
4、容错性:只要大部分的redis节点还存活,a那么客户端就能正常加锁和释放锁。
依赖:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
获取分布式锁:
public class RedisUtil { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "EX"; /** * @param jedis redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 过期时间 * @return */ public static boolean getDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime){ String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (result.equals(LOCK_SUCCESS)){ return true; } return false; } }
为什么set方法能够满足前面提到的分布式锁的原则,源码:
/** * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1 * GB). * @param key 唯一标识key * @param value 存储的值value * @param nxxx 可选项:NX、XX 其中NX表示当key不存在时才set值,XX表示当key存在时才set值 * @param expx 过期时间单位,可选项:EX|PX 其中EX为seconds,PX为milliseconds * @param time 过期时间,单位取上一个参数 * @return Status code reply */ public String set(final String key, final String value, final String nxxx, final String expx, final long time) { checkIsInMultiOrPipeline(); client.set(key, value, nxxx, expx, time); return client.getStatusCodeReply(); }
如上的tryDistributedLock就可以实现简单的redis分布式锁了(此set方法的原子性)
1、set方法中nxx参数为NX,表示当key不存在时才会set值,保证了互斥性;
2、set值的同时设置过期时间(过期后del此key),客户端宕机或网络延迟时不会一直持有锁,避免了死锁发生;
3、set方法中的value,比如UUID之类的,用来表示当前请求客户端的唯一性标识;
4、因为是redis单例,暂时没有考虑容错性;
解锁操作
private static final Long RELEASE_SUCCESS = 1L; /** * 释放锁 * * @param jedis redis客户端 * @param lockKey 锁的key * @param uniqueId 请求标识 * @return 是否释放 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String uniqueId) { String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(uniqueId)); return RELEASE_SUCCESS.equals(result); }
使用Lua脚本告诉redis,如果这个key存在,且其存储的值和指定的value一致才可以删除这个key,从而释放锁,这样也保证了分布式锁的几个原则. 常见的错误释放锁会直接del这个key,没有考虑当前锁的拥有者,不符合分布式锁原则的有始有终原则;
不适用lua 脚本的话,也可以使用如下代码:
public static void releaseLock(Jedis jedis,String lockKey,String uniqueId){ if(uniqueId.equals(jedis.get(lockKey))){ jedis.del(lockKey); } }
如上代码存在这样的场景: Client A去加锁lockKey,然后释放锁,在执行del(lockKey)之前,这时lockKey锁expire到期失效了,此时Client B尝试加锁lockKey成功,Client A接着执行释放锁操作(del),便释放了Client B的锁。
参考文章:
https://blog.csdn.net/fanrenxiang/article/details/79803037

浙公网安备 33010602011771号