Maison > base de données > Redis > le corps du texte

Comment implémenter le verrouillage distribué Redis et quels sont ses scénarios d'application

PHPz
Libérer: 2023-05-30 17:55:51
avant
1581 Les gens l'ont consulté

    Introduction

    Le verrouillage est un outil très courant dans le processus de développement. Vous devez le connaître, verrouillage pessimiste, verrouillage optimiste, verrouillage exclusif, verrouillage équitable, verrouillage injuste, etc., de nombreux concepts, si vous l'êtes. familier avec Java Si vous ne comprenez pas encore les verrous, vous pouvez vous référer à cet article : Les "verrous" Java qu'il faut mentionner Cet article est très complet Cependant, pour les débutants, connaître les concepts de ces verrous n'est peut-être pas possible. en raison du manque d'expérience pratique.Si vous ne comprenez pas les scénarios d'utilisation réels des verrous, vous pouvez utiliser les trois mots-clés Volatile, Synchronized et ReentrantLock pour assurer la sécurité des threads en Java. Cette partie des connaissances sera certainement demandée dans le. première série d'entretiens de base (vous devez les maîtriser).

    Dans un système distribué, les technologies de verrouillage Java ne peuvent pas verrouiller le code sur deux machines en même temps, elles doivent donc être mises en œuvre via des verrous distribués. L'utilisation compétente des verrous distribués est également une compétence qui doit être maîtrisée par les principaux développeurs.

    1. Intervieweur :

    Avez-vous déjà rencontré un scénario dans lequel vous devez utiliser des verrous distribués ?

    Analyse des problèmes : cette question est principalement utilisée à titre d'introduction. Vous devez d'abord comprendre dans quels scénarios les verrous distribués doivent être utilisés et quels problèmes les verrous distribués doivent résoudre. Dans ce principe, cela vous aidera à mieux comprendre le principe de mise en œuvre de. serrures distribuées.

    Les scénarios d'utilisation des verrous distribués doivent généralement répondre aux scénarios suivants :

    • Le système est un système distribué et les verrous Java ne peuvent plus être verrouillés.

    • Exploitez des ressources partagées, telles que les seules données utilisateur de la bibliothèque.

    • L'accès synchrone, c'est-à-dire que plusieurs processus exploitent des ressources partagées en même temps.

    Réponse : Laissez-moi vous donner un exemple de scénario dans lequel j'utilise des verrous distribués dans un projet :

    Les points de consommation sont disponibles dans de nombreux systèmes, notamment les cartes de crédit, les sites de commerce électronique, l'échange de points contre des cadeaux, etc. Voici le fonctionnement des "points de consommation". Il s'agit d'un scénario typique où des verrous sont nécessaires.

    Événement A :

    En prenant comme exemple l'échange de points contre des cadeaux, le processus complet de consommation de points est simplement divisé en 3 étapes :

    A1 : L'utilisateur sélectionne le produit, lance l'échange et soumet la commande.

    A2 : Le système lit les points restants de l'utilisateur : détermine si les points actuels de l'utilisateur sont suffisants.

    A3 : Les points utilisateurs seront déduits.

    Événement B :

    Le système distribue les points aux utilisateurs en trois étapes simples :

    B1 : Calculez les points que l'utilisateur mérite pour la journée

    B2 : Lisez les points originaux de l'utilisateur

    B3 : Ajoutez cette application à l'original points Gagnez des points

    Ensuite la question se pose, que se passe-t-il si l'utilisateur consomme des points et qu'il accumule des points en même temps (les points utilisateur sont exploités en même temps) ?

    Hypothèse : pendant que l'utilisateur consomme des points, une tâche hors ligne consiste à calculer des points et à attribuer des points à l'utilisateur (par exemple, en fonction du montant de la consommation de l'utilisateur ce jour-là). suivre la logique est un peu alambiqué, alors soyez patient et comprenez-la.

    L'utilisateur U dispose de 1 000 points (les points utilisateur d'enregistrement des données peuvent être compris comme des ressources partagées), et cet échange consommera 999 points.

    Situation déverrouillée : Lorsque le programme de l'événement A atteint l'étape 2 de lecture des points, le résultat lu lors de l'opération A:2 est de 1000 points. Il est jugé que les points restants sont suffisants pour cet échange, puis l'étape 3 A est exécutée : Des points seront déduits pour 3 opérations (1000 - 999 = 1). Le résultat normal devrait toujours être de 1 point pour l'utilisateur. Mais l'événement B est également en cours d'exécution à ce moment-là. Cette fois, 100 points seront attribués à l'utilisateur U. Deux threads le feront en même temps (accès synchrone, il y aura la possibilité suivante : A:2 -). > B :2 -> A:3 -> B:3 , avant que A:3 ne soit terminé (points déduits, 1000 - 999), le total des points de l'utilisateur U est lu par le fil de l'événement B, et enfin celui de l'utilisateur U. total de points C'est devenu 1100 points, et j'ai échangé en vain un cadeau de 999 points, ce qui n'a évidemment pas répondu aux résultats escomptés.

    Certaines personnes disent, comment est-il possible de faire fonctionner des points utilisateur en même temps par hasard, et le processeur est si rapide ? Tant qu'il y a suffisamment d'utilisateurs et que la concurrence est suffisamment grande, la loi de Murphy prendra effet tôt ou tard. Ce n'est qu'une question de temps avant que le bug ci-dessus n'apparaisse, et il peut être bloqué par l'industrie noire. À l'heure actuelle, en tant que développeur, si vous voulez résoudre ce danger caché, vous devez comprendre l'utilisation de. déverrouillage.

    (Écrire du code est une chose rigoureuse !)

    Java lui-même fournit deux implémentations de verrous intégrées, l'une est synchronisée implémentée par la JVM et le verrou fourni par le JDK, et de nombreuses classes d'opérations atomiques sont des threads sécurisés, lorsque votre application est une application autonome ou à processus unique, vous pouvez utiliser ces deux verrous pour implémenter des verrous.

    Mais la plupart des systèmes actuels des sociétés Internet sont distribués. À l'heure actuelle, le verrou synchronisé fourni avec Java ne peut plus répondre aux exigences de verrouillage dans un environnement distribué, car le code sera déployé sur plusieurs machines dans l'ordre. Pour résoudre ce problème, les verrous distribués ont vu le jour. Les caractéristiques des verrous distribués sont multi-processus et la mémoire ne peut pas être partagée sur plusieurs machines physiques. La solution courante est basée sur l'interférence de la couche mémoire. basé sur Redis ou ZooKeeper Distributed Lock.

    (Je ne peux pas l'analyser plus en détail, l'intervieweur n'est plus satisfait ?)

    2. Intervieweur :

    Méthode d'implémentation du verrouillage distribué Redis

    Il existe actuellement deux méthodes principales d'implémentation pour résoudre le problème du verrouillage distribué : 1. L'un est basé sur le mode Redis Cluster et l'autre est... 2. Basé sur le mode cluster Zookeeper.

    Donnez la priorité à la maîtrise de ces deux-là, et vous n'aurez pratiquement aucun problème à gérer l'entretien.

    Réponse :

    1. Verrouillage distribué basé sur Redis

    Méthode 1 : Utilisez la commande setnx pour verrouiller

    public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) {
    		// 第一步:加锁
        Long result = jedis.setnx(lockKey, requestId);
        if (result == 1) {
            // 第二步:设置过期时间
            jedis.expire(lockKey, expireTime);
        }
    }
    Copier après la connexion

    Explication du code :

    La commande setnx signifie définie si elle n'existe pas. Si la clé de verrouillage n'existe pas, stockez la clé dans Redis. Si le résultat renvoie 1 après un enregistrement réussi, cela signifie que le paramètre est réussi. S'il n'est pas 1, cela signifie un échec. . D'autres discussions l'ont déjà défini. setnx命令,意思就是 set if not exist,如果lockKey不存在,把key存入Redis,保存成功后如果result返回1,表示设置成功,如果非1,表示失败,别的线程已经设置过了。

    expire()

    expire(), définissez le délai d'expiration pour éviter un blocage. Supposons que si un verrou n'est pas supprimé après avoir été défini, alors le verrou équivaut à toujours exister, ce qui entraîne impasse.

    (À ce stade, je voudrais souligner un "mais" à l'intervieweur)

    Réfléchissez-y, quels sont les défauts de ma méthode ci-dessus ? Continuez à expliquer à l'intervieweur...

    Il y a deux étapes dans le verrouillage. La première étape est jedis.setnx, et la deuxième étape est jedis.expire pour définir le délai d'expiration de setnx et expirer. Il ne s'agit pas d'une opération atomique. Si le programme s'exécute Après avoir terminé la première étape, une exception s'est produite. La deuxième étape, jedis.expire(lockKey, expireTime) n'a pas été exécutée, ce qui signifie que le verrou n'a pas de délai d'expiration et qu'un blocage peut se produire. Comment améliorer ce problème ?

    Amélioration :

    public class RedisLockDemo {
        private static final String SET_IF_NOT_EXIST = "NX";
        private static final String SET_WITH_EXPIRE_TIME = "PX";
        /**
         * 获取分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @param expireTime 超期时间
         * @return 是否获取成功
         */
        public static boolean getLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    				// 两步合二为一,一行代码加锁并设置 + 过期时间。
            if (1 == jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)) {
                return true;//加锁成功
            }
            return false;//加锁失败
        }
    }
    Copier après la connexion

    Explication du code :

    Combinez le verrouillage et le réglage du délai d'expiration en une seule ligne de code, opération atomique.

    (L'intervieweur était très satisfait avant de poser d'autres questions)

    3.

    Réponse :

    Libérer le verrou signifie supprimer la clé

    Utilisez la commande del pour déverrouiller

    public static void unLock(Jedis jedis, String lockKey, String requestId) {
        // 第一步: 使用 requestId 判断加锁与解锁是不是同一个客户端
        if (requestId.equals(jedis.get(lockKey))) {
            // 第二步: 若在此时,这把锁突然不是这个客户端的,则会误解锁
            jedis.del(lockKey);
        }
    }
    Copier après la connexion

    Explication du code : Juger par requestId Le verrouillage et le déverrouillage sont-ils le même client et jedis.del(lockKey) ? Les deux étapes ne sont pas des opérations atomiques, il apparaîtra après la première si l'opération de jugement est terminée que le verrou a effectivement expiré et a été acquis par. d'autres threads. Lors de l'exécution de l'opération jedis.del(lockKey), cela équivaut à libérer le verrou de quelqu'un d'autre, ce qui est déraisonnable. Bien sûr, il s'agit d'une situation très extrême. S'il n'y a pas d'autres opérations commerciales dans les première et deuxième étapes de la méthode de déverrouillage, la mise en ligne du code ci-dessus peut ne pas vraiment poser de problèmes. La première raison est la concurrence commerciale. pas élevé, ce défaut ne sera pas du tout exposé, donc le problème n'est pas grave.

    Mais écrire du code est un travail rigoureux, et pour être parfait, il faut être parfait. Des améliorations sont proposées pour résoudre les problèmes du code ci-dessus.

    Amélioration du code :

    public class RedisTool {
        private static final Long RELEASE_SUCCESS = 1L;
        /**
         * 释放分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @return 是否释放成功
         */
        public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }
    }
    Copier après la connexion

    Explication du code :

    Utilisez la méthode eval du client jedis et une seule ligne de script pour résoudre la première méthode Atomic problèmes impliqués.

    3. Intervieweur :

    Principe de mise en œuvre du verrouillage distribué basé sur ZooKeeper

    Réponse : Encore un exemple de consommation de points et d'accumulation de points : Événement A Le L'opération de modification des points doit être effectuée en même temps que l'événement B. Les deux machines l'exécutent en même temps. La logique métier correcte est de laisser une machine l'exécuter en premier, puis l'autre machine. Soit l'événement A est exécuté en premier, soit. l'événement B est exécuté en premier. De cette manière seulement, nous pouvons garantir que A:2 -> B:2 -> A:3 -> ce genre de bug passe en ligne, le patron va être en colère, je vais peut-être pleurer).

    Que faire ? Utilisez les verrous distribués Zookeeper.

    Après qu'une machine ait reçu la requête, elle obtient d'abord un verrou distribué sur zookeeper (zk créera un znode) et effectuera l'opération ; puis une autre machine essaie également de créer ce znode, et se trouve qu'elle ne peut pas être créé parce qu'il a été créé par quelqu'un d'autre. Vous ne pouvez qu'attendre que la première machine ait fini de s'exécuter avant de pouvoir obtenir le verrou.

    En utilisant la fonctionnalité de nœuds séquentiels de ZooKeeper, si nous créons 3 nœuds dans le répertoire /lock/, le cluster ZK créera les nœuds dans l'ordre dans lequel ils sont créés. Les nœuds sont divisés en /. lock/0000000001, /lock /0000000002, /lock/0000000003, le dernier chiffre est incrémenté dans l'ordre et le nom du nœud est complété par zk.

    Il existe également un nœud appelé nœud temporaire dans ZK. Le nœud temporaire est créé par un client lorsque le client se déconnecte du cluster ZK. EPHEMARAL_SEQUENTIAL est un nœud de séquence temporaire.

    La logique de base du verrouillage distribué est d'utiliser la présence ou l'absence de nœuds dans ZK comme statut de verrouillage pour implémenter le verrouillage distribué
    • Client La fin appelle la méthode create() pour créer un nœud de séquence temporaire nommé "/dlm-locks/lockname/lock-".
    • Le client appelle la méthode getChildren("lockname") pour obtenir tous les nœuds enfants créés.
    • Une fois que le client a obtenu les chemins de tous les nœuds enfants, s'il constate que le nœud qu'il a créé à l'étape 1 a le plus petit numéro de séquence parmi tous les nœuds, il recherchera à la séquence qu'il a créée. Le numéro est-il classé en premier ? S'il est premier, alors on considère que ce client a obtenu le verrou, et qu'aucun autre client n'a obtenu le verrou avant lui.
    • Si le nœud créé n'est pas le plus petit de tous les nœuds, alors il est nécessaire de surveiller le plus grand nœud avec un numéro de séquence plus petit que le nœud que vous avez créé, puis entrer dans l’état d’attente. Une fois le nœud enfant surveillé modifié, obtenez le nœud enfant et déterminez s'il faut obtenir le verrou.

    Bien que le processus de libération du verrou soit relativement simple, qui consiste en fait à supprimer le nœud enfant créé, vous devez toujours prendre en compte les situations anormales telles que l'échec de la suppression du nœud .

    Supplément supplémentaire

    Les verrous distribués peuvent également résoudre les problèmes de la base de données#🎜🎜#

    方法一:

    利用 Mysql 的锁表,创建一张表,设置一个 UNIQUE KEY 这个 KEY 就是要锁的 KEY,所以同一个 KEY 在mysql表里只能插入一次了,这样对锁的竞争就交给了数据库,处理同一个 KEY 数据库保证了只有一个节点能插入成功,其他节点都会插入失败。

    这样 lock 和 unlock 的思路就很简单了,伪代码:

    def lock :
        exec sql: insert into locked—table (xxx) values (xxx)
        if result == true :
            return true
        else :
            return false
    def unlock :
        exec sql: delete from lockedOrder where order_id='order_id'
    Copier après la connexion

    方法二:

    使用流水号+时间戳做幂等操作,可以看作是一个不会释放的锁。

    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!

    Étiquettes associées:
    source:yisu.com
    Déclaration de ce site Web
    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
    Tutoriels populaires
    Plus>
    Derniers téléchargements
    Plus>
    effets Web
    Code source du site Web
    Matériel du site Web
    Modèle frontal
    À propos de nous Clause de non-responsabilité Sitemap
    Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!