一:redis常见的用途
1 缓存
2 计数器
3 排行榜
4 分布式锁
二:redis实现分布式锁
分布式锁主要有以下几个问题:
1 互斥性:多个机器不能同时获得锁,同一时刻只有一台机器占有锁
2 安全性:保证加锁和解锁都是同一台机器,不能误释放别人的锁
3 死锁:节点故障,但是一直占有锁未释放,其他节点一直加锁失败
4 容错:一个节点故障,保证其他节点可以释放和获取到锁
三:主要用到的redis命令
1:setnx key value key不存在时 赋值 然后返回1 key存在时 返回0
有一个问题就是,如果线程1 获取锁之后 在释放锁之前挂掉了,锁一直没有释放其他线程就没法获取说那么这就死锁了,所以一定要设置一个过期时间。赋值和设置过期时间是2个操作,必须要保证原子性。。下边命令能满足
SET key value [EX seconds] [PX milliseconds] [NX|XX]
2:在释放锁时我们很多时候会直接del key
,但是这样是不安全的,可能会释放不该释放的锁
所以我们在释放锁的时候 要验证一下 是不是自己锁的,是自己锁的那么就释放,不是自己锁的 就不该自己释放
总结一下就是:拿到锁之后 要设置过期时间,释放锁的时候 要判断一下
对于锁的过期时间:设置要合理,不能过长也不能过短,过长影响新的线程重新获得锁的流程,影响业务响应时间。太短导致业务未执行完,锁自动释放,另一个线程获得锁,重新开始执行逻辑,这就间接要求业务保证幂等性,非幂等性的业务会影响数据一致性。针对这种情况解决方案:守护线程为锁延长过期时间。
对于释放锁判断问题:判断redis里边的key-value value值是不是当前线程设置的,就要先从redis取出来 然后比较 相等才能删除。取值--->删除 这是2步操作,不是原子操作,存在判断后锁过期,另一个线程获得锁,然后误释放锁的情况。针对这种情况,可以使用lua脚本。
luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"
四:上代码
self.tryLock(self.lockFunc,10)
self.unlock(["helloworld"],["123456"])
上面描述的是单点情况,若在redis集群环境依然存在问题:
由于Redis集群数据同步为异步,假设在Master节点获取到锁后未完成数据同步情况下Master节点crash,此时在新的Master节点依然可以获取锁,所以多个Client同时获取到了锁。
解决方案:
redlock算法,set向多半节点发送命令,过半节点成功即为成功,释放时想全部节点发送。
附带一篇 redis调用lua