Lorsque plusieurs hôtes entrent en concurrence pour une ressource partagée, il est indispensable d'utiliser des verrous distribués afin d'éviter la corruption des données et les défaillances logiques. Cette rubrique explique le fonctionnement des verrous distribués, leur mise en œuvre sur ApsaraDB for Redis et Tair (Enterprise Edition), ainsi que le maintien de la cohérence des verrous après un basculement. Les commandes CAD et CAS spécifiques à Tair éliminent le besoin de scripts Lua requis par Redis standard, ce qui réduit la complexité de l'implémentation et améliore le débit.
Quand utiliser des verrous distribués
Les scénarios de concurrence variés nécessitent différents mécanismes de coordination :
| Scénario | Mécanisme |
|---|---|
| Plusieurs threads dans le même processus | Mutex ou verrou lecture/écriture |
| Plusieurs processus sur le même hôte | Sémaphore, pipeline ou mémoire partagée |
| Plusieurs hôtes dans un système distribué | Verrou distribué |
Les verrous distribués sont des verrous d'exclusion mutuelle à portée globale. Utilisez-les pour coordonner l'accès aux ressources partagées entre les hôtes et prévenir les défaillances logiques causées par la contention des ressources.

Propriétés d'un verrou distribué bien implémenté
Un verrou distribué doit satisfaire trois propriétés. Chaque approche d'implémentation présentée dans cette rubrique est évaluée selon ces critères :
Exclusion mutuelle : À tout instant, un seul client détient le verrou.
Absence d'interblocage : Les verrous utilisent un mécanisme basé sur un bail. Si un client rencontre une exception après avoir acquis un verrou, celui-ci expire automatiquement, empêchant ainsi le blocage indéfini des ressources.
Cohérence : Après un basculement de haute disponibilité (HA), l'état du verrou doit rester intact. Les basculements peuvent être déclenchés par des erreurs externes (pannes matérielles, exceptions réseau) ou internes (requêtes lentes, défauts système). Lorsqu'un basculement se produit, un nœud réplica est promu au rang de nouveau nœud maître.
Implémenter des verrous distribués sur Redis
Les méthodes décrites dans cette section s'appliquent à la fois à ApsaraDB for Redis et à l'édition Open Source de Redis.
Acquérir un verrou
Exécutez la commande SET avec les options NX et EX pour acquérir un verrou de manière atomique :
SET resource_1 random_value NX EX 5
| Paramètre | Description |
|---|---|
resource_1 |
La clé du verrou. Si cette clé existe, la ressource est verrouillée et inaccessible aux autres clients. |
random_value |
Une chaîne aléatoire unique pour tous les clients. Utilisée pour vérifier la propriété avant de libérer le verrou. |
EX |
La période de validité du verrou en secondes. Utilisez PX pour une précision en millisecondes. |
NX |
Annule l'opération SET si la clé existe déjà. |
Dans cet exemple, le verrou expire après 5 secondes s'il n'est pas libéré explicitement. Cette expiration prévient les interblocages : si le client plante, le système récupère automatiquement le verrou.
Libérer un verrou
L'exécution directe de DEL resource_1 n'est pas sûre. Prenons le scénario d'échec suivant :
À t1, l'application 1 acquiert
resource_1avec un bail de 3 secondes.L'application 1 se bloque pendant plus de 3 secondes. La clé expire à t2 et le verrou est libéré automatiquement.
À t3, l'application 2 acquiert le verrou.
L'application 1 reprend son activité à t4 et exécute
DEL resource_1, libérant par erreur un verrou qu'elle ne possède plus.
Seul le client ayant défini le verrou doit pouvoir le libérer. Utilisez le script Lua suivant pour vérifier la propriété et supprimer la clé de manière atomique :
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
Renouveler un verrou
Si le client ne peut pas terminer son travail dans le délai imparti par le bail, il doit renouveler le verrou. Seul le client ayant défini le verrou peut le renouveler :
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("expire", KEYS[1], ARGV[2])
else
return 0
end
Implémenter des verrous distribués sur Tair
Instances prises en charge : Instances Tair basées sur DRAM et instances optimisées pour la mémoire persistante.
Sur ces instances, le type de données TairString fournit les commandes CAD et CAS. Ces commandes remplacent les scripts Lua mentionnés précédemment par des opérations atomiques uniques, réduisant ainsi la complexité de l'implémentation et améliorant le débit.
Acquérir un verrou
L'acquisition du verrou est identique à l'approche utilisée pour Redis :
SET resource_1 random_value NX EX 5
Libérer un verrou
La CAD (Compare-And-Delete) vérifie la propriété et supprime la clé en une seule opération atomique, remplaçant entièrement le script Lua :
CAD resource_1 my_random_value
Cela équivaut à : si GET(resource_1) == my_random_value, alors exécuter DEL(resource_1).
Renouveler un verrou
La CAS (Compare-And-Swap) prolonge l'expiration du verrou en une seule opération atomique, remplaçant entièrement le script Lua :
CAS resource_1 my_random_value my_random_value EX 10
La commande CAS ne vérifie pas si la nouvelle valeur est égale à la valeur d'origine. Transmettez la même valeur aléatoire pour les champs ancienne et nouvelle valeur afin de préserver l'identité du verrou tout en prolongeant le bail.
Exemple de code Jedis
Les exemples Java suivants illustrent un cycle de vie complet de verrou à l'aide de Jedis. Les trois opérations — acquisition, libération et renouvellement — utilisent les mêmes valeurs resourceKey et randomValue.
Définir les commandes CAS et CAD
enum TairCommand implements ProtocolCommand {
CAD("CAD"), CAS("CAS");
private final byte[] raw;
TairCommand(String alt) {
raw = SafeEncoder.encode(alt);
}
@Override
public byte[] getRaw() {
return raw;
}
}
Acquérir un verrou
public boolean acquireDistributedLock(Jedis jedis, String resourceKey, String randomValue, int expireTime) {
SetParams setParams = new SetParams();
setParams.nx().ex(expireTime);
String result = jedis.set(resourceKey, randomValue, setParams);
return "OK".equals(result);
}
Libérer un verrou
public boolean releaseDistributedLock(Jedis jedis, String resourceKey, String randomValue) {
jedis.getClient().sendCommand(TairCommand.CAD, resourceKey, randomValue);
Long ret = jedis.getClient().getIntegerReply();
return 1 == ret;
}
Renouveler un verrou
public boolean renewDistributedLock(Jedis jedis, String resourceKey, String randomValue, int expireTime) {
jedis.getClient().sendCommand(TairCommand.CAS, resourceKey, randomValue, randomValue, "EX", String.valueOf(expireTime));
Long ret = jedis.getClient().getIntegerReply();
return 1 == ret;
}
Garantir la cohérence des verrous après un basculement
La réplication du maître vers le réplica est asynchrone. Si un nœud maître tombe en panne après avoir écrit un verrou mais avant que l'écriture ne soit répliquée, le réplica promu ne conserve aucune trace de ce verrou. Deux clients pourraient alors détenir simultanément le même verrou, violant ainsi la propriété de cohérence.
Trois approches permettent de remédier à ce risque :
| Approche | Coût | Limitations | Idéal pour |
|---|---|---|---|
| Algorithme Redlock | Élevé (nécessite plusieurs instances) | Ne peut pas être utilisé avec des instances cluster ou maître-réplica standard ; acquisition de verrou plus lente | Exigences de tolérance aux pannes les plus élevées |
| Commande WAIT | Faible | Ne garantit pas la cohérence si un basculement se produit avant le retour de WAIT | Déploiements standards où le coût est important |
| Réplication semi-synchrone Tair | Intégrée | Se dégrade en mode asynchrone en cas de panne d'un réplica | Charges de travail à forte concurrence sur les instances Tair |
Utiliser l'algorithme Redlock
L'algorithme Redlock, proposé par les fondateurs du projet open source Redis, réduit la probabilité de perte de verrou en distribuant les verrous sur N instances Redis indépendantes. Une instance maître-réplica unique peut perdre un verrou avec une probabilité de k% lors d'un basculement. Avec Redlock, la probabilité que toutes les N instances perdent leurs verrous simultanément chute à (k%)^N.
L'algorithme exige que les verrous soient obtenus sur M nœuds sur N, où 1 < M ≤ N. Il n'est pas nécessaire que les N verrous soient obtenus simultanément.
Compromis liés à Redlock :
L'acquisition et la libération des verrous sont plus lentes car plusieurs instances sont impliquées.
Cette solution nécessite plusieurs instances ApsaraDB for Redis ou Redis gérées indépendamment, ce qui augmente les coûts d'infrastructure.
Elle est incompatible avec les instances cluster et les instances maître-réplica standard.
Utiliser la commande WAIT
La commande WAIT bloque le client actuel jusqu'à ce que toutes les commandes d'écriture précédentes soient répliquées sur un nombre spécifié de nœuds réplicas, ou jusqu'à l'expiration du délai (en millisecondes). La commande WAIT est beaucoup plus rentable que l'algorithme Redlock.
Après avoir acquis un verrou, exécutez WAIT pour confirmer la réplication :
SET resource_1 random_value NX EX 5
WAIT 1 5000
Dans cet exemple, le délai est de 5 000 millisecondes. Si WAIT renvoie 1, les données sont synchronisées entre le nœud maître et les nœuds réplicas, garantissant ainsi la cohérence. Toutefois, si un basculement HA est déclenché avant que WAIT ne renvoie une réponse positive, des données peuvent être perdues — la valeur de retour indique uniquement une possible défaillance de synchronisation. En cas d'erreur renvoyée par WAIT, ré-acquérez le verrou ou vérifiez son état avant de poursuivre.
Remarques d'utilisation :
WAIT bloque uniquement le client qui l'envoie ; les autres clients ne sont pas affectés.
N'exécutez pas WAIT avant de libérer un verrou. Les verrous distribués sont conçus pour être mutuellement exclusifs, donc un léger retard dans la libération ne provoque pas de défaillances logiques.
Utiliser Tair
Les instances Tair basées sur DRAM traitent la cohérence des verrous au niveau de l'infrastructure :
Performances triplées : Les instances Tair basées sur DRAM offrent des performances trois fois supérieures à celles de Redis open source, permettant de soutenir des charges de travail de verrouillage à forte concurrence sans interruption de service.
Réplication semi-synchrone : Une réponse positive n'est renvoyée qu'après que l'écriture a été validée sur le nœud maître et répliquée vers au moins un nœud réplica. Cela empêche la perte de verrou après un basculement.
La réplication semi-synchrone se dégrade en réplication asynchrone si un nœud réplica tombe en panne ou si une exception réseau se produit pendant la synchronisation.
Les commandes CAD et CAS réduisent encore davantage la complexité de l'implémentation par rapport aux scripts Lua et éliminent le besoin de plusieurs instances indépendantes requis par Redlock.