Серия расширенных распределенных системных архитектур (13): принцип и реализация распределенной блокировки Zookeeper
Серия расширенных распределенных системных архитектур (13): принцип и реализация распределенной блокировки Zookeeper

представленный ранее Zookeeper кластер ZAB протоколЦентр конфигурацииРегистрационный центрДанные и хранениеУправление сессиями и транзакциямии другие связанные знания,Сегодня я подробно познакомлю вас zookeeper Знания, связанные с распределенными блокировками. Я надеюсь, что вы сможете многому от этого научиться! Если это помогло, пожалуйста, нажмите, чтобы посмотреть и отправить в поддержку! ! !

Что такое распределенная блокировка?

Обычно, когда мы используем блокировки для одной службы, мы можем использовать Java В комплект поставки входят некоторые блокировки для реализации последовательного доступа к ресурсам. Однако с развитием бизнеса сейчас в основном сервисы компании многочисленны и просты. Локор Synchronize Он может решить проблему только одного потока JVM, поэтому для одного сервиса Java Замок не может удовлетворить потребности нашего бизнеса,Чтобы решить проблему доступа нескольких служб к общим ресурсам между службами,Значит есть блокировка раздачи,распределенный Причина блокировки — кластер.

В распределенной системе нескольким процессам или узлам может потребоваться одновременный доступ к общим ресурсам. Чтобы обеспечить согласованность данных и контроль параллелизма, необходимо использовать распределенные блокировки для координации доступа между этими процессами или узлами. Распределенные блокировки позволяют каждому процессу или узлу получать доступ к общим ресурсам в соответствии с определенными правилами, тем самым избегая конфликтов и состояний гонки.

На рисунке ниже показан распространенный случай применения распределенных блокировок.

Каковы способы реализации распределенных блокировок?

Существует три основных способа реализации распределенных блокировок (ZooKeeper, Reids, Mysql).

Сегодня мы в основном объясняем использование ZooKeeper для реализации распределенных блокировок. Сценарии применения ZooKeeper в основном включают следующие аспекты:

  • 1. Регистрация и подписка на услугу (общий узел)
  • 2.распределенное уведомление (прослушивание ZNode)
  • 3. Сервисная команда (функция ZNode)
  • 4. Подписка и публикация данных (Watcher)
  • 5.распределенный замок (временный узел)

ZooKeeper реализует распределенные блокировки главным образом потому, что ZooKeeper обеспечивает строгую согласованность данных. Службы блокировки можно разделить на две категории:

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

Контролируйте время. Все клиенты, которые пытаются получить блокировку, будут выполняться последовательно, но будет иметь порядковый номер (zxid). У нас будет узел, например: /testLock, и под ним будут созданы все временные узлы. родительский узел ZK (/testLock) поддерживает порядковый номер, который является встроенным атрибутом ZK. Он обеспечивает синхронизацию создания дочернего узла, таким образом формируя глобальное время для каждого клиента.

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

Распределенная блокировка ZooKeeper реализована на основе упорядоченных узлов (последовательных узлов) и механизма наблюдения, предоставляемого ZooKeeper. Конкретные этапы реализации заключаются в следующем:

  • 1. Каждый процесс или узел создает упорядоченный узел на определенном узле ZooKeeper. Имя узла может представлять собой возрастающее число или другие строки, которые можно сортировать.
  • 2. Процессы или узлы конкурируют за получение блокировок в соответствии с порядком имен узлов. Процесс или узел, получивший блокировку, может получить доступ к общим ресурсам, а другим процессам или узлам придется подождать.
  • 3. Когда процесс или узел снимает блокировку, ZooKeeper уведомит первый процесс или узел в очереди ожидания, чтобы тот продолжил борьбу за получение блокировки.

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

Преимущества и ограничения распределенных блокировок ZooKeeper

Преимущества распределенных блокировок ZooKeeper

Распределенная блокировка ZooKeeper имеет следующие преимущества:

  • Он может обеспечить управление параллелизмом и согласованность данных в распределенной среде.
  • Взаимных блокировок и состояний гонки можно избежать.
  • Может обеспечить более высокую производительность и надежность.
Ограничения распределенных блокировок ZooKeeper

