Серия расширенных распределенных системных архитектур (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
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose