Maison  >  Article  >  Java  >  Explication détaillée du principe de la méthode de verrouillage distribué mise en œuvre par Redisson

Explication détaillée du principe de la méthode de verrouillage distribué mise en œuvre par Redisson

巴扎黑
巴扎黑original
2017-08-22 16:46:272895parcourir

Verrou distribué RedissonQySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

Le verrou basé sur les annotations précédent en a un Le type de verrou est un verrou distribué Redis de base. J'implémente le verrou basé sur le RLock fourni par le composant Redisson. Cet article examinera comment Redisson implémente le verrou. QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

Différentes versions ont différents mécanismes de verrouillage QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Le réseau

a cité la version 3.2.3 récemment publiée de Redisson. Différentes versions peuvent implémenter différents mécanismes de verrouillage. Les premières versions semblent utiliser de simples commandes setnx, getset et autres. La configuration est terminée, mais plus tard. le principe d'implémentation a été modifié car redis prend en charge le script Lua. QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.2.3</version></dependency>

setnx doit être complété avec getset et transactions, afin de mieux éviter les problèmes de blocage, et depuis la nouvelle version prend en charge les scripts Lua, elle peut éviter d'utiliser des transactions et d'exécuter plusieurs commandes Redis, et l'expression sémantique est plus claire. Réseau d'apprentissage chinois QySHTML5-Réseau d'apprentissage pionnier HTML5

Caractéristiques de l'interface RLockRéseau d'apprentissage chinois QySHTML5-Réseau d'apprentissage pionnier HTML5

Hérite du verrouillage d'interface standardQySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

Possède un verrouillage standard interface Toutes les fonctionnalités, telles que le verrouillage, le déverrouillage, le trylock, etc. Réseau d'apprentissage chinois QySHTML5 - Réseau d'apprentissage HTML5 Pioneer

Verrouillage de l'interface standard étenduRéseau d'apprentissage chinois QySHTML5 - Réseau d'apprentissage HTML5 Pioneer

étend de nombreuses méthodes, les plus couramment utilisées sont : le déverrouillage forcé, le verrouillage avec période de validité et un ensemble de méthodes asynchrones. Les deux premières méthodes visent principalement à résoudre le problème de blocage qui peut être causé par le verrouillage standard. Par exemple, après qu'un thread a acquis un verrou, la machine sur laquelle se trouve le thread tombe en panne. À ce moment-là, le thread qui a acquis le verrou ne peut pas libérer le verrou normalement, ce qui fait attendre les threads restants en attente du verrou. Réseau d'apprentissage chinois QySHTML5-Réseau d'apprentissage pionnier HTML5

Mécanisme réentrantRéseau d'apprentissage chinois QySHTML5-Réseau d'apprentissage pionnier HTML5

L'implémentation de chaque version est différente. La principale considération pour la réentrance est la performance. Si le même thread demande à nouveau des ressources de verrouillage sans libérer le verrou, il n'a pas besoin de passer par le processus de candidature. pour continuer à renvoyer le verrou qui a été acquis et enregistrez simplement le nombre de réentrées, ce qui est similaire à la fonction ReentrantLock dans jdk. Le nombre de réentrées est utilisé conjointement avec la commande hincrby. Les paramètres détaillés sont dans le code ci-dessous. QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

Comment déterminer s'il s'agit du même fil de discussion ? QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

La solution de Redisson consiste à ajouter un guid de l'instance RedissonLock à l'identifiant de l'instance actuelle thread, Return by getLockName QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

