关于分布式锁的那些事儿

什么是分布式锁分布式锁的特性

互斥(线程独享):即同一时刻只有一个线程能够获取锁

避免死锁:获得锁的线程崩溃后,不会影响后续线程获取锁,操作共享资源

隔离性:A获取的锁,不能让B去解锁(解铃还须系铃人)

原子性:加锁和解锁必须保证为原子操作

分布式锁的实现方式

基于Redis

演变过程:


那么还问存在其他问题吗?
分析分布式锁的特征:互斥、死锁、原子等特性,我们都算是解决了!
但还未考虑隔离性的问题!

场景

线程A加锁成功后,去操作共享资源

但是因为发生了意外,线程A操作的时间超过了锁过期时间,锁被释放了

线程B进来了,枷锁成功,去操作共享资源了

此时,线程A操作完成了,回来释放锁,线程B的锁被A释放(动了别人的老婆!)

隔离性带来的问题:

锁的过期时间设置不合理,导致线程A锁过期,被释放

线程A释放了线程B的锁

分析:

线程A的过期时间设置不合理,那就换一个合理的时间————对应到现实工作中,就是根据程序员的工作经验,对改值进行较为合理的设置,实在不行,杀了祭天!(不是很可靠)

其实很简单,锁过期就像去麦当劳喝咖啡喝完了呗,还想喝怎么办?续杯!————获取锁时,先设置一个过期时间,同时,开启一个守护线程,定时去查看锁的剩余存活时间,假如锁的存活时间快过期了,但业务代码还没执行完,赶紧去给大爷续杯,即重新设置过期时间(看门狗)

至于第二个问题,还是那句老话————解铃还须系铃人,加一个业务唯一标识,每个线程只能根据业务唯一去释放自己的锁,同时,需要注意:判断是否为自己的锁和删除锁应为原子操作!不然仍旧会删错锁!


实现

Redission的看门狗(基于Netty时间轮算法实现):

privatelonglockWatchdogTimeout=30*1000;publicRedissonLock(CommandAsyncExecutorcommandExecutor,Stringname){super(commandExecutor,name);=commandExecutor;//会获取看门狗设置的时间,默认为10s检查一次,锁过快过期,且业务代码还没执行完,就会给锁续上这个时间,默认30=().getCfg().getLockWatchdogTimeout();=().getSubscribeService().getLockPubSub();}privateRFutureBooleantryAcquireOnceAsync(longwaitTime,longleaseTime,TimeUnitunit,longthreadId){//如果锁是永不过期,那么就按常规方式索取锁if(leaseTime!=-1){returntryLockInnerAsync(waitTime,leaseTime,unit,threadId,_NULL_BOOLEAN);}//否则,会在获取锁之后,加一个定时任务,在锁执行完业务代码自行释放之前,不断的给所续上过期时间(默认10s检查一次,每次给锁续期30s)RFutureBooleanttlRemainingFuture=tryLockInnerAsync(waitTime,internalLockLeaseTime,,threadId,_NULL_BOOLEAN);((ttlRemaining,e)-{if(e!=null){return;}//lockacquiredif(ttlRemaining){scheduleExpirationRenewal(threadId);}});returnttlRemainingFuture;}

线程隔离的问题:

//如果是自己的锁,则进行删除,否则返回("GET",KEY[1])==ARGV[1]("DEL",KEY[1])elsereturn0
总览


小总结

作为技术宅男,要有极客精神(其实就是闲了无聊),有心的人,可能会发现,以上粉色标粗的Redis单实例字样,确实!以上分析的分布式锁适合单节点的Redis实例,如果遇到主从+哨兵的模式基本凉凉!

凉凉场景:

线程A在遇到主从架构时,先在Master上加锁成功

此时,还未等加锁命令SET同步到Slave上,Master就出现问题,宕机了!

通过哨兵过半原则,重新选出新的主节点,那么此时这把锁在新的主库上是找不到的!出现新问题了!

为之奈何?


亲妈解法!


如果一遇到这种问题,就要程序员提桶跑路,那么Redis的作者恐怕在大佬圈是混不下去了!于是,他苦心钻研,誓死捍卫Redis尊严!于是乎它就出世了!————RedLock

要求:原理:疑问:分析:理性看待

其实,Redis作者研究出来的RedLock,在一些极端的情况下是存在风险的,比如:

N节点的时钟存在较大偏差时,T2-T1Expire的讨论就是毫无意义的,依然存在琐失效的问题,想要解决这个问题,就得需要人工的去维护N节点之间的时钟趋于一致

RedLock仍旧解决不了获得锁的线程客户端发生长时间GC,导致锁过期,如果再出现第二个线程仍旧可以获取锁,此时,就会出现同一时刻两个线程对共享资源同时获得锁的矛盾情况,严重违反分布式锁特性中的互斥性

因为RedLock无法提供类似fencingtoken的设计方案,从而推导出RedLock无法保证分布式的正确性

神仙打架局:

基于Zookeeper

利用节点名称唯一性

原理

利用临时顺序节点

原理

二者对比

效率:ZK锁远不如Redis锁

失败处理:

ZK锁只需要维护Watch监听器,等待锁被释放
Redis锁则是自旋重试,高并发时耗性能

宕机处理:

ZK是根据客户端上报心跳(长连接),判断客户端是否存在(持有锁),无心跳上报时,会删除节点(释放锁)————(客户端长GC时,锁会被ZK释放)
Redis则是需要等到过期时间,才会释放锁

总结

市面上常见的分布式锁,基本上都研究了一下,感觉收获颇丰!当然这些都是理论,光说不练假把式,下一篇就是分布式锁的实现大合集!期待!

免责声明:本文章如果文章侵权,请联系我们处理,本站仅提供信息存储空间服务如因作品内容、版权和其他问题请于本站联系