Home > Database > Redis > body text

How to implement Redis distributed lock and what are its application scenarios

PHPz
Release: 2023-05-30 17:55:51
forward
1499 people have browsed it

    Introduction

    Lock is a very common tool in the development process. You must be familiar with it, pessimistic lock, optimistic lock, exclusive lock, fair lock, Unfair locks, etc., there are many concepts. If you don’t understand locks in Java, you can refer to this article: Java “locks” that must be mentioned. This article is very comprehensive, but for beginners, Knowing the concepts of these locks, due to lack of practical work experience, you may not understand the actual usage scenarios of unlocking. In Java, thread safety can be achieved through the three keywords Volatile, Synchronized, and ReentrantLock. This part of knowledge will be included in the first round of basic interviews. You will definitely ask (you must be proficient in it).

    In a distributed system, Java lock technologies cannot lock the code on two machines at the same time, so it must be implemented through distributed locks. Proficient use of distributed locks is also a skill that must be mastered by big factory developers.

    1. Interviewer:

    Have you ever encountered a scenario where you need to use distributed locks?

    Problem analysis: This question is mainly used as an introduction. You must first understand in what scenarios distributed locks need to be used and what problems distributed locks need to solve. Under this premise, it will help you better understand distributed locks. implementation principle.

    The scenarios for using distributed locks generally need to meet the following scenarios:

    • The system is a distributed system, and Java locks can no longer be locked.

    • Manipulate shared resources, such as the only user data in the library.

    • Synchronous access, that is, multiple processes operate shared resources at the same time.

    Answer: Let me tell you an example of a scenario where I use distributed locks in a project:

    Consumption points are available in many systems, including credit cards, e-commerce websites, etc. Points can be exchanged for gifts, etc. The operation of "consuming points" here is a typical scenario that requires the use of locks.

    Event A:

    Taking points redemption for gifts as an example, the complete point consumption process is simply divided into 3 steps:

    A1: The user selects the product, initiates the exchange and submits the order .

    A2: The system reads the user's remaining points: determines whether the user's current points are sufficient.

    A3: User points will be deducted.

    Event B:

    The system distributes points to users in three simple steps:

    B1: Calculate the points the user deserves for the day

    B2: Read The user’s original points

    B3: Add the points deserved this time to the original points

    Then the problem arises, if the user’s consumption points and the user’s accumulated points occur at the same time (user points are operated at the same time) What will happen?

    Assumption: While the user is consuming points, the offline task happens to be calculating points and issuing points to the user (for example, based on the user's consumption amount on the day). These two things are done at the same time. The following logic is a bit convoluted, so be patient and understand it.

    User U has 1,000 points (the data recording user points can be understood as shared resources), and this redemption will consume 999 points.

    Unlocked situation: When the event A program reaches the second step to read the points, the result read by the A:2 operation is 1000 points. It is judged that the remaining points are enough for this redemption, and then the step A is executed. Step 3 A: Points will be deducted for 3 operations (1000 - 999 = 1). The normal result should still be 1 point for the user. But event B is also executing at this time. This time, 100 points will be issued to user U. Two threads will do it at the same time (synchronous access). Without locking, there will be the following possibility: A:2 -> B :2 -> A:3 -> B:3, before A:3 is completed (points deducted, 1000 - 999), user U's total points are read by the thread of event B, and finally user U's total points It became 1100 points, and I redeemed a gift of 999 points in vain, which obviously did not meet the expected results.

    Some people say how it is possible to operate user points at the same time so coincidentally, and the CPU is so fast. As long as there are enough users and the concurrency is large enough, Murphy's Law will take effect sooner or later. It is only a matter of time before the above bug appears, and it may be hacked. The industry is stuck with this bug and is crazy about it. At this time, as a developer, if you want to solve this hidden danger, you must understand the use of unlocking.

    (Writing code is a rigorous matter!)

    Java itself provides two built-in lock implementations, one is synchronized implemented by the JVM and the Lock provided by the JDK. And many atomic operation classes are thread-safe. When your application is a stand-alone or single-process application, you can use these two locks to implement locks.

    However, most of the current systems of Internet companies are distributed. At this time, Java's own synchronized or Lock can no longer meet the locking requirements in a distributed environment, because the code will be deployed on multiple machines. In order to solve this problem, distributed locks came into being. The characteristics of distributed locks are multi-process, and memory cannot be shared on multiple physical machines. The common solution is based on the interference of the memory layer. The implementation solution is distributed locks based on Redis. or ZooKeeper distributed lock.

    (I can’t analyze it in more detail, the interviewer is not satisfied anymore?)

    2. Interviewer:

    Redis distributed lock implementation method

    There are currently two main implementation methods to solve the distributed lock problem: one is based on the Redis Cluster mode, and the other is... 2. Based on Zookeeper cluster mode.

    Give priority to mastering these two, and you will basically have no problem handling interviews.

    answer:

    1. Распределенная блокировка на основе Redis

    Метод 1: Используйте команду setnx для блокировки

    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);
        }
    }
    Copy after login

    Пояснение кода:

    setnx команда, Это означает, что установлен, если он не существует. Если lockKey не существует, сохраните ключ в Redis. Если результат возвращает 1 после успешного сохранения, это означает, что настройка прошла успешно. Если он не равен 1, это означает сбой. Другие потоки уже установить его.

    expire(), установите время истечения срока действия, чтобы предотвратить взаимоблокировку. Предположим, что если блокировка не удаляется после установки, то она эквивалентна всегда существующей, что приводит к взаимоблокировке.

    (Здесь хотелось бы подчеркнуть интервьюеру одно «но»)

    Подумайте, какие недостатки есть в моем вышеописанном методе? Продолжайте объяснять интервьюеру...

    Блокировка состоит из двух шагов. Первый шаг — jedis.setnx, второй — jedis.expire для установки срока действия. setnx и expire не являются атомарная операция. Если программа выполняется после того, как после первого шага произошло исключение. На втором шаге jedis.expire(lockKey, expireTime) не был выполнен, что означает, что у блокировки нет срока действия, и может возникнуть взаимоблокировка. . Как улучшить эту проблему?

    Улучшение:

    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;//加锁失败
        }
    }
    Copy after login

    Объяснение кода:

    Объедините блокировку и установку срока действия в одну, одну строку кода, атомарную операцию.

    (Интервьюер был очень доволен, прежде чем задавать дальнейшие вопросы)

    3.Интервьюер: А как насчет операции разблокировки?

    Ответ:

    Снятие блокировки означает удаление ключа

    Используйте команду del для разблокировки

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

    Пояснение кода: используйте requestId, чтобы определить, Блокировка и разблокировка одинаковы Два шага client и jedis.del(lockKey) не являются атомарными операциями. Теоретически после выполнения первой операции проверки будет видно, что срок действия блокировки действительно истек и был получен другими потоками. Это время выполнения операции jedis.del(lockKey) эквивалентно снятию чужой блокировки, что неразумно. Конечно, это очень крайняя ситуация. Если на первом и втором шагах метода разблокировки нет других бизнес-операций, размещение приведенного выше кода в сети может не вызвать проблем. Первая причина – параллелизм бизнес-процессов. Если это так, невысокий, этот изъян вообще не будет выявляться, так что проблема не большая.

    Но написание кода — это кропотливая работа, и чтобы быть идеальным, вы должны быть совершенными. Предлагаются улучшения для решения проблем в приведенном выше коде.

    Улучшение кода:

    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;
        }
    }
    Copy after login

    Объяснение кода:

    Используйте метод eval клиента jedis и всего одну строку сценария, чтобы решить проблему атомарности, связанную с первым методом.

    3.Интервьюер:

    Принцип реализации распределенной блокировки на основе ZooKeeper

    Ответ: Это все еще пример потребления и накопления очков: событие A и событие B должны быть выполняться одновременно Операция изменения точек выполняется на двух машинах одновременно. Правильная бизнес-логика заключается в том, чтобы позволить одной машине выполнить ее сначала, а затем другой машине. Либо событие A выполняется первым, либо событие B выполняется одновременно. выполняется первым, чтобы гарантировать, что не произойдет A. :2 -> B:2 -> A:3 -> B:3 Чем больше очков вы тратите, тем больше очков вы тратите (думая, что как только этот вид если ошибка выйдет в сеть, босс рассердится, я могу заплакать).

    что делать? Используйте распределенные блокировки Zookeeper.

    После того, как машина получает запрос, она сначала получает распределенную блокировку на Zookeeper (zk создаст znode) и выполняет операцию; затем другая машина также пытается создать znode, но обнаруживает, что не может его создать ., поскольку он был создан кем-то другим, вам остается только подождать, пока первая машина завершит выполнение, прежде чем вы сможете получить блокировку.

    Используя функцию последовательных узлов ZooKeeper, если мы создадим 3 узла в каталоге /lock/, кластер ZK создаст узлы в том порядке, в котором они были созданы.Узлы разделены на /lock/ 0000000001, /lock/0000000002 , /lock/0000000003, последняя цифра последовательно увеличивается, а имя узла завершается знаком zk.

    ZK также имеет узел, называемый временным узлом. Временный узел создается клиентом. Когда клиент отключается от кластера ZK, узел автоматически удаляется. EPHEMERAL_SEQUENTIAL — это временный узел последовательности.

    Основная логика распределенных блокировок заключается в использовании наличия или отсутствия узлов в ZK в качестве статуса блокировки для реализации распределенных блокировок

    • Клиент вызывает функцию create() метод Создайте временный узел последовательности с именем «/dlm-locks/lockname/lock-».

    • Клиент вызывает метод getChildren("lockname") для получения всех созданных дочерних узлов.

    • После того, как клиент получит пути всех дочерних узлов, если он обнаружит, что узел, созданный им на шаге 1, имеет наименьший порядковый номер среди всех узлов, он проверит, является ли порядковый номер он создал ранги первым.Во-первых, если он первый, то считается, что этот клиент получил блокировку, и ни один другой клиент не получил блокировку до него.

    • Если созданный узел не является самым маленьким из всех узлов, то необходимо отслеживать самый большой узел с меньшим порядковым номером, чем созданный им узел, а затем перейти в состояние ожидания . После изменения отслеживаемого дочернего узла получите дочерний узел и определите, следует ли получать блокировку.

    Хотя процесс снятия блокировки относительно прост и фактически представляет собой удаление созданного дочернего узла, вам все равно необходимо учитывать нештатные ситуации, такие как невозможность удаления узла.

    Дополнительное дополнение

    Распределенные блокировки также могут решить проблему с базой данных

    方法一:

    利用 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'
    Copy after login

    方法二:

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

    The above is the detailed content of How to implement Redis distributed lock and what are its application scenarios. For more information, please follow other related articles on the PHP Chinese website!

    Related labels:
    source:yisu.com
    Statement of this Website
    The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
    Latest Articles by Author
    Popular Tutorials
    More>
    Latest Downloads
    More>
    Web Effects
    Website Source Code
    Website Materials
    Front End Template
    About us Disclaimer Sitemap
    php.cn:Public welfare online PHP training,Help PHP learners grow quickly!