public class RedissonLock extends RedissonExpirable implements RLock { final UUID id; protected RedissonLock(CommandExecutor commandExecutor, String name, UUID id) {  super(commandExecutor, name);  this.internalLockLeaseTime = TimeUnit.SECONDS.toMillis(30L);  this.commandExecutor = commandExecutor;  this.id = id; } String getLockName(long threadId) {  return this.id + ":" + threadId; }

RLock obtient le Scénario de deux noms de verrou QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

Voici le code source de tryLock : la méthode tryAcquire consiste à demander le verrou et à revenir le temps restant de la période de validité du verrou, s'il est vide, cela signifie que le verrou n'a pas été directement acquis et renvoyé par d'autres threads. Si le temps est obtenu, la logique de compétition en attente sera entrée. QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {  long time = unit.toMillis(waitTime);  long current = System.currentTimeMillis();  final long threadId = Thread.currentThread().getId();  Long ttl = this.tryAcquire(leaseTime, unit);  if(ttl == null) {   //直接获取到锁   return true;  } else {   //有竞争的后续看  } }

Pas de concurrence, obtenez le verrou directement QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

Jetons d'abord un coup d'œil à ce que Redis fait après avoir d'abord acquis le verrou et libéré le verrou. Vous pouvez utiliser le moniteur Redis pour surveiller l'exécution. de redis en arrière-plan. Lorsque nous ajoutons @RequestLockable à la méthode, nous appelons en fait lock et unlock. Voici les commandes redis : QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

Ajouter. LockQySHTML5 Chinese Learning Network-HTML5 Pioneer Learning Network

Étant donné que les versions supérieures de Redis prennent en charge les scripts Lua, Redisson le prend également en charge et adopte le mode script, ceux qui ne le sont pas familier avec les scripts Lua peut le rechercher. La logique d'exécution des commandes lua est la suivante : QySHTML5 Chinese Learning Network - HTML5 Pioneer Learning Network

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {    this.internalLockLeaseTime = unit.toMillis(leaseTime);    return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call(/&#39;exists/&#39;, KEYS[1]) == 0) then redis.call(/&#39;hset/&#39;, KEYS[1], ARGV[2], 1); redis.call(/&#39;pexpire/&#39;, KEYS[1], ARGV[1]); return nil; end; if (redis.call(/&#39;hexists/&#39;, KEYS[1], ARGV[2]) == 1) then redis.call(/&#39;hincrby/&#39;, KEYS[1], ARGV[2], 1); redis.call(/&#39;pexpire/&#39;, KEYS[1], ARGV[1]); return nil; end; return redis.call(/&#39;pttl/&#39;, KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{Long.valueOf(this.internalLockLeaseTime), this.getLockName(threadId)});  }

加锁的流程:QySHTML5中文学习网 - HTML5先行者学习网

  1. 判断lock键是否存在,不存在直接调用hset存储当前线程信息并且设置过期时间,返回nil,告诉客户端直接获取到锁。

  2. 判断lock键是否存在,存在则将重入次数加1,并重新设置过期时间,返回nil,告诉客户端直接获取到锁。

  3. 被其它线程已经锁定,返回锁有效期的剩余时间,告诉客户端需要等待。

"EVAL" "if (redis.call(&#39;exists&#39;, KEYS[1]) == 0) then redis.call(&#39;hset&#39;, KEYS[1], ARGV[2], 1); redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); return nil; end;if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[2]) == 1) then redis.call(&#39;hincrby&#39;, KEYS[1], ARGV[2], 1); redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); return nil; end;return redis.call(&#39;pttl&#39;, KEYS[1]);" "1" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"  "1000" "346e1eb8-5bfd-4d49-9870-042df402f248:21"

上面的lua脚本会转换成真正的redis命令,下面的是经过lua脚本运算之后实际执行的redis命令。QySHTML5中文学习网 - HTML5先行者学习网

1486642677.053488 [0 lua] "exists" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"1486642677.053515 [0 lua] "hset" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0" "346e1eb8-5bfd-4d49-9870-042df402f248:21" "1"1486642677.053540 [0 lua] "pexpire" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0" "1000"

解锁QySHTML5中文学习网 - HTML5先行者学习网

解锁的流程看起来复杂些:QySHTML5中文学习网 - HTML5先行者学习网

  1. 如果lock键不存在,发消息说锁已经可用

  2. 如果锁不是被当前线程锁定,则返回nil

  3. 由于支持可重入,在解锁时将重入次数需要减1

  4. 如果计算后的重入次数>0,则重新设置过期时间

  5. 如果计算后的重入次数<=0,则发消息说锁已经可用

"EVAL" "if (redis.call(&#39;exists&#39;, KEYS[1]) == 0) then redis.call(&#39;publish&#39;, KEYS[2], ARGV[1]); return 1; end;if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[3]) == 0) then return nil;end; local counter = redis.call(&#39;hincrby&#39;, KEYS[1], ARGV[3], -1); if (counter > 0) then redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[2]); return 0; else redis.call(&#39;del&#39;, KEYS[1]); redis.call(&#39;publish&#39;, KEYS[2], ARGV[1]); return 1; end; return nil;""2" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0" "redisson_lock__channel:{lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0}" "0" "1000" "346e1eb8-5bfd-4d49-9870-042df402f248:21"

无竞争情况下解锁redis命令:QySHTML5中文学习网 - HTML5先行者学习网

主要是发送一个解锁的消息,以此唤醒等待队列中的线程重新竞争锁。QySHTML5中文学习网 - HTML5先行者学习网

1486642678.493691 [0 lua] "exists" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"1486642678.493712 [0 lua] "publish" "redisson_lock__channel:{lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0}" "0"

有竞争,等待QySHTML5中文学习网 - HTML5先行者学习网

有竞争的情况在redis端的lua脚本是相同的,只是不同的条件执行不同的redis命令,复杂的在redisson的源码上。当通过tryAcquire发现锁被其它线程申请时,需要进入等待竞争逻辑中。QySHTML5中文学习网 - HTML5先行者学习网

  • this.await返回false,说明等待时间已经超出获取锁最大等待时间,取消订阅并返回获取锁失败

  • this.await返回true,进入循环尝试获取锁。

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {    long time = unit.toMillis(waitTime);    long current = System.currentTimeMillis();    final long threadId = Thread.currentThread().getId();    Long ttl = this.tryAcquire(leaseTime, unit);    if(ttl == null) {      return true;    } else {      //重点是这段      time -= System.currentTimeMillis() - current;      if(time <= 0L) {        return false;      } else {        current = System.currentTimeMillis();        final RFuture subscribeFuture = this.subscribe(threadId);        if(!this.await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {          if(!subscribeFuture.cancel(false)) {            subscribeFuture.addListener(new FutureListener() {              public void operationComplete(Future<RedissonLockEntry> future) throws Exception {                if(subscribeFuture.isSuccess()) {                  RedissonLock.this.unsubscribe(subscribeFuture, threadId);                }              }            });          }          return false;        } else {          boolean var16;          try {            time -= System.currentTimeMillis() - current;            if(time <= 0L) {              boolean currentTime1 = false;              return currentTime1;            }            do {              long currentTime = System.currentTimeMillis();              ttl = this.tryAcquire(leaseTime, unit);              if(ttl == null) {                var16 = true;                return var16;              }              time -= System.currentTimeMillis() - currentTime;              if(time <= 0L) {                var16 = false;                return var16;              }              currentTime = System.currentTimeMillis();              if(ttl.longValue() >= 0L && ttl.longValue() < time) {                this.getEntry(threadId).getLatch().tryAcquire(ttl.longValue(), TimeUnit.MILLISECONDS);              } else {                this.getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);              }              time -= System.currentTimeMillis() - currentTime;            } while(time > 0L);            var16 = false;          } finally {            this.unsubscribe(subscribeFuture, threadId);          }          return var16;        }      }    }  }

循环尝试一般有如下几种方法:QySHTML5中文学习网 - HTML5先行者学习网

  • while循环,一次接着一次的尝试,这个方法的缺点是会造成大量无效的锁申请。

  • Thread.sleep,在上面的while方案中增加睡眠时间以降低锁申请次数,缺点是这个睡眠的时间设置比较难控制。

  • 基于信息量,当锁被其它资源占用时,当前线程订阅锁的释放事件,一旦锁释放会发消息通知待等待的锁进行竞争,有效的解决了无效的锁申请情况。核心逻辑是this.getEntry(threadId).getLatch().tryAcquire,this.getEntry(threadId).getLatch()返回的是一个信号量,有兴趣可以再研究研究。

redisson依赖QySHTML5中文学习网 - HTML5先行者学习网

由于redisson不光是针对锁,提供了很多客户端操作redis的方法,所以会依赖一些其它的框架,比如netty,如果只是简单的使用锁也可以自己去实现。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn