错误示范
之前看过很多redis实现分布式锁基本都是在程序中使用时间戳进行加锁超时判断,然而这种方法并不能在高并发情况避免误删;以下是错误示范
错误示范
public boolean lock(String key, String value) {
//如果key值不存在,则返回 true,且设置 value
if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//获取key的值,判断是是否超时
String curVal = redisTemplate.opsForValue().get(key);
if (StringUtils.isNotEmpty(curVal) && Long.parseLong(curVal) < System.currentTimeMillis()) {
//获得之前的key值,同时设置当前的传入的value。这个地方可能几个线程同时过来,但是redis本身天然是单线程的,所以getAndSet方法还是会安全执行,
//首先执行的线程,此时curVal当然和oldVal值相等,因为就是同一个值,之后该线程set了自己的value,后面的线程就取不到锁了
String oldVal = redisTemplate.opsForValue().getAndSet(key, value);
if(StringUtils.isNotEmpty(oldVal) && oldVal.equals(curVal)) {
return true;
}
}
return false;
}
很明显,在高并发情况下,会出现一个线程获取锁后被其他线程修改掉超时时间。并且解锁时也会出现一些问题;
redis官方给了正确的加锁姿势,快来看!!
使用单个实例正确实现
在尝试克服上述单实例设置的限制之前,让我们在这个简单的情况下检查如何正确地执行它,因为这实际上是一个可行的解决方案,在不时可以接受竞争条件的应用程序中,并且因为锁定到单个实例是我们将用于此处描述的分布式算法的基础。
要获得锁定,可以采用以下方法:
SET resource_name my_random_value NX PX 30000
java中使用jedis set 命令加锁,即
// keys 加锁key args:密钥 解锁时确认与加锁着为同一对象,一般可用 uuid
jedis.set(keys,args,"NX","PX",30000)
该命令仅在密钥尚不存在时才设置密钥(NX选项),到期时间为30000毫秒(PX选项)。密钥设置为“我的随机值”值。此值必须在所有客户端和所有锁定请求中都是唯一的。
基本上使用随机值是为了以安全的方式释放锁,使用一个告诉Redis的脚本:只有当密钥存在并且密钥中存储的值正是我期望的那个时才删除密钥。这是通过以下Lua脚本完成的:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
在java中使用jedis eval命令执行脚本解锁,即
String str = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
// keys 即加锁key ,args 即 密钥
jedis.eval(str, Collections.singletonList(keys), Collections.singletonList(args))
这一点很重要,以避免删除由另一个客户端创建的锁。例如,客户端可以获取锁定,在某些操作中被阻止的时间长于锁定有效时间(密钥将到期的时间),并且稍后移除已经由某个其他客户端获取的锁定。仅使用DEL是不安全的,因为客户端可能会删除另一个客户端的锁定。使用上面的脚本而不是每个锁都使用随机字符串“签名”,因此只有在客户端尝试删除锁定时,锁定才会被删除。
这个随机字符串应该是什么?我假设它是来自/ dev / urandom的20个字节,但是你可以找到更便宜的方法来使它对你的任务足够独特。例如,安全选择是使用/ dev / urandom对RC4进行种子处理,并从中生成伪随机流。一个更简单的解决方案是使用unix时间和微秒分辨率的组合,将其与客户端ID连接起来,它不是那么安全,但可能在大多数环境中都可以完成任务。
我们用作关键时间的时间被称为“锁定有效时间”。它既是自动释放时间,也是客户端为了执行所需操作所需的时间,而另一个客户端可能能够再次获取锁定,而不会在技术上违反互斥保证,这仅限于给定的窗口从获得锁定的那一刻起的时间。
所以现在我们有了获得和释放锁的好方法。系统推理由单个始终可用的实例组成的非分布式系统是安全的。让我们将概念扩展到我们没有这种保证的分布式系统。