一,redis锁的普通实现 设置key(myKey)的值为myUuid(当前线程唯一标志值),NX代表key不存在才设置,PX代表30秒后过期 set myKey myUuid NX PX 30000 使用lua脚本执行,保证原子性,判断key对应的值是当前线程的myUuid才删除,避免由于当前线程阻塞超过30秒后再删除key时,误删了其他线程设置的key。 if redis.call('get',myKey) == myUuid then return redis.call('del',myKey) else return 0 end 二,Redisson对redis锁的实现 首先当前线程获取一个uuid,再获取一个线程id,然后两者拼起来uuid:threadId作为当前线程特殊标志, 假设要加锁的key是myKey,key过期时间是myTime, 1.则获取锁时,发送以下lua脚本导redis上: //加锁 if (redis.call('exists', myKey) == 0) then //如果myKey不存在,则 redis.call('hset', myKey, uuid:threadId, 1); //设置myKey的值为{"uuid:threadId": 1},key是map类型 redis.call('pexpire', myKey, myTime); //设置myKey的过期时间为myTime return nil; end; //实现可重入锁 if (redis.call('hexists', myKey, uuid:threadId) == 1) then //如果myKey已存在,且存在uuid:threadId这个值,代表是当前线程加的锁,则 redis.call('hincrby', myKey, uuid:threadId, 1); //重入次数加1 redis.call('pexpire', myKey, myTime); //重新设置myKey的过期时间为myTime return nil; end; //返回myKey的失效时间毫秒数 return redis.call('pttl', myKey); 2.解锁时,发送以下lua脚本导redis上: if (redis.call('exists', myKey) == 0) then //如果myKey不存在,则 redis.call('publish', channelName, unlockMessage); //发送解锁信息到channel上 return 1; end; if (redis.call('hexists', myKey, uuid:threadId) == 0) then //myKey已经存在,但不是当前线程拥有的,则直接返回 return nil; end; //走到这步,表示myKey是当前线程拥有的,则将重入次数-1 local counter = redis.call('hincrby', myKey, uuid:threadId, -1); if (counter > 0) then //重入次数减1后的值>0,则 redis.call('pexpire', myKey, myNewTime); //重新设置超时时间 return 0; else redis.call('del', KEYS[1]); //重入次数减1后的值=0,则删除myKey redis.call('publish', channelName, unlockMessage);//发送解锁信息到channel上 return 1; end; return nil; 3.使用Redisson代码加锁解锁: RLock lock = redisson.getLock("myKey"); lock.lock(); //执行业务 lock.lock();//可重入锁 lock.unlock();//解锁 lock.unlock();//解锁 三,以上实现只能在单机上运行,一旦redis有主从结构,会出现问题: 1.客户端A从master中获取锁成功,master将数据同步给slave时发生了故障,slave没同步到锁相关数据。 2.slave升级为master,其他客户端可以它上面获取锁,但此时客户端A还保留着锁。 四,但是在单机上运行一旦发生单点故障则所有锁都不可用了,如需要在多台机器上实现redis锁,保障高可用,可用Redlock算法 1.需要有N个redis单位,这个单位可以是redis单机或redis集群(集群是主从模式,哨兵模式或cluster模式都行),单位与单位之间没有任何关系 2.客户端获取redis锁时,依次从这几个单位获取锁,当获取锁数量大于等于(N/2+1)个,且使用时间小于锁超时时间,则获取锁成功。 3.获取锁成功后,锁实际超时时间 = 设置的超时时间 - 获取锁花费时间 4.如获取锁失败,比如获取数量过少或获取花费时间大于锁超时时间,则客户端在所有redis单位上都解锁 (有可能在redis单位上加锁成功了,但没响应给客户端,客户端不知道加锁成功了,因此要对所有单位都解锁) 五,Redisson整合了Redlock算法,相关代码如下: //lock1, lock2, lock3中分别配置了3个redis单位的信息 RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); try { //1000ms获取不到锁则返回获取失败,10000ms是锁失效时间 if (redLock.tryLock(1000, 10000, TimeUnit.MILLISECONDS)) { //执行业务 } } catch (Exception e) { } finally { redLock.unlock();//解锁 }
Redis_Redisson_Redlock
最新推荐文章于 2024-05-09 07:00:00 发布