分布式锁概念和实现 Distributed Lock应用场景 重复操作(?)、并发数据正确
技术需求
互斥
防止死锁 设置有效时间,避免无法释放
性能 减少锁等待时间导致大量阻塞,粒度尽量小
容错 保证外部系统正常(不影响业务),客户端加锁解锁过程可控
Redis实现分布式锁 set key value[expiration EX seconds|PX milliseconds][NX|XX] EX 设置键的过期时间为second秒 PX 设置键的过期时间为millisecond 毫秒 NX 只在键不存在时,才对键进行设置操作。SET key value NX 效果等同于 SETNX key value XX:只在键已经存在时,才对键进行设置操作 例:SET resource_name my_random_value NX PX 30000 效果:当resource_name这个key不存在时创建这样的key,设值为my_random_value,并设置过期时间30000毫秒
原子性和Lua脚本 Redis服务器会单线程原子性执行Lua脚本
分布式删除key
1 2 3 4 5 6 //分布式锁删除一个key if redis.call("get",KEYS[1])== ARGV[1] then return redis.call("del',KEYS[1]) else return 0 end
限制访问频率(指定时间最大访问次数)
1 2 3 4 5 6 7 8 9 10 11 12 //实现一个访问颗率限制功能 local times=redis.call('incr',KEYS[1]) // 如果是第一次进来,设置一个过期时间 if times == 1 then redis.call('expire',KEYS[1],ARGV[1]) end //如果在指定时间内访问次数大于指定次数,则返回0,表示访问被限制 if times > tonumber(ARGV[2]) then return 0 end // 返回1,允许被访问 return 1
Redission分布式锁 实现了可重入锁(ReentrantLock)公平锁(FairLock)、联锁(MultiLock)红锁(RedLock)、读写锁(ReadWriteLock)等,还提供许多分布式服务 Redisson支持单点模式、主从模式、哨兵模式、集群模式,只是配置的不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class RedissonLock { private Redisson redisson; //加锁 public void lock(string lockName, long leaseTime){ RLock rLock = redisson.getLock(lockName); rLock.lock(leaseTime,TimeUnit.SECONDS); } //释放锁 public void unlock(string lockName){ redisson.getLock(lockName).unlock(); } //判断是否加锁 public boolean isLock(string lockName){ RLock rLock = redisson.getLock(lockName); return rLock.isLocked(); } //获取锁 public boolean tryLock(String lockName, long leaseTime){ RLock rLock =redisson.getLock(lockName); boolean getLock = false; try { getLock = rLock.tryLock(leaseTime, TimeUnit.SECONDS); } catch (InterruptedException e){ e.printstackTrace(); } return false; return getLock; } }
RedisTemplate分布式锁 高版本
1 2 3 4 5 6 7 8 9 10 //加锁 public boolean setIfNotExists(String key, String value, int seconds){ key = getkey(key); return redisTemplate.opsForValue().setIfAbsent(key, value,seconds,TimeUnit.SEcONDs); } //释放 public Boolean unlock(string key, string value){ key = getKey(key); return redisTemplate.execute(UNLOCK SCRIPT, Collections,singletonList(key), value); }
低版本 需自己通过脚本实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 //加锁 public Boolean lock(String key, String value, int expireseconds){ key = getkey(key); return redisTemplate.execute(LocK SCRIPT, Collections,singletonList(key), value, expireseconds); } //lua脚本,用来设置分布式锁 private static final string LOCK LUA SCRIPT = "if redis.call('setNx',KEYS[1],ARGV[1])then\n" + " if redis.call('get',KEYS[1])==ARGV[1] then\n" + " return redis.call('expire',KEYS[1],ARGV[2])\n" + " else\n"+ " return 0\n" + " end\n" + "end\n"; private static final RedisScript<Boolean> LOCK SCRIPT = RedisScript.of(LOCK LUA SCRIPT, Boolean.class); //释放锁 //lua脚本,用来释放分布式锁 private static final String UNLOCK LUA SCRIPT = "if redis.call('get',KEYS[1])== ARGV[1] then\n" + " return redis.call('del',KEYS[1])\n" "else\n" + " return 0\n"+ "end"; private static final RedisScript<Boolean> UNLOCK SCRIPT = RedisScript.Of(UNLOCK LUA SCRIPT,Boolean.class);
Redisson分布式锁原理 典型的分布式锁实现
执行流程 获取锁->成功,启动看门狗 、 执行lua脚本
看门狗WatchDog 业务未处理完成时,自动延长锁时间(默认30s)
RLock接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface RRLock { //加锁,锁的有效期默认30秒 void lock(); //获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false boolean tryLock(); boolean tryLock(long time, TimeUnit unit)throws InterruptedException; //解锁 void unlock(); //中断锁,表示该锁可以被中断 void lockInterruptibly(); void lockInterruptibly(long leaseTime, TimeUnit unit); //检验该锁是否被线程使用,如果被使用返回True boolean isLocked();
tryLock
tryAquire
tryLockInnerAsync
unlockInnerAsync
非本线程持有锁返回nil,重入次数减一,若为0则释放锁,若大于0则延长过期时间
异常情况
客户端长时间内阻塞导致锁失效 网络问题或者GC等原因导致长时间阻塞,然后业务程序还没执行完锁就过期
Redis服务器时钟漂移 如果Redis服务器的机器时间发生了向前跳跃,就会导致这个key过早超时失效
单点实例安全问题 Redis主机在同步锁之前宕机那么向其他及其申请锁就会再次得到这把锁
RedLock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //获取多个 RLock 对象 RLock lock1=redissonclient1.getLock(lockkey); RLock lock2 = redissonclient2.getLock(lockKey); RLock lock3 =redissonclient3.getLock(lockKey); //根据多个RLock对象构建 RedissonRedLockRedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); try { //尝试获取锁 boolean res = redLock.tryLock(100,10,TimeUnit.SECONDS); if(res){ //成功获得锁,在这里处理业务 } catch(Exception e){ throw new RuntimeException("aquire lock fail"); }finally{ //无论如何,最后都要解锁 redLock.unlock(); }
示例 getLock tryLock releaseLock
思考题 列举日常开发中分布式锁应用场景? Zookeeper实现 redis实现
分布式锁常见问题