Несколько реализаций распределенных блокировок~

задняя часть база данных MySQL ZooKeeper

В настоящее время почти многие крупномасштабные веб-сайты и приложения развертываются распределенным образом, и проблема согласованности данных в распределенных сценариях всегда была относительно важной темой. Теория распределенного CAP говорит нам, что «ни одна распределенная система не может одновременно удовлетворять требованиям согласованности, доступности и устойчивости к разделам, а может одновременно удовлетворять только двум из них». необходимо сделать выбор между этими тремя. В подавляющем большинстве сценариев в области Интернета необходимо пожертвовать строгой согласованностью в обмен на высокую доступность системы.Системе часто требуется только обеспечить «конечную согласованность», пока конечное время находится в пределах приемлемого для пользователей диапазона.

Во многих сценариях для обеспечения окончательной непротиворечивости данных требуется множество технических решений, таких как распределенные транзакции, распределенные блокировки и т. д. Иногда нам нужно гарантировать, что метод может выполняться только одним и тем же потоком в одно и то же время. В автономной среде Java фактически предоставляет множество API, связанных с параллельной обработкой, но эти API бессильны в распределенных сценариях. То есть чистый Java Api не может обеспечить возможность распределенной блокировки. Поэтому в настоящее время существует множество решений для реализации распределенных замков.

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

Распределенная блокировка на основе реализации базы данных на основе кеша (redis, memcached, tair) распределенная блокировка на базе Zookeeper

Прежде чем разбирать эти схемы реализации, давайте подумаем, какими должны быть нужные нам распределенные блокировки? (Здесь в качестве примера используется блокировка метода, и то же самое относится и к блокировкам ресурсов.)

Можно гарантировать, что в распределенном кластере приложений один и тот же метод может выполняться только одним потоком на одной машине в одно и то же время.

Блокировка должна быть блокировкой с повторным входом (избегайте взаимоблокировки)

Эта блокировка предпочтительно является блокирующей блокировкой (подумайте, следует ли использовать ее в соответствии с потребностями бизнеса)

Имеются высокодоступные функции захвата блокировки и снятия блокировки.

Лучшая производительность при получении и освобождении блокировок


Распределенная блокировка на основе базы данных

На основе таблицы базы данных

Для реализации распределенных блокировок проще всего создать таблицу блокировок напрямую, а затем сделать это, манипулируя данными в таблице.

Когда мы хотим заблокировать метод или ресурс, мы добавляем запись в таблицу и удаляем эту запись, когда хотим снять блокировку.

Создайте таблицу базы данных следующим образом:

CREATE TABLE `methodLock` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
  `desc` varchar(1024) NOT NULL DEFAULT '备注信息',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

Когда мы хотим заблокировать метод, выполните следующий SQL:

insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)

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

После выполнения метода, если вы хотите снять блокировку, вам необходимо выполнить следующий Sql:

delete from methodLock where method_name ='method_name'

Приведенная выше простая реализация имеет следующие проблемы:

1. Эта блокировка сильно зависит от доступности БД.БД это единая точка.Как только БД зависнет бизнес система будет недоступна.

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

3. Эта блокировка может быть только неблокирующей, потому что операция вставки данных сразу сообщит об ошибке после сбоя вставки. Потоки, которые не получили блокировку, не будут попадать в очередь.Чтобы снова получить блокировку, операция получения блокировки запускается снова.

4. Эта блокировка невозвратная, и тот же поток не может снова получить блокировку, пока блокировка не будет снята. Потому что данные уже существуют в data.

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

  • Является ли база данных одной точкой? Занимайтесь двумя базами данных, и данные синхронизируются в обоих направлениях раньше. Повесив трубку, быстро переключитесь на резервную базу данных.
  • Нет срока годности? Просто выполните запланированное задание и регулярно очищайте данные о тайм-ауте в базе данных.
  • неблокирующий? Задействуйте цикл while, пока вставка не будет успешной, а затем вернитесь к успеху.
  • не реентерабельный? Добавьте поле в таблицу базы данных для записи информации о хосте и информации о потоке машины, которая в данный момент получает блокировку, а затем сначала запросите базу данных при получении блокировки в следующий раз.Если можно найти информацию о хосте и информацию о потоке текущей машины в базе, напрямую Просто назначьте ему блокировку.

Эксклюзивная блокировка базы данных

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

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

public boolean lock(){
    connection.setAutoCommit(false)
    while(true){
        try{
            result = select * from methodLock where method_name=xxx for update;
            if(result==null){
                return true;
            }
        }catch(Exception e){

        }
        sleep(1000);
    }
    return false;
}

добавить после запросаfor update, база данных добавит эксклюзивную блокировку к таблице базы данных во время процесса запроса (еще одна вещь здесь, когда механизм InnoDB заблокирован, он будет использовать только блокировки на уровне строк при извлечении через индексы, в противном случае он будет использовать блокировки на уровне таблицы). . Здесь Если мы хотим использовать блокировки на уровне строки, нам нужно добавить индекс к method_name.Стоит отметить, что этот индекс должен быть создан как уникальный индекс, иначе возникнет проблема, что несколько перегруженных методов не могут быть доступны на одновременно (рекомендуется перегрузить методы, а также добавить тип параметра). Когда монопольная блокировка добавляется к записи, другие потоки не могут добавить монопольную блокировку к строке.

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

public void unlock(){
    connection.commit();
}

пройти черезconnection.commit()операцию снятия блокировки.

Этот метод может эффективно решить вышеупомянутые проблемы невозможности снятия блокировки и блокировки блокировки.

  • блокирующий замок?for updateОператор возвращается сразу после успешного выполнения и блокируется в случае сбоя выполнения до тех пор, пока не завершится успешно.
  • После блокировки служба не работает и не может быть освобождена? Таким образом, база данных сама снимет блокировку после того, как служба выйдет из строя.

Однако он по-прежнему не может напрямую решить проблему единой точки базы данных и повторного входа.

Здесь может быть другая проблема, хотя мыmethod_nameИспользуется уникальный индекс, и показано, что использованиеfor updateиспользовать блокировку на уровне строки. Однако MySql оптимизирует запрос.Даже если в условии используется поле индекса, MySQL определяет, следует ли использовать индекс для извлечения данных, оценивая стоимость различных планов выполнения.Если MySQL считает, что полное сканирование таблицы более эффективным, например, для некоторых очень маленьких таблиц он не будет использовать индексы, и в этом случае InnoDB будет использовать блокировки таблиц вместо блокировок строк. Было бы трагично, если бы это произошло. . .


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

Суммировать

Кратко опишите способ использования базы данных для реализации распределенных блокировок. Эти два способа зависят от таблицы базы данных. Один заключается в том, чтобы определить, есть ли в данный момент блокировка, по наличию записей в таблице, а другой — определить, есть ли есть текущая блокировка через базу данных.Распределенные блокировки реализованы с использованием монопольных блокировок.

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

Это легко понять непосредственно с помощью базы данных.

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

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

Работа с базой данных требует определенных накладных расходов, и необходимо учитывать проблемы с производительностью.

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


Распределенная блокировка на основе кеша

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

В нашей компании есть много зрелых продуктов для кэширования, включая Redis, memcached и Tair.

Здесь мы берем Tair в качестве примера для анализа решения по использованию кеша для реализации распределенной блокировки. В Интернете есть много связанных статей о Redis и memcached, а также есть несколько зрелых фреймворков и алгоритмов, которые можно использовать напрямую.

Реализация распределенных блокировок на основе Tair фактически аналогична Redis, основной метод реализации — использованиеTairManager.putметод достижения.

public boolean trylock(String key) {
    ResultCode code = ldbTairManager.put(NAMESPACE, key, "This is a Lock.", 2, 0);
    if (ResultCode.SUCCESS.equals(code))
        return true;
    else
        return false;
}
public boolean unlock(String key) {
    ldbTairManager.invalid(NAMESPACE, key);
}

Вышеупомянутая реализация также имеет несколько проблем:

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

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

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

Конечно, есть и способы ее решения.

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

** Однако, как долго я должен установить время истечения срока действия? Как установить время аннулирования слишком короткое, блокировка будет автоматически снята до выполнения метода, тогда возникнут проблемы с параллелизмом. Если установленное время слишком велико, другим потокам, которые захватывают блокировку, возможно, придется некоторое время ждать. **Эта проблема также возникает при использовании базы данных для реализации распределенных блокировок.


Суммировать

Кэш можно использовать вместо базы данных для реализации распределенных блокировок, что может обеспечить лучшую производительность.В то же время многие службы кэширования развернуты в кластерах, что позволяет избежать проблем с одной точкой. И многие службы кэширования предоставляют методы, которые можно использовать для реализации распределенных блокировок, например, метод Tair put, метод setnx Redis и т. д. Кроме того, эти службы кэширования также обеспечивают поддержку автоматического удаления данных с истекшим сроком действия, и вы можете напрямую установить тайм-аут для управления снятием блокировок.

Преимущества использования кэша для реализации распределенных блокировок

Он имеет хорошую производительность и более удобен в реализации.

Недостатки использования кэша для реализации распределенных блокировок

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


Распределенная блокировка на основе Zookeeper

Распределенные блокировки, которые могут быть реализованы временными упорядоченными узлами на базе zookeeper.

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

Давайте посмотрим, сможет ли Zookeeper решить вышеупомянутые проблемы.

  • Замок не снимается? Использование Zookeeper может эффективно решить проблему, что блокировка не может быть снята, потому что при создании блокировки клиент создаст временную ноду в ZK.Как только клиент получает блокировку и внезапно зависает (разрыв сеансового соединения), то этот временный узел Узел автоматически удаляется. Затем другие клиенты могут снова получить блокировку.

  • Неблокирующие замки? С помощью Zookeeper можно добиться блокировки блокировки.Клиенты могут создавать последовательные узлы в ZK и привязывать прослушиватели к узлам.Как только узлы изменятся, Zookeeper уведомит клиента, и клиент сможет проверить, является ли созданный им узел текущим.Узел с наименьшим серийный номер среди всех узлов, если он есть, то он получит блокировку и выполнит бизнес-логику.

  • Не реентерабельный? Использование Zookeeper также может эффективно решить проблему отсутствия повторного входа. Когда клиент создает узел, он напрямую записывает информацию о хосте и потоке текущего клиента в узел. В следующий раз, когда он захочет получить блокировку, он будет самый маленький узел в настоящее время Сравните данные в . Если информация совпадает с вашей собственной, то вы можете напрямую получить блокировку, если нет, создать временный узел последовательности для участия в очереди.

  • Одноточечный вопрос? Использование Zookeeper позволяет эффективно решать одноточечные задачи.ZK развертывается в кластере.Пока выживает более половины машин в кластере, он может предоставлять внешние сервисы.

Вы можете напрямую использовать стороннюю библиотеку zookeeper.CuratorКлиент, который инкапсулирует службу блокировки с повторным входом.

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    try {
        return interProcessMutex.acquire(timeout, unit);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return true;
}
public boolean unlock() {
    try {
        interProcessMutex.release();
    } catch (Throwable e) {
        log.error(e.getMessage(), e);
    } finally {
        executorService.schedule(new Cleaner(client, path), delayTimeForClean, TimeUnit.MILLISECONDS);
    }
    return true;
}

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

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

На самом деле, использование Zookeeper также может привести к проблемам параллелизма, но это не является распространенным явлением. Учитывая такую ​​ситуацию, из-за сетевого джиттера сеансовое соединение клиента с кластером ZK разрывается, тогда ZK думает, что клиент зависает, и удалит временную ноду, в это время другие клиенты могут получить распределенные блокировки. Могут возникнуть проблемы с параллелизмом. Эта проблема не является распространенной, поскольку zk имеет механизм повторных попыток. Как только кластер zk не может обнаружить пульс клиента, он повторяет попытку.Клиент Curator поддерживает несколько стратегий повторных попыток. Временный узел будет удален, если он не работает после нескольких попыток. (Поэтому также более важно выбрать подходящую стратегию повторных попыток и найти баланс между степенью детализации блокировки и параллелизмом.)


Суммировать

Преимущества использования Zookeeper для реализации распределенных блокировок

Эффективно решать одноточечные проблемы, проблемы без повторного входа, неблокирующие проблемы и проблемы, которые не могут быть сняты. Его проще реализовать.

Недостатки использования Zookeeper для реализации распределенных блокировок

Производительность не так хороша, как при использовании кеша для реализации распределенных блокировок. Требуется некоторое понимание принципов ЗК.


Сравнение трех схем

Ни один из вышеперечисленных способов не может быть идеальным. Как и CAP, он не может быть одновременно удовлетворен с точки зрения сложности, надежности, производительности и т. д. Таким образом, это лучший способ выбрать наиболее подходящий вариант в соответствии с различными сценариями применения.

С точки зрения простоты понимания (от низкого к высокому)

База данных > Кэш > Zookeeper

С точки зрения сложности реализации (от низкой к высокой)

Zookeeper >= кеш > база данных

С точки зрения производительности (от высокого к низкому)

Кэш> Zookeeper> = База данных

С точки зрения надежности (от высокого к низкому)

Zookeeper > Кэш > База данных