Однако распределенные блокировки ZooKeeper также имеют некоторые ограничения:

  • 1. Из-за необходимости часто создавать, удалять и отслеживать узлы в ZooKeeper, будут возникать высокие затраты на сеть и производительность.
  • 2. Когда блокировка удерживается, другим процессам или узлам необходимо дождаться снятия блокировки предыдущим узлом, прежде чем они смогут продолжить попытки получить блокировку. Таким образом, ситуация с конкуренцией за блокировку будет относительно ровной и ее не будет. ситуация, когда определенный процесс или узел всегда занимал блокировку.

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

Основной процесс использования ZooKeeper для реализации распределенных блокировок выглядит следующим образом:

  • 1. Создайте клиент ZooKeeper и подключитесь к серверу ZooKeeper.
  • 2. Создайте узел блокировки в каталоге ZooKeeper, например /locks/lock_node.
  • 3. Когда вам нужно приобрести замок,Вызовите метод create(), чтобы создать временный упорядоченный узел в каталоге /locks.,Например/locks/lock_node/lock_000000001,Также установите событие наблюдателя,Контролируйте его предыдущий узел.
  • 4. Вызовите метод getChildren(), чтобы получить все дочерние узлы в каталоге /locks и определить, является ли созданный вами узел узлом с наименьшим порядковым номером.
  • 5. Если это узел с наименьшим порядковым номером, это означает, что блокировка получена и код критической секции может быть выполнен; в противном случае вызывается метод существует() для мониторинга узла перед самим собой;
  • 6. Когда предыдущий узел удаляется, запускается событие наблюдателя, и шаги 4 и 5 повторяются до тех пор, пока не будет получена блокировка.
  • 7. При снятии блокировки вызовите метод delete(), чтобы удалить созданный вами узел, и другие процессы или узлы, ожидающие блокировки, смогут получить блокировку.

Следует отметить, что реализация распределенных блокировок также требует решения следующих проблем:

  • 1. Создание и удаление временных узлов должно быть атомарным, иначе несколько узлов будут созданы или удалены одновременно, что приведет к сбою блокировки.
  • 2. Если процесс или узел создает временный узел, но не удаляет его вовремя, это приведет к взаимоблокировке, поскольку другие процессы или узлы никогда не смогут получить блокировку.
  • 3. Если процесс или узел по какой-либо причине не снимает блокировку вовремя после получения блокировки, это заставит другие процессы или узлы ждать, снижая производительность системы.

поэтому,При реализации распределенной блокировки,Надо подумать о блокировкенадежностьЭффективностьиотказоустойчивость,и обрабатывать исключения,Для обеспечения корректности блокировки и стабильности системы.

Кроме того, ZooKeeper также предоставляет механизм распределенной блокировки на основе временных узлов, который называется «блокировкой временного узла». При использовании эфемерных блокировок узлов каждый клиентский процесс создает временный узел в ZooKeeper и регистрирует на нем наблюдателя для мониторинга узла. Когда клиентскому процессу необходимо получить блокировку, он создает эфемерный узел под указанным узлом ZooKeeper. Если порядковый номер узла является наименьшим среди всех текущих узлов, клиентский процесс получает блокировку, в противном случае процесс должен ждать, пока Наблюдатель не услышит, что узел удален;

Преимущество блокировки эфемерного узла заключается в том, что она не вызывает стадного эффекта, и когда процесс теряет блокировку, созданный им эфемерный узел будет автоматически удален, что может эффективно уменьшить объем данных в ZooKeeper. Однако его недостатком является то, что каждому клиенту необходимо создать эфемерный узел, а если клиентов много, количество узлов в ZooKeeper может быстро увеличиться, что приведет к снижению производительности.

Механизм распределенной блокировки ZooKeeper может быть реализован по-разному для удовлетворения различных потребностей. Разработчикам необходимо выбрать метод реализации блокировки, который им подходит, исходя из реальной ситуации, чтобы создать эффективную и надежную распределенную систему. Он включает в себя реализацию распределенных блокировок. Использование ZooKeeper для реализации распределенных блокировок позволяет избежать проблемы одновременного использования нескольких узлов с общими ресурсами и обеспечить согласованность и надежность данных.

В ZooKeeper,Реализация распределенной блокировки основана на временных узлах и механизме Watch.,Две основные операции должны выполняться одновременно.:Замокиразблокировать замок。 Существует два конкретных метода реализации:

  • Один из них — использовать последовательные узлы для реализации конкуренции блокировок.
  • Другой способ — использовать номер версии в пути блокировки для реализации управления блокировкой.

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

