当前位置: 首页 > news >正文

公司网站公司简介南京企业自助建站系统

公司网站公司简介,南京企业自助建站系统,网店怎么开啊流程是怎样,大连成久建设工程有限公司在一个分布式系统中#xff0c;由于涉及到多个实例同时对同一个资源加锁的问题#xff0c;像传统的synchronized、ReentrantLock等单进程情况加锁的api就不再适用#xff0c;需要使用分布式锁来保证多服务实例之间加锁的安全性。常见的分布式锁的实现方式有zookeeper和redis…在一个分布式系统中由于涉及到多个实例同时对同一个资源加锁的问题像传统的synchronized、ReentrantLock等单进程情况加锁的api就不再适用需要使用分布式锁来保证多服务实例之间加锁的安全性。常见的分布式锁的实现方式有zookeeper和redis等。而由于redis分布式锁相对于比较简单在实际的项目中redis分布式锁被用于很多实际的业务场景中。 redis分布式锁的实现中又以Redisson比较出名所以本文来着重看一下Redisson是如何实现分布式锁的以及Redisson提供了哪些其它的功能。 一、如何保证加锁的原子性 说到redis的分布式锁可能第一时间就想到了setNx命令这个命令保证一个key同时只能有一个线程设置成功这样就能实现加锁的互斥性。但是Redisson并没有通过setNx命令来实现加锁而是自己实现了一套完成的加锁的逻辑。 Redisson的加锁使用代码如下接下来会有几节着重分析一下这段代码逻辑背后实现的原理。 先通过RedissonClient传入锁的名称拿到一个RLock然后通过RLock实现加锁和释放锁。 getLock获得的RLock接口的实现是RedissonLock所以我们看一下RedissonLock对lock()方法的实现。 lock方法会调用重载的lock方法传入的leaseTime为-1调用到这个lock方法之后会调用tryAcquire实现加锁的逻辑。 tryAcquire最后会调到tryAcquireAsync方法传入了leaseTime和当前加锁线程的id。tryAcquire和tryAcquireAsync的区别就是tryAcquireAsync是异步执行而tryAcquire是同步等待tryAcquireAsync的结果也就是异步转同步的过程。 tryAcquireAsync方法会根据leaseTime是不是-1来判断使用哪个分支加锁其实不论走哪个分支最后都是调用tryLockInnerAsync方法来实现加锁只不过是参数不同罢了。但是我们这里的leaseTime其实就是-1所以会走下面的分支尽管传入到tryAcquireAsync的leaseTime是-1但是在调用tryLockInnerAsync方法传入的leaseTime参数是internalLockLeaseTime默认是30s。 tryLockInnerAsync方法。 通过tryLockInnerAsync方法的实现可以看出最终加锁是通过一段lua脚本来实现加锁的redis在执行lua脚本的时候是可以保证加锁的原子性的所以Redisson实现加锁的原子性是依赖lua脚本来实现的。其实对于RedissonLock这个实现来说最终实现加锁的逻辑都是通过tryLockInnerAsync来实现的。 来一张图总结一下lock方法加锁的调用逻辑。 二、如何通过lua脚本实现加锁 通过上面分析可以看出redis是通过执行lua脚本来实现加锁保证加锁的原子性。那么接下来分析一下这段lua脚本干了什么。 其中这段脚本中的lua脚本中的参数的意思 KEYS[1]就是锁的名称对于我们的demo来说就是myLock ARGV[1]就是锁的过期时间不指定的话默认是30s ARGV[2]代表了加锁的唯一标识由UUID和线程id组成。一个Redisson客户端一个UUIDUUID代表了一个唯一的客户端。所以由UUID和线程id组成了加锁的唯一标识可以理解为某个客户端的某个线程加锁。 那么这些参数是怎么传过去的呢其实是在这里。 getName方法就是获取锁的名称 leaseTime就是传入的锁的过期时间如果指定超时时间就是指定的时间没指定默认是30s getLockName就是获取加锁的客户端线程的唯一标识。 分析一下这段lua的加锁的逻辑。 1先调用redis的exists命令判断加锁的key存不存在如果不存在的话那么就进入if。不存在的意思就是还没有某个客户端的某个线程来加锁第一次加锁肯定没有人来加锁于是第一次if条件成立。 2然后调用redis的hincrby的命令设置加锁的key和加锁的某个客户端的某个线程加锁次数设置为1加锁次数很关键是实现可重入锁特性的一个关键数据。用hash数据结构保存。hincrby命令完成后就形成如下的数据结构。 myLock:{ b983c153-7421-469a-addb-44fb92259a1b:1:1 } 3最后调用redis的pexpire的命令将加锁的key过期时间设置为30s。 从这里可以看出第一次有某个客户端的某个线程来加锁的逻辑还是挺简单的就是判断有没有人加过锁没有的话就自己去加锁设置加锁的key再存一下加锁的线程和加锁次数设置一下锁的过期时间为30s。 画一张图来看一下lua脚本加锁的逻辑干了什么。 至于第二段if是干什么的我们后面再说。 三、为什么需要设置加锁key的过期时间 通过上面的加锁逻辑可以知道虽然我们没有手动设置锁的过期时间但是Redisson默认会设置一个30s的过期时间为什么需要过期时间呢 主要原因是为了防止死锁。当某个客户端获取到锁还没来得及主动释放锁那么此时假如客户端宕机了又或者是释放锁失败了那么如果没有设置过期时间那么这个锁key会一直在那么其它线程来加锁的时候会发现key已经被加锁了那么其它线程一直会加锁失败就会产生死锁的问题。 四、如何自动延长加锁时间 通过上面的分析我们都知道在加锁的时候就算没有指定锁的过期时间Redisson默认也会给锁设置30s的过期时间主要是用来防止死锁。 虽然设置了默认过期时间能够防止死锁但是这也有一个问题如果在30s内任务没有结束但是锁已经被释放了失效了一旦有其它线程加锁成功那么就完全有可能出现线程安全数据错乱的问题。 所以Redisson对于这种未指定超时时间的加锁就实现了一个叫watchdog机制也就是看门狗机制来自动延长加锁的时间。 在客户端通过tryLockInnerAsync方法加锁成功之后如果你没有指定锁过期的时间那么客户端会起一个定时任务来定时延长加锁时间默认每10s执行一次。所以watchdog的本质其实就是一个定时任务。 最后会定期执行如下的一段lua脚本来实现加锁时间的延长。 解释一下这段lua脚本中参数的意思其实是跟加锁的参数的意思是一样 KEYS[1]就是锁的名称对于我们的demo来说就是myLock ARGV[1]就是锁的过期时间 ARGV[2]代表了加锁的唯一标识b983c153-7421-469a-addb-44fb92259a1b:1。 这段lua脚本的意思就是判断来续约的线程跟加锁的线程是同一个如果是同一个那么将锁的过期时间延长到30s然后返回1代表续约成功不是的话就返回0代表续约失败下一次定时任务也就不会执行了。 注意因为有了看门狗机制所以说如果你没有设置过期时间超时自动释放锁的逻辑后面会说并且没有主动去释放锁那么这个锁就永远不会被释放因为定时任务会不断的去延长锁的过期时间造成死锁的问题。但是如果发生宕机了是不会造成死锁的因为宕机了服务都没了那么看门狗的这个定时任务就没了也自然不会去续约等锁自动过期了也就自动释放锁了跟上述说的为什么需要设置过期时间是一样的。 五、如何实现可重入加锁 可重入加锁的意思就是同一个客户端同一个线程也能多次对同一个锁进行加锁。 也就是同时可以执行多次 lock方法流程都是一样的最后也会调用到lua脚本所以可重入加锁的逻辑最后也是通过加锁的lua脚本来实现的。 上面加锁逻辑的lua脚本的前段我上面已经说过下半部分也就是可重入加锁的逻辑。 下面这段if的意思就是判断当前已经加锁的key对应的加锁线程跟要来加锁的线程是不是同一个如果是的话就将这个线程对应的加锁次数加1也就实现了可重入加锁同时返回nil回去。 可重入加锁成功之后加锁key和对应的值可能是这样。 myLock:{ b983c153-7421-469a-addb-44fb92259a1b:1:2 } 所以加锁lua脚本的第二段if的逻辑其实是实现可重入加锁的逻辑。 六、如何主动释放锁和避免其它线程释放了自己加的锁 当业务执行完成之后肯定需要主动释放锁那么为什么需要主动释放锁呢 第一假设你任务执行完没有手动释放锁如果没有指定锁的超时时间那么因为有看门狗机制势必会导致这个锁无法释放那么就可能造成死锁的问题。 第二如果你指定了锁超时时间锁超时自动释放逻辑后面会说虽然并不会造成死锁的问题但是会造成资源浪费的问题。假设你设置的过期时间是30s但是你的任务2s就完成了那么这个锁还会白白被占有28s的时间这28s内其它线程都无法成功加锁。 所以任务完成之后一定需要主动释放锁。 那么Redisson是如何主动释放锁和避免其它线程释放了自己加的锁 主动释放锁是通过unlock方法来完成的接下来就分析一下unlock方法的实现。unlock会调用unlockAsync传入当然释放线程的id代表了当前线程来释放锁unlock其实也是将unlockAsync的异步操作转为同步操作。 unlockAsync最后会调用RedissonLock的unlockInnerAsync来实现释放锁的逻辑。 也是执行一段lua脚本。 1先判断来释放锁的线程是不是加锁的线程如果不是那么直接返回nil所以从这里可以看出主要是通过一个if条件来防止线程释放了其它线程加的锁。 2如果来释放锁的线程是加锁的线程那么就将加锁次数减1然后拿到剩余的加锁次数 counter 变量。   3如果counter大于0说明有重入加锁锁还没有彻底的释放完那么就设置一下锁的过期时间然后返回0 4如果counter没大于0说明当前这个锁已经彻底释放完了于是就把锁对应的key给删除然后发布一个锁已经释放的消息然后返回1。 七、如何实现超时自动释放锁 前面我们说了不指定锁超时时间的话那么会有看门狗线程不断的延长加锁时间不会导致锁超时释放自动过期。那么指定超时时间的话是如何实现到了指定时间超时释放锁的呢 能够设置超时自动释放锁的方法。 void lock(long leaseTime, TimeUnit unit) boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) 通过传入leaseTime参数就可以指定锁超时的时间。 无论指不指定超时时间最终其实都会调用tryAcquireAsync方法只不过当不指定超时时间时leaseTime传入的是-1也就是代表不指定超时时间但是Redisson默认还是会设置30s的过期时间当指定超时时间那么leaseTime就是我们自己指定的时间最终也是通过同一个加锁的lua脚本逻辑。 指定和不指定超时时间的主要区别是加锁成功之后的逻辑不一样不指定超时时间时会开启watchdog后台线程不断的续约加锁时间而指定超时时间就不会去开启watchdog定时任务这样就不会续约加锁key到了过期时间就会自动删除也就达到了释放锁的目的。 所以指定超时时间达到超时释放锁的功能主要还是通过redis自动过期来实现因为指定了超时时间加锁成功之后就不会开启watchdog机制来延长加锁的时间。 在实际项目中指不指定锁的超时时间是根据具体的业务来的如果你能够比较准确的预估出代码执行的时间那么可以指定锁超时释放时间来防止业务执行错误导致无法释放锁的问题如果不能预估出代码执行的时间那么可以不指定超时时间。 八、如何实现不同线程加锁的互斥 上面我们分析了第一次加锁逻辑和可重入加锁的逻辑因为lua脚本加锁的逻辑同时只有一个线程能够执行redis是单线程的原因所以一旦有线程加锁成功那么另一个线程来加锁前面两个if条件都不成立最后通过调用redis的pttl命令返回锁的剩余的过期时间回去。 这样客户端就根据返回值来判断是否加锁成功因为第一次加锁和可重入加锁的返回值都是nil而加锁失败就返回了锁的剩余过期时间。 所以加锁的lua脚本通过条件判断就实现了加锁的互斥操作保证其它线程无法加锁成功。 所以总的来说加锁的lua脚本实现了第一次加锁、可重入加锁和加锁互斥的逻辑。 九、加锁失败之后如何实现阻塞等待加锁 从上面分析加锁失败之后会走如下的代码。 从这里可以看出最终会执行死循环自旋地的方式来不停地通过tryAcquire方法来尝试加锁直到加锁成功之后才会跳出死循环如果一直没有成功加锁那么就会一直旋转下去所谓的阻塞实际上就是自旋加锁的方式。 但是这种阻塞可能会产生问题因为如果其它线程释放锁失败那么这个阻塞加锁的线程会一直阻塞加锁这肯定会出问题的。所以有没有能够可以指定阻塞的时间如果超过一定时间还未加锁成功的话那么就放弃加锁的方法。答案肯定是有的接着往下看。 十、如何实现阻塞等待一定时间还未加锁成功就放弃加锁 超时放弃加锁的方法 boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) boolean tryLock(long time, TimeUnit unit) 通过waitTime参数或者time参数来指定超时时间。这两个方法的主要区别就是上面的方法支持指定锁超时时间下面的方法不支持锁超时自动释放。 tryLock(long time, TimeUnit unit)这个方法最后也是调用tryLock(long waitTime, long leaseTime, TimeUnit unit)方法的实现。代码如下。 其实通过源码就可以看出是怎么实现一定时间之内还未获取到锁就放弃加锁的逻辑其实相比于一直获取锁主要是加了超时的判断如果超时了那么就退出循环放弃加锁 十一、如何实现公平锁 1什么是公平锁 所谓的公平锁就是指线程成功加锁的顺序跟线程来加锁的顺序是一样实现了先来先成功加锁的特性所以叫公平锁。就跟排队一样不插队才叫公平。 前面几节讲的RedissonLock的实现是非公平锁但是里面的一些机制比如看门狗都是一样的。 2公平锁和非公平锁的比较 公平锁的优点是按序平均分配锁资源不会出现线程饿死的情况它的缺点是按序唤醒线程的开销大执行性能不高。非公平锁的优点是执行效率高谁先获取到锁锁就属于谁不会“按资排辈”以及顺序唤醒但缺点是资源分配随机性强可能会出现线程饿死的情况。 3如何使用公平锁 通过RedissonClient的getFairLock就可以获取到公平锁。Redisson对于公平锁的实现是RedissonFairLock类通过RedissonFairLock来加锁就能实现公平锁的特性使用代码如下。 RedissonFairLock继承了RedissonLock主要是重写了tryLockInnerAsync方法也就是加锁逻辑的方法。 下面来分析一下RedissonFairLock的加锁逻辑。 这段加锁的逻辑很长我就简单说一下这段lua脚本干了啥。 当线程来加锁的时候如果加锁失败了那么会将线程扔到一个set集合中这样就按照加锁的顺序给线程排队set集合的头部的线程就代表了接下来能够加锁成功的线程。当有线程释放了锁之后其它加锁失败的线程就会来继续加锁加锁之前会先判断一下set集合的头部的线程跟当前要加锁的线程是不是同一个如果是的话那就加锁成功如果不是的话那么就加锁失败这样就实现了加锁的顺序性。 当然这段lua脚本还做了一些其它细节的事这里就不再赘述。 十二、如何实现读写锁 在实际的业务场景中其实会有很多读多写少的场景那么对于这种场景来说使用独占锁来加锁在高并发场景下会导致大量的线程加锁失败阻塞对系统的吞吐量有一定的影响为了适配这种读多写少的场景Redisson也实现了读写锁的功能。 读写锁的特点 读与读是共享的不互斥 读与写互斥 写与写互斥 Redisson使用读写锁的代码。 Redisson通过RedissonReadWriteLock类来实现读写锁的功能通过这个类可以获取到读锁或者写锁所以真正的加锁的逻辑是由读锁和写锁实现的。 那么Redisson是如何具体实现读写锁的呢 前面说过加锁成功之后会在redis中维护一个hash的数据结构存储加锁线程和加锁次数。在读写锁的实现中会往hash数据结构中多维护一个mode的字段来表示当前加锁的模式。 所以能够实现读写锁最主要是因为维护了一个加锁模式的字段mode这样有线程来加锁的时候就能根据当前加锁的模式结合读写的特性来判断要不要让当前来加锁的线程加锁成功。 如果没有加锁那么不论是读锁还是写锁都能加成功成功之后根据锁的类型维护mode字段。 如果模式是读锁那么加锁线程是来加读锁的就让它加锁成功。 如果模式是读锁那么加锁线程是来加写锁的就让它加锁失败。 如果模式是写锁那么加锁线程是来加写锁的就让它加锁失败加锁线程自己除外。 如果模式是写锁那么加锁线程是来加读锁的就让它加锁失败加锁线程自己除外。 十三、如何实现批量加锁(联锁) 批量加锁的意思就是同时加几个锁只有这些锁都算加成功了才是真正的加锁成功。 比如说在一个下单的业务场景中同时需要锁定订单、库存、商品基于这种需要锁多种资源的场景中Redisson提供了批量加锁的实现对应的实现类是RedissonMultiLock。 Redisson提供了批量加锁使用代码如下。 Redisson对于批量加锁的实现其实很简单源码如下 就是根据顺序去依次调用传入myLock1、myLock2、myLock3 加锁方法然后如果都成功加锁了那么multiLock就算加锁成功。 十四、Redis分布式锁存在的问题 对于单Redis实例来说如果Redis宕机了那么整个系统就无法工作了。所以为了保证Redis的高可用性一般会使用主从或者哨兵模式。但是如果使用了主从或者哨兵模式此时Redis的分布式锁的功能可能就会出现问题。 举个例子来说假如现在使用了哨兵模式如图。 基于这种模式Redis客户端会在master节点上加锁然后异步复制给slave节点。 但是突然有一天因为一些原因master节点宕机了那么哨兵节点感知到了master节点宕机了那么就会从slave节点选择一个节点作为主节点实现主从切换如图 这种情况看似没什么问题但是不幸的事发生了那就是客户端对原先的主节点加锁加成之后还没有来得及同步给从节点主节点宕机了从节点变成了主节点此时从节点是没有加锁信息的如果有其它的客户端来加锁是能够加锁成功的这不是很坑爹么。。 那么如何解决这种问题呢Redis官方提供了一种叫RedLock的算法Redisson刚好实现了这种算法接着往下看。 十五、如何实现RedLock算法 RedLock算法 在Redis的分布式环境中我们假设有N个Redis master。这些节点完全互相独立不存在主从复制或者其他集群协调机制。之前我们已经描述了在Redis单实例下怎么安全地获取和释放锁。我们确保将在每N)个实例上使用此方法获取和释放锁。在这个样例中我们假设有5个Redis master节点这是一个比较合理的设置所以我们需要在5台机器上面或者5台虚拟机上面运行这些实例这样保证他们不会同时都宕掉。 为了取到锁客户端应该执行以下操作: 获取当前Unix时间以毫秒为单位。 依次尝试从N个实例使用相同的key和随机值获取锁。在步骤2当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应客户端应该尽快尝试另外一个Redis实例。 客户端使用当前时间减去开始获取锁时间步骤1记录的时间就得到获取锁使用的时间。当且仅当从大多数这里是3个节点的Redis节点都取到锁并且使用的时间小于锁失效时间时锁才算获取成功。 如果取到了锁key的真正有效时间等于有效时间减去获取锁所使用的时间步骤3计算的结果。 如果因为某些原因获取锁失败没有在至少N/21个Redis实例取到锁或者取锁时间已经超过了有效时间客户端应该在所有的Redis实例上进行解锁即便某些Redis实例根本就没有加锁成功。 Redisson对RedLock算法的实现 使用方法如下。 RLock lock1  redissonInstance1.getLock(lock1);RLock lock2 redissonInstance2.getLock(lock2);RLock lock3 redissonInstance3.getLock(lock3); RedissonRedLock lock new RedissonRedLock(lock1, lock2, lock3);// 同时加锁lock1 lock2 lock3// 红锁在大部分节点上加锁成功就算成功。lock.lock();...lock.unlock(); RedissonRedLock加锁过程如下 获取所有的redisson node节点信息循环向所有的redisson node节点加锁假设节点数为N例子中N等于5。一个redisson node代表一个主从节点。 如果在N个节点当中有N/2 1个节点加锁成功了那么整个RedissonRedLock加锁是成功的。 如果在N个节点当中小于N/2 1个节点加锁成功那么整个RedissonRedLock加锁是失败的。 如果中途发现各个节点加锁的总耗时大于等于设置的最大等待时间则直接返回失败。 RedissonRedLock底层其实也就基于RedissonMultiLock实现的RedissonMultiLock要求所有的加锁成功才算成功RedissonRedLock要求只要有N/2 1个成功就算成功。
http://www.zqtcl.cn/news/488372/

