представленный ранее Zookeeper кластер ZAB протокол、Центр конфигурации、Регистрационный центр、Данные и хранение、Управление сессиями и транзакциямии другие связанные знания,Сегодня я подробно познакомлю вас zookeeper Знания, связанные с распределенными блокировками. Я надеюсь, что вы сможете многому от этого научиться! Если это помогло, пожалуйста, нажмите, чтобы посмотреть и отправить в поддержку! ! !
Обычно, когда мы используем блокировки для одной службы, мы можем использовать Java В комплект поставки входят некоторые блокировки для реализации последовательного доступа к ресурсам. Однако с развитием бизнеса сейчас в основном сервисы компании многочисленны и просты. Локор Synchronize Он может решить проблему только одного потока JVM, поэтому для одного сервиса Java Замок не может удовлетворить потребности нашего бизнеса,Чтобы решить проблему доступа нескольких служб к общим ресурсам между службами,Значит есть блокировка раздачи,распределенный Причина блокировки — кластер.
В распределенной системе нескольким процессам или узлам может потребоваться одновременный доступ к общим ресурсам. Чтобы обеспечить согласованность данных и контроль параллелизма, необходимо использовать распределенные блокировки для координации доступа между этими процессами или узлами. Распределенные блокировки позволяют каждому процессу или узлу получать доступ к общим ресурсам в соответствии с определенными правилами, тем самым избегая конфликтов и состояний гонки.
На рисунке ниже показан распространенный случай применения распределенных блокировок.
Существует три основных способа реализации распределенных блокировок (ZooKeeper, Reids, Mysql).
Сегодня мы в основном объясняем использование ZooKeeper для реализации распределенных блокировок. Сценарии применения ZooKeeper в основном включают следующие аспекты:
ZooKeeper реализует распределенные блокировки главным образом потому, что ZooKeeper обеспечивает строгую согласованность данных. Службы блокировки можно разделить на две категории:
Поддерживать эксклюзивный доступ ко всем клиентам, которые пытаются получить текущую блокировку. В конце концов, будет только один ключ, который может успешно получить текущую блокировку. Обычно мы рассматриваем узел (ZNode) в ZooKeeper как блокировку и добиваемся этого. создание временного узла: когда несколько клиентов создают блокировку, только тот клиент, который ее успешно создал, может владеть блокировкой.
Контролируйте время. Все клиенты, которые пытаются получить блокировку, будут выполняться последовательно, но будет иметь порядковый номер (zxid). У нас будет узел, например: /testLock, и под ним будут созданы все временные узлы. родительский узел ZK (/testLock) поддерживает порядковый номер, который является встроенным атрибутом ZK. Он обеспечивает синхронизацию создания дочернего узла, таким образом формируя глобальное время для каждого клиента.
Распределенная блокировка ZooKeeper реализована на основе упорядоченных узлов (последовательных узлов) и механизма наблюдения, предоставляемого ZooKeeper. Конкретные этапы реализации заключаются в следующем:
Поскольку упорядоченные узлы ZooKeeper отсортированы в порядке создания, блокировки можно получить, прослушивая изменения в предыдущем узле. Когда процессу или узлу необходимо получить блокировку, он создает упорядоченный узел в ZooKeeper и получает минимальное значение среди всех упорядоченных узлов. Если текущий узел имеет минимальное значение, это означает, что процесс или узел получил блокировку; в противном случае процессу или узлу необходимо отслеживать изменения в предыдущем узле и ждать, пока предыдущий узел снимет блокировку, прежде чем пытаться получить блокировку. заблокируйте снова.
Распределенная блокировка ZooKeeper имеет следующие преимущества:
Однако распределенные блокировки ZooKeeper также имеют некоторые ограничения:
Основной процесс использования ZooKeeper для реализации распределенных блокировок выглядит следующим образом:
/locks/lock_node/lock_000000001
,Также установите событие наблюдателя,Контролируйте его предыдущий узел.Следует отметить, что реализация распределенных блокировок также требует решения следующих проблем:
поэтому,При реализации распределенной блокировки,Надо подумать о блокировкенадежность
、Эффективность
иотказоустойчивость
,и обрабатывать исключения,Для обеспечения корректности блокировки и стабильности системы.
Кроме того, ZooKeeper также предоставляет механизм распределенной блокировки на основе временных узлов, который называется «блокировкой временного узла». При использовании эфемерных блокировок узлов каждый клиентский процесс создает временный узел в ZooKeeper и регистрирует на нем наблюдателя для мониторинга узла. Когда клиентскому процессу необходимо получить блокировку, он создает эфемерный узел под указанным узлом ZooKeeper. Если порядковый номер узла является наименьшим среди всех текущих узлов, клиентский процесс получает блокировку, в противном случае процесс должен ждать, пока Наблюдатель не услышит, что узел удален;
Преимущество блокировки эфемерного узла заключается в том, что она не вызывает стадного эффекта, и когда процесс теряет блокировку, созданный им эфемерный узел будет автоматически удален, что может эффективно уменьшить объем данных в ZooKeeper. Однако его недостатком является то, что каждому клиенту необходимо создать эфемерный узел, а если клиентов много, количество узлов в ZooKeeper может быстро увеличиться, что приведет к снижению производительности.
Механизм распределенной блокировки ZooKeeper может быть реализован по-разному для удовлетворения различных потребностей. Разработчикам необходимо выбрать метод реализации блокировки, который им подходит, исходя из реальной ситуации, чтобы создать эффективную и надежную распределенную систему. Он включает в себя реализацию распределенных блокировок. Использование ZooKeeper для реализации распределенных блокировок позволяет избежать проблемы одновременного использования нескольких узлов с общими ресурсами и обеспечить согласованность и надежность данных.
В ZooKeeper,Реализация распределенной блокировки основана на временных узлах и механизме Watch.,Две основные операции должны выполняться одновременно.:Замок
иразблокировать замок
。 Существует два конкретных метода реализации:
Независимо от того, какой метод реализации используется, необходимо обработать ситуацию конкуренции блокировок и ситуацию ненормального выхода узла, чтобы обеспечить правильность и надежность блокировки. При реализации распределенных блокировок необходимо учитывать множество факторов, включая степень детализации блокировки, время удержания блокировки, методы конкуренции блокировок и т. д., и ее необходимо корректировать и оптимизировать в соответствии с конкретными сценариями применения.
Поскольку Zookeeper получает ссылки, это трудоемкий процесс.,Здесь вы можете запустить проект,Инициализировать ссылка и инициализируется только один раз. С помощью пружинных функций, Реализация код следующий:
@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);
}
}
Реализация кода
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:
@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();
}
Производительность средняя, а баланс базы данных mysql равен 0 (примечание: перед всеми тестами инвентарь должен быть изменен на 5000).
Далее, давайте сначала улучшим производительность.
На производительность влияет бесконечное вращение в Базовой поставке:
Только представьте: для того, чтобы каждый запрос выполнялся нормально, в конечном итоге должен быть создан узел. Если можно избежать конкуренции, производительность обязательно улучшится. Здесь распределенные блокировки реализованы с помощью узла временной сериализации zk:
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.
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.
Сохраните данные в соответствующем потоке.
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();
}
}
}
Мы знаем, что Redis в основном реализует распределенные блокировки с помощью команды setnx, а 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 для отправки слияний и увольнения?