Базовая реализация

Идеи реализации
  • Несколько запросов на добавление одного и того же временного узла одновременно, только один может быть успешно добавлен. Добавить успешное приобретение в блокировку
  • Выполнять бизнес-логику
  • После завершения бизнес-процесса удалите узел, чтобы снять блокировку.
Инициализировать ссылку

Поскольку Zookeeper получает ссылки, это трудоемкий процесс.,Здесь вы можете запустить проект,Инициализировать ссылка и инициализируется только один раз. С помощью пружинных функций, Реализация код следующий:

Язык кода:javascript
копировать
@Component
public class zkClient {
    private static final String connectString = "192.168.107.135";
 
    private static final String ROOT_PATH = "/distributed";
 
    private ZooKeeper zooKeeper;
 
    @PostConstruct
    public void init() throws IOException {
        this.zooKeeper = new ZooKeeper(connectString, 30000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("zookeeper Получение ссылки успешно");
            }
        });
        //Создаем корневой узел распределенной блокировки
        try {
            if (this.zooKeeper.exists(ROOT_PATH, false) == null) {
                this.zooKeeper.create(ROOT_PATH, null,
                        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    @PreDestroy
    public void destroy() {
        if (zooKeeper != null) {
            try {
                zooKeeper.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * Инициализировать метод распределенного объекта
     */
    public ZkDistributedLock getZkDistributedLock(String lockname){
        return new ZkDistributedLock(zooKeeper,lockname);
    }
}

Реализация кода

Язык кода:javascript
копировать
public class ZkDistributedLock {
    public static final String ROOT_PATH = "/distribute";
    private String path;
    private ZooKeeper zooKeeper;
 
 
    public ZkDistributedLock(ZooKeeper zooKeeper, String lockname) {
        this.zooKeeper = zooKeeper;
        this.path = ROOT_PATH + "/" + lockname;
    }
 
    public void lock() {
        try {
            zooKeeper.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            Thread.sleep(200);
            lock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
    }
 
    public  void  unlock(){
        try {
            this.zooKeeper.delete(path,0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
 
    }
}

Преобразуйте метод checkAndLock StockService:

Язык кода:javascript
копировать
    @Autowired
    private zkClient client;
    
    public void checkAndLock() {
        // Блокировка, повторите попытку после неудачной попытки получить блокировку.
        ZkDistributedLock lock = this.client.getZkDistributedLock("lock");
        lock.lock();
        // Сначала проверьте, достаточен ли запас
        Stock stock = this.stockMapper.selectById(1L);
        // Дальнейшее сокращение запасов
        if (stock != null && stock.getCount() > 0) {
            stock.setCount(stock.getCount() - 1);
            this.stockMapper.updateById(stock);
        }
        lock.unlock();
    }
Стресс-тест Jmeter

Производительность средняя, ​​а баланс базы данных mysql равен 0 (примечание: перед всеми тестами инвентарь должен быть изменен на 5000).

Базовая реализация Проблемы
  • Средняя производительность (немного лучше, чем у MySQL)
  • Не реентерабельный

Далее, давайте сначала улучшим производительность.

Оптимизация производительности

На производительность влияет бесконечное вращение в Базовой поставке:

Только представьте: для того, чтобы каждый запрос выполнялся нормально, в конечном итоге должен быть создан узел. Если можно избежать конкуренции, производительность обязательно улучшится. Здесь распределенные блокировки реализованы с помощью узла временной сериализации zk:

Внедрить блокировку блокировки
Реализация кода
Язык кода:javascript
копировать
public class ZkDistributedLock {
    public static final String ROOT_PATH = "/distribute";
    private String path;
    private ZooKeeper zooKeeper;
 
 
    public ZkDistributedLock(ZooKeeper zooKeeper, String lockname) {
        this.zooKeeper = zooKeeper;
        try {
            this.path = zooKeeper.create(ROOT_PATH + "/" + lockname + "_",
                    null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    public void lock() {
        String preNode = getpreNode(path);
        //Если у узла нет предыдущего узла, это означает, что этот узел является наименьшим узлом
        if (StringUtils.isEmpty(preNode)) {
            return;
        }
        //Перепроверяем, получена ли блокировка
        try {
            Thread.sleep(20);
            lock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Получить предыдущий узел указанного узла
     *
     * @param path
     * @return
     */
    private String getpreNode(String path) {
        //Получаем серийный номер текущего узла
        Long curSerial = Long.valueOf(StringUtil.substringAfter(path, '_'));
        //Получаем все сериализованные дочерние узлы по корневому пути
        try {
            List<String> nodes = this.zooKeeper.getChildren(ROOT_PATH, false);
            //обработка пустого решения
            if (CollectionUtils.isEmpty(nodes)) {
                return null;
            }
            //Получаем предыдущий узел
            Long flag = 0L;
            String preNode = null;
            for (String node : nodes) {
                //Получить номер сериализации каждого узла
                Long serial = Long.valueOf(StringUtil.substringAfter(path, '_'));
                if (serial < curSerial && serial > flag) {
                    flag = serial;
                    preNode = node;
                }
            }
            return preNode;
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
 
    public void unlock() {
        try {
            this.zooKeeper.delete(path, 0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
 
    }
}

В основном изменен метод строительства и метод блокировки:

И добавил метод getPreNode для получения предыдущего узла.

Результаты испытаний следующие:

Производительность еще слабее.

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

решение:монитор Внедрить блокировку блокировки
монитор Внедрить блокировку блокировки

Для этого алгоритма есть отличная возможность оптимизации: если в настоящее время имеется 1000 узлов, ожидающих блокировки, и если клиент, получивший блокировку, снимает блокировку, эти 1000 клиентов будут разбужены. В результате этого стадного эффекта Zookeeper должен уведомить 1000 клиентов, что заблокирует другие операции. В лучшем случае он должен разбудить только клиента, соответствующего новому наименьшему узлу. Что следует сделать? При настройке прослушивания событий каждый клиент должен настроить прослушивание событий для дочерних узлов непосредственно перед ним. Например, список дочерних узлов: /lock/lock-0000000000, /lock/lock-0000000001, /lock/lock-0000000002, последовательность. номер Клиент с серийным номером 1 прослушивает сообщение об удалении дочернего узла с серийным номером 0, а клиент с серийным номером 2 прослушивает сообщение об удалении дочернего узла с серийным номером 1.

Таким образом, скорректированный алгоритм распределенной блокировки выглядит следующим образом:
  • Клиент подключается к Zookeeper и создает временные и упорядоченные дочерние узлы в /lock. Дочерний узел, соответствующий первому клиенту, — это /lock/lock-0000000000, а второй — /lock/lock-0000000001.
  • Клиент получает список дочерних узлов в /lock и определяет, является ли созданный им дочерний узел дочерним узлом с наименьшим порядковым номером в текущем списке дочерних узлов. Если да, то считается, что он получил блокировку. В противном случае он контролирует. сообщение об удалении дочернего узла непосредственно перед самим собой, повторяйте этот шаг после получения уведомления об изменении дочернего узла, пока не будет получена блокировка;
  • Выполнить бизнес-код;
  • После завершения бизнес-процесса удалите соответствующий дочерний узел, чтобы снять блокировку.
Преобразуйте метод блокировки ZkDistributedLock:
Язык кода:javascript
копировать
    public void lock() {
        String preNode = getpreNode(path);
        //Если у узла нет предыдущего узла, это означает, что этот узел является наименьшим узлом
        if (StringUtils.isEmpty(preNode)) {
            return;
        } else {
            CountDownLatch countDownLatch = new CountDownLatch(1);
            try {
                if (this.zooKeeper.exists(ROOT_PATH + "/" + preNode, watchedEvent -> {
                    countDownLatch.countDown();
                }) == null) {
                    return;
                }
                countDownLatch.await();
                return;
 
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock();
        }
    }

Результаты стресс-теста следующие:

Видно, что производительность значительно улучшилась и уступает только распределенной блокировке Redis.

Оптимизация: реентерабельная блокировка

Локальные переменные потока ThreadLocal введены для обеспечения повторного входа распределенных блокировок zk.

Сохраните данные в соответствующем потоке.

Язык кода:javascript
копировать
public class ZkDistributedLock {
    public static final String ROOT_PATH = "/distribute";
    private String path;
    private ZooKeeper zooKeeper;
    private static final ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>();
 
 
    public ZkDistributedLock(ZooKeeper zooKeeper, String lockname) {
        this.zooKeeper = zooKeeper;
        try {
            this.path = zooKeeper.create(ROOT_PATH + "/" + lockname + "_",
                    null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    public void lock() {
        Integer flag = THREAD_LOCAL.get();
        if (flag != null && flag > 0) {
            THREAD_LOCAL.set(flag + 1);
            return;
        }
        String preNode = getpreNode(path);
        //Если у узла нет предыдущего узла, это означает, что этот узел является наименьшим узлом
        if (StringUtils.isEmpty(preNode)) {
            return;
        } else {
            CountDownLatch countDownLatch = new CountDownLatch(1);
            try {
                if (this.zooKeeper.exists(ROOT_PATH + "/" + preNode, watchedEvent -> {
                    countDownLatch.countDown();
                }) == null) {
                    return;
                }
                countDownLatch.await();
                THREAD_LOCAL.set(1);
                return;
 
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock();
        }
    }
 
    /**
     * Получить предыдущий узел указанного узла
     *
     * @param path
     * @return
     */
    private String getpreNode(String path) {
        //Получаем серийный номер текущего узла
        Long curSerial = Long.valueOf(StringUtil.substringAfter(path, '_'));
        //Получаем все сериализованные дочерние узлы по корневому пути
        try {
            List<String> nodes = this.zooKeeper.getChildren(ROOT_PATH, false);
            //обработка пустого решения
            if (CollectionUtils.isEmpty(nodes)) {
                return null;
            }
            //Получаем предыдущий узел
            Long flag = 0L;
            String preNode = null;
            for (String node : nodes) {
                //Получить номер сериализации каждого узла
                Long serial = Long.valueOf(StringUtil.substringAfter(path, '_'));
                if (serial < curSerial && serial > flag) {
                    flag = serial;
                    preNode = node;
                }
            }
            return preNode;
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
 
    public void unlock() {
        try {
            THREAD_LOCAL.set(THREAD_LOCAL.get() - 1);
            if (THREAD_LOCAL.get() == 0) {
                this.zooKeeper.delete(path, 0);
                THREAD_LOCAL.remove();
            }
 
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
 
    }
}

Краткое описание распределенной блокировки zk

  • Взаимная исключительность: неповторяемость узлов zk и упорядоченность сериализованных узлов.
  • Анти-тупиковая ситуация:
    • Блокировки могут сниматься автоматически: временные узлы.
    • Реентерабельные блокировки: с помощью ThreadLocal.
  • Предотвращение случайного удаления: временные узлы.
  • Блокировка/разблокировка должна быть атомарной.
  • Проблемы с одной точкой: использование Zookeeper может эффективно решать проблемы с одной точкой, как правило, используется ZK.
  • кластер Вопрос: кластер Zookeeper строго последователен,Пока выживет более половины машин в кластере,Вы можете предоставлять услуги внешнему миру.
  • Честный замок: упорядоченный узел.

Сравнение реализации распределенной блокировки между ZooKeeper и Redis

Мы знаем, что Redis в основном реализует распределенные блокировки с помощью команды setnx, а Zookeeper использует временные узлы и механизмы прослушивания событий для реализации распределенных блокировок. Так в чем же ключевые различия между этими двумя методами?

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

Кажется, ZooKeeper лучше Redis,Но API и библиотеки, предоставляемые Redis, богаче.,Это может значительно снизить рабочую нагрузку на разработку. А если это небольшой проект,Redis развернут.,Возможно, нет особой необходимости развертывать еще один набор кластера ZooKeeper для реализации распределенных блокировок.,Каждый делает свой выбор исходя из сценария.

Справочная статья: https://blog.csdn.net/polsnet/article/. details/130444403 https://blog.csdn.net/m0_62436868 /article/details/13046561

Рекомендуемая литература Нажмите на заголовок, чтобы перейти

IPv4 Начни заряжаться! может быть новый IT катастрофа. . .

Крупнейший производитель серверов: выручка резко падает 100 100 миллионов

Я нашел потрясающий инструмент дистанционного управления и мониторинга, и это немного здорово.

соотношение ping Более мощный и потрясающий инструмент командной строки!

ИТ, финансируемые из-за границы, последовательно проигрывают! Citrix и Radware могут покинуть Китай

Новый технический директор: кто будет использовать rebase для отправки слияний и увольнения?

boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.