云虚拟主机和网站建设,wordpress下载类插件,珠海市网站建设品牌,wordpress仿站模板制作教程为什么使用分布式锁
单机环境并发时#xff0c;使用synchronized或lock接口可以保证线程安全#xff0c;但它们是jvm层面的锁#xff0c;分布式环境并发时#xff0c;100个并发的线程可能来自10个服务节点#xff0c;那就是跨jvm了。
简单分布式锁实现
SETNX 格式…为什么使用分布式锁
单机环境并发时使用synchronized或lock接口可以保证线程安全但它们是jvm层面的锁分布式环境并发时100个并发的线程可能来自10个服务节点那就是跨jvm了。
简单分布式锁实现
SETNX 格式setnx key value 当且仅当key不存在时key能设置成功并返回1否则返回0。 SETNX即SET if Not eXists 如果不存在。 SETNX存在的问题
高并发场景下有可能存在key自动过期了加锁的线程还未执行完成的情况。此时线程2加锁进来了且刚好线程1执行完了线程1会把线程2刚刚创建的锁删掉导致后面进来了更多的线程。【自己加的锁被别人删除了】
解决方案给每个加锁的key生成一个唯一的UUID作为value删除之前从redis拿出来的UUID与当前线程一致才能删除key。 然而finally依然存在问题因为释放锁的代码不是原子操作。【超时零界点】在判断uuid相同后且还未删除key前此时key超时了其他线程加锁成功当前线程执行delete命令时删除的依然是其他线程加的锁。
以上问题均是由于锁的时间不够长导致接下来的解决方案是【锁续命】
锁续命
原理线程1加锁成功后再创建一个线程使用定时任务来监控锁剩余时间。定时任务执行的周期必须小于锁的超时时间。比如锁超时时间默认设置为30秒那么该线程每10秒执行一次给锁重新设置超时时间为30秒 保证主线程删除锁时是自己加的锁。如果主线程已释放锁子线程执行定时任务时会先判断主线程加的key是否还存在。 目前市面上已有成熟的解决方案如redisson它适用于分布式各种场景。参考redisson帮助文档
Redisson续命锁流程 引入redisson的依赖包
dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactIdversion3.27.2/version
/dependency 创建一个Redisson的bean注册到Spring容器中 //获取锁对象
RLock redissonLock redisson.getLock(lockKey);
//加分布式锁
redissonLock.lock();
........
//解锁
redissonLock.unlock(); Redisson加锁源码
底层是基于lua脚本实现它能保证原子性。因为Redis服务端执行命令是单线程的读到这一块lua代码会将它当成一个整体执行完再去执行下一条命令。
第一步通过lua脚本加锁成后返回null加锁失败返回锁剩余时间。 hset key field value 通过hash结构设置field为UUID线程IDvalue是1表示重入次数。 key是构建锁时传入的redisson.getLock(lockKey); 锁超时时间默认是30秒。
第二步看门狗机制给锁续命 Future模式异步执行lua脚本加锁加锁成功后回调Future的监听器获取加锁结果。 加锁的lua脚本执行成功后返回null则进入scheduleExpireRenewal()方法定时的给锁重置超时时间。 创建一个TimeTask延时任务10秒后才执行run()方法lua脚本判断锁存在则重设锁的超时时间为30秒。它同样是使用的Future模式下面添加了一个监听重置结束后会回调监听器监听器拿到结果再回调scheduleExpireRenewal()方法本身。
第三步加锁失败的线程自旋等待
加锁失败后先订阅该key的channel通道消息类似于一个队列或topic当锁在过期之前释放了需要唤醒等待的线程竞争锁。进入while(true)自旋加锁。先尝试一次加锁成功则退出循环失败则获取Semaphore调用tryAcquire()等待ttl秒后重新进入while(true)尝试获取锁。如果1000个线程同时结束等待就会一起抢锁所以该锁是非公平锁。 第四步释放锁时发布该key的channel消息通知等待的线程竞争锁。 一旦channel中有消息后会执行LockPubSub.onMessage()方法获取一个Semaphora信号量释放等待的线程让他们竞争锁。
总结redisson的架构设计涉及到Future自旋锁看门狗机制发布订阅lua脚本semaphore等技术。 它的lock()接口实现的是非公平锁。tryAcquire()加锁成功后继续执行业务代码。其他线程拿锁时返回锁剩余的时间ttl判断ttl大于0则使用Semaphore.getLatch().tryAcquire()等到超时时间结束后再去抢锁。如果ttl还没有变为0此时锁已释放主线程会向指定的channel发送一条消息等待的线程会订阅这个消息LockPubSub.onMessage()拿到释放锁的消息后调用Semaphore.getLatch().release()唤醒阻塞的线程立刻抢锁。 主线程给锁续命使用的是Future机制异步开启了一个线程每隔10秒嵌套调用重设锁时间的方法。
lua脚本