Il existe généralement trois façons d'implémenter les verrous distribués: 1. Verrouillage optimiste de la base de données; 2. Lock distribué basé sur Redis; 3. Lock distribué basé sur Zookeeper. Ce blog présentera la deuxième méthode, qui est d'implémenter le verrouillage distribué basé sur Redis. Bien qu'il existe divers blogs sur Internet qui introduisent la mise en œuvre des verrous distribués Redis, leur implémentation a divers problèmes. Afin d'éviter les enfants trompeurs, ce blog présentera en détail comment mettre correctement en œuvre les verrous distribués Redis.
fiabilité
Premièrement, afin de garantir que des verrous distribués sont disponibles, nous devons au moins nous assurer que la mise en œuvre de la serrure remplit les quatre conditions suivantes en même temps:
Exclusion mutuelle. À tout moment, un seul client peut maintenir la serrure.
Aucune impasse ne se produit. Même si un client se bloque pendant la période de maintien de verrouillage sans le déverrouiller activement, il peut garantir que d'autres clients peuvent ajouter des verrous.
Tolérant aux pannes. Tant que la plupart des nœuds Redis fonctionnent normalement, le client peut verrouiller et déverrouiller.
La personne qui a attaché la cloche doit être délire. Le verrouillage et le déverrouillage doivent être le même client, et le client ne peut pas détacher les serrures ajoutées par d'autres.
Implémentation de code
Dépendances des composants
Tout d'abord, nous devons introduire des composants open source Jedis via Maven et ajouter le code suivant au fichier pom.xml:
<dependency> <proupId> redis.clients </proupId> <ArtefactId> Jedis </letefactive> <version> 2.9.0 </DERNIFRIGNE> </DENDENCENCE>
Code de verrouillage
Posture correcte
Parler est bon marché, montrez-moi le code. Affichez d'abord le code, puis expliquez pourquoi cela est implémenté:
classe publique restool {private static final string lock_success = "ok"; private static final String set_if_not_exist = "nx"; chaîne finale statique privée set_with_expire_time = "px"; / ** * essaie d'obtenir le verrouillage distribué * @param jedis redis client * @param lockkey lock * @param demande demander demande id * @param expirer expire est obtenu avec succès * / public static booléen trygetdestributedlock (Jedis jedis, string lockkey, string requestId, int expiretime) {String result = jedis.set (LockkeComme vous pouvez le voir, nous ajoutons simplement une seule ligne de code: Jedis.set (StringKey, StringValue, StringNxxx, StringExpx, intTime). Cette méthode set () a cinq paramètres formels au total:
Le premier est une clé, nous utilisons la touche comme verrouillage, car la clé est unique.
Le second est la valeur. Ce que nous transmettons, c'est demandeID. De nombreuses chaussures pour enfants peuvent ne pas comprendre. N'est-ce pas suffisant d'avoir une clé comme verrouillage? Pourquoi avons-nous encore besoin d'utiliser de la valeur? La raison en est que lorsque nous avons parlé de la fiabilité ci-dessus, la serrure distribuée doit remplir la quatrième condition pour décompresser la cloche. En attribuant la valeur au demandeID, nous saurons quelle demande a été ajoutée au verrou et il y aura une base de déverrouillage. RequestId peut être généré à l'aide de la méthode UUID.randomuuid (). ToString ().
Le troisième est nxxx. Nous remplissons ce paramètre NX, ce qui signifie SEFIFNOTEXIST, c'est-à-dire que lorsque la clé n'existe pas, nous effectuons l'opération définie; Si la clé existe déjà, aucune opération n'est effectuée;
Le quatrième est expx. Nous passons ce paramètre PX, ce qui signifie que nous voulons ajouter un paramètre expiré à cette touche. Le temps spécifique est déterminé par le cinquième paramètre.
Le cinquième est le temps, qui fait écho au quatrième paramètre et représente le temps d'expiration de la clé.
En général, l'exécution de la méthode set () ci-dessus conduira uniquement à deux résultats: 1. Il n'y a pas de verrouillage actuellement (la touche n'existe pas), puis l'opération de verrouillage est effectuée et le verrouillage est défini pour être valide pour le verrouillage, et la valeur représente le client verrouillé. 2. Il existe déjà un verrou et aucune opération n'est effectuée.
Si vous faites attention, vous constaterez que notre code de verrouillage remplit les trois conditions décrites dans notre fiabilité. Tout d'abord, Set () ajoute des paramètres NX, qui peuvent garantir que si une clé existe déjà, la fonction ne sera pas appelée avec succès, c'est-à-dire qu'un seul client peut maintenir le verrou pour satisfaire Mutex. Deuxièmement, puisque nous définissons un temps d'expiration pour le verrou, même si le support de verrouillage se bloque dans le crash ultérieur et ne se déverrouille pas, la serrure sera automatiquement déverrouillée car elle a atteint le temps d'expiration (c'est-à-dire que la clé est supprimée), et il n'y aura pas de blocage. Enfin, parce que nous attribuons la valeur à DequedId, qui représente l'identité de demande du client verrouillé, le client peut vérifier s'il s'agit du même client lors du déverrouillage. Étant donné que nous ne considérons que les scénarios de déploiement autonomes Redis, nous ne considérerons pas la tolérance aux pannes pour le moment.
Erreur Exemple 1
Un exemple d'erreur courant consiste à utiliser la combinaison de jedis.setnx () et jedis.expire () pour obtenir le verrouillage. Le code est le suivant:
public static void tortgetlock1 (Jedis Jedis, String Lockkey, String requestId, int expirtime) {long result = jedis.setnx (lockkey, requestId); if (result == 1) {// si le programme se bloque soudainement ici, le temps d'expiration ne peut pas être réglé, et une impasse se produira Jedis.expire (Lockkey, Expiration);}}La fonction de la méthode setnx () est SETIFNOTEXIST, et la méthode Expire () consiste à ajouter un temps d'expiration au verrou. À première vue, il semble être le même que la méthode SET () précédente. Cependant, comme ce sont deux commandes Redis, elles ne sont pas atomiques. Si le programme se bloque soudainement après l'exécution de setnx (), le verrou ne définit pas le temps d'expiration. Alors une impasse se produira. La raison pour laquelle certaines personnes implémentent cela sur Internet est que la version inférieure de Jedis ne prend pas en charge la méthode multi-paramètres ().
Erreur Exemple 2
Cet exemple d'erreur est plus difficile à trouver des problèmes et l'implémentation est également plus compliquée. Idée d'implémentation: utilisez la commande jedis.setnx () pour implémenter le verrouillage, où la clé est le verrou et la valeur est le temps d'expiration du verrou. Processus d'exécution: 1. Essayez d'ajouter un verrou via la méthode setnx (). Si le verrouillage actuel n'existe pas, le verrou sera renvoyé avec succès. 2. Si la serrure existe déjà, acquérez le temps d'expiration du verrou et comparez-le avec l'heure actuelle. Si le verrou a expiré, définissez un nouveau temps d'expiration et renvoyez, le verrouillage est ajouté avec succès. Le code est le suivant:
public static boolean tortgetlock2 (Jedis jedis, String Lockkey, int expiretime) {long expires = System.currenttimemillis () + Expiretime; String Expiresstr = String.Valueof (expires); // si le verrouillage actuel n'existe pas, le verrouillage est retourné avec succès si (Jedis.Setnx (Lockke Si le verrouillage existe, le temps d'expiration du verrou est obtenu la chaîne CurrentValuestr = jedis.get (Lockkey); if (currentValuestr! = Null && long.parselong (currentValuestr) <System.currenttimemillis ()) {// Le verrou a expiré, obtenez le temps d'expiration de l'expiration du verrouillage, et définit l'expiration Jedis.getset (Lockkey, Expiresstr); if (oldValuestr! = null && oldvaluestr.equals (currentValuestr)) {// Compte tenu du cas de la concurrence multi-thread, seulement si la valeur de réglage d'un thread est la même que la valeur actuelle, il a le droit de verrouiller true;}} // dans d'autres cas, l'échec de verrouillage sera retourné faux;}Alors, quel est le problème avec ce code? 1. Étant donné que le client génère le temps d'expiration lui-même, il est nécessaire de forcer le temps de chaque client à être synchronisé sous l'approche distribuée. 2. Lorsque le verrouillage expire, si plusieurs clients exécutent la méthode jedis.getSet () en même temps, bien qu'un seul client puisse se verrouiller, le temps d'expiration du verrou de ce client peut être écrasé par d'autres clients. 3. Le verrou n'a pas le logo du propriétaire, c'est-à-dire que tout client peut le déverrouiller.
Déverrouiller le code
Posture correcte
Affichons d'abord le code, puis expliquons lentement pourquoi cela est implémenté:
public class RedisTool {private static final long RELEASE_SUCCESS = 1L;/** * Release the distributed lock* @param jedis Redis client* @param lockKey lock* @param requestId Request ID* @return Whether the release was successful*/public static Boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {String script = "if redis.call('get', KEYS[1]) == argv [1] puis return redis.call ('del', clés [1]) else return 0 end "; objet result = jedis.eval (script, collections.Singletonlist (lockkey), collections.Singletonlist (requestId)); if (release_success.equals (résultat)) {return true;} return false;}}Comme vous pouvez le voir, nous n'avons besoin que de deux lignes de code pour la déverrouiller! Dans la première ligne de code, nous avons écrit un simple code de script LUA. La dernière fois que nous avons vu ce langage de programmation, c'était dans "Hacker and Painter", mais nous ne nous attendions pas à ce qu'il soit utilisé cette fois. Dans la deuxième ligne de code, nous passons le code LUA à la méthode jedis.eval () et attribuons les touches de paramètre [1] à Lockkey et Argv [1] à DequedId. La méthode EVAL () consiste à remettre le code LUA au serveur Redis pour exécution.
Alors, quelle est la fonction de ce code LUA? En fait, c'est très simple. Tout d'abord, obtenez la valeur correspondant au verrouillage, vérifiez si elle est égale à DequedId et si elle est égale, supprimez le verrou (déverrouiller). Alors pourquoi utiliser la langue Lua pour l'implémenter? Car il est nécessaire de s'assurer que les opérations ci-dessus sont atomiques. Pour quels problèmes d'atomicité apportera, vous pouvez lire [déverrouiller le code - Erreur Exemple 2]. Alors, pourquoi puis-je exécuter la méthode eval () assurer l'atomicité, qui provient des caractéristiques de Redis. Voici une explication partielle de la commande EVAL sur le site officiel:
Autrement dit, lorsque la commande EVAL exécute le code LUA, le code LUA sera exécuté en tant que commande et Redis n'exécutera pas d'autres commandes tant que la commande EVAL sera exécutée.
Erreur Exemple 1
Le code de déverrouillage le plus courant consiste à utiliser directement la méthode Jedis.del () pour supprimer le verrou. Cette méthode de déverrouillage directement sans juger d'abord le propriétaire de la serrure entraînera un déverrouillage de tout client à tout moment, même si le verrou n'est pas le sien.
public static void tailleleaselock1 (Jedis Jedis, String Lockkey) {Jedis.del (Lockkey); }Erreur Exemple 2
À première vue, ce code de déverrouillage est très bien. Je l'ai même presque implémenté comme ça auparavant, ce qui est similaire à la bonne posture. La seule différence est qu'il est divisé en deux commandes à exécuter. Le code est le suivant:
public static void tortReleSelock2 (Jedis Jedis, String Lockkey, String requestId) {// Déterminez si le verrouillage et le déverrouillage sont le même client si (requestId.equalQuant aux commentaires du code, le problème est que si la méthode Jedis.del () est appelée, le verrou est déverrouillé lorsqu'il n'appartient plus au client actuel. Alors, y a-t-il vraiment un tel scénario? La réponse est oui. Par exemple, le client a verrouillage, et après une période de temps, le client a déverrouillé. Avant d'exécuter Jedis.del (), la serrure expire soudainement. À l'heure actuelle, le client B essaie de verrouiller avec succès, puis le client A exécute la méthode del (), puis le verrouillage du client B est déverrouillé.
Résumer
Cet article présente principalement comment implémenter correctement le verrouillage distribué Redis à l'aide du code Java. Deux exemples d'erreur classiques sont donnés pour le verrouillage et le déverrouillage. En fait, il n'est pas difficile de mettre en œuvre des verrous distribués via Redis, tant qu'il est garanti de répondre aux quatre conditions de fiabilité.
Dans quel scénario les verrous distribués sont-ils principalement utilisés? Lorsque la synchronisation est requise, par exemple, l'insertion d'un élément de données nécessite de vérifier à l'avance si la base de données a des données similaires. Lorsque plusieurs demandes sont insérées en même temps, il peut être déterminé que la base de données n'a pas de données similaires et toutes peuvent être ajoutées. À l'heure actuelle, un traitement synchrone est requis, mais la table de verrouillage de la base de données directe prend trop de temps, donc le verrouillage distribué redis est utilisé. Dans le même temps, un seul thread peut effectuer le fonctionnement de l'insertion de données et d'autres threads attendent.
Ce qui précède est tout le contenu de cet article sur la langue Java décrivant la bonne implémentation des verrous distribués redis. J'espère que ce sera utile à tout le monde. Les amis intéressés peuvent continuer à se référer à d'autres sujets connexes sur ce site. S'il y a des lacunes, veuillez laisser un message pour le signaler. Merci vos amis pour votre soutien pour ce site!