相关文章:

  • 天津做网站外包公司有哪些美橙互联网站
  • 石家庄网站建设蓝点办公室装修工程
  • 申请网站空间就是申请域名建设机械网站咨询
  • 做美食网站有哪些网站怎么做自响应
  • 衡水网站建设维护宝安官网网站建设比较好的
  • 网站建设的审批重庆建设工程信息网30系统
  • 泉州软件开发培训机构怎么做网站内部链接的优化
  • 网站定位是什么中国it外包公司排名
  • 洛阳微信平台网站建设网站成功案例分析
  • 网站建设在淘宝怎么分类深圳软件开发招聘信息
  • .net如何做网站个人网站的制作
  • 网站优化排名推广站长统计官方网站
  • 长沙wap网站建设wordpress 用户 函数
  • 淮安做网站的公司有哪些公司目前上海有几个区
  • 怎么做自动跳转网站建站之星 discuz
  • 网站建设开发合同范本页面设计有哪几种风格
  • 重庆做网站重庆做网站做公司网站建设价格
  • 住房建设部官方网站公示公告国内卖到国外的电商平台
  • 安徽省建设厅网站巅川建设有限公司宁波城乡建设网站
  • 做财务还是网站运营wordpress主题 微博
  • 为什么要用CGI做网站网站建设 自学 电子版 pdf下载
  • 建设网站的规则营销型网站建设jm3q
  • 深圳建网站价格防水堵漏公司做网站效果怎样
  • 网站建设东莞老铁博客外国炫酷网站网址
  • 笔杆子写作网站牡丹江信息网0453免费发布信息
  • 网站建设介绍推广用语解释seo网站推广
  • 加盟企业网站建设目的速卖通下载app
  • 阳江北京网站建设网页设计与网站建设pdf
  • 做考试平台的网站网站之前没备案
  • 网站维护要多久时间北京网站优化哪家好