Сводка по распределенной блокировке

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

База данных:

基于数据库表做乐观锁,用于分布式锁。(version) 

基于数据库表做悲观锁(InnoDB,for update)

基于数据库表数据记录做唯一约束(表中记录方法名称)

На основе кеша:

使用redis的setnx()用于分布式锁。(setNx,直接设置值为当前时间+超时时间,保持操作原子性)

使用memcached的add()方法,用于分布式锁。

使用Tair的put()方法,用于分布式锁。

На основе Zookeeper:

每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 
判断是否获取锁只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。

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

Уникальные ограничения на основе записи данных таблицы базы данных (имя метода записи таблицы)

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

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

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

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

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

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

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

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

3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。 Потоки, которые не получили блокировку, не будут попадать в очередь.Чтобы снова получить блокировку, операция получения блокировки запускается снова.

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

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

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

Выполнять пессимистическую блокировку на основе таблиц базы данных (механизм InnoDB, для оператора обновления)

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

Добавить для обновления после оператора запроса, база данных добавит монопольную блокировку к таблице базы данных во время процесса запроса.(这里再多提一句,InnoDB引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。这里我们希望使用行级锁,就要给method_name添加索引,值得注意的是,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上).

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

public void lock(){
connection.setAutoCommit(false)
int count = 0;
while(count < 4){
try{
select * from lock where lock_name=xxx for update;
if(结果不为空){
//代表获取到锁
return;
}
}catch(Exception e){

}
//为空或者抛异常的话都表示没有获取到锁
sleep(1000);
count++;
}
throw new LockException();
}

Снимите блокировку с помощью операции connection.commit().

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

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

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

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

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

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

  1. Сначала объясните значение оптимистического замка:

     大多数是基于数据版本(VERSION)的记录机制实现的。何谓数据版本号?即为数据增加一个版本标识,
     在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个“VERSION”字段来实现读取出数据时
     ,将此版本号一同读出,之后更新时,对此版本号加1。
     
     在更新过程中,会对版本号进行比较,如果是一致的,没有发生改变,则会成功执行本次操作;
     如果版本号不一致,则会更新失败。
    
  2. Имея некоторое представление о значении оптимистической блокировки в сочетании с конкретными примерами, давайте сделаем вывод, как с этим бороться:

  • Предположим, у нас есть таблица ресурсов, как показано на следующем рисунке: T_RESOURCE, которая имеет 6 идентификаторов полей, RESOOURCE, STATE, ADD_TIME, UPDATE_TIME, VERSION, которые представляют первичный ключ таблицы, ресурс, статус выделения (1 нераспределенный 2 выделенный), время создания ресурса, время обновления ресурса, номер версии данных ресурса.

  • Предположим, что теперь мы выделяем данные с ID=5780, тогда в случае нераспределенных сценариев мы обычно сначала запрашиваем данные с STATE=1 (нераспределенные), а затем выбираем из них часть данных с помощью следующего оператора: Если обновление может пройти успешно, значит, ресурс занят ОБНОВЛЕНИЕ T_RESOURCE УСТАНОВИТЕ СОСТОЯНИЕ = 2, ГДЕ СОСТОЯНИЕ = 1 И ID = 5780.

  • В распределенном сценарии, поскольку операция UPDATE базы данных является атомарной, приведенное выше утверждение теоретически не представляет проблемы, но если это утверждение находится в типичной ситуации «ABA», мы не можем его воспринять. Некоторые люди могут спросить, в чем проблема «ABA»? Можно поискать в интернете.Тут я говорю просто,если в процессе вашего первого SELECT и второго UPDATE,так как две операции неатомарные,если в этом процессе есть поток,то ресурсы будут заняты в первую очередь.(STATE =2), а затем освободить ресурс (СОСТОЯНИЕ=1).На самом деле, когда вы выполняете операцию UPDATE, вы не можете знать, что ресурс изменился. Может быть, вы скажете, что в упомянутом вами сценарии все должно быть в порядке, но в реальных условиях, таких как депозиты или отчисления на банковский счет, эта ситуация еще более ужасна.

  • Так как же решить указанную выше проблему, если мы используем оптимистическую блокировку?

      A. 先执行SELECT操作查询当前数据的数据版本号,比如当前数据版本号是26:
      SELECT ID, RESOURCE, STATE,VERSION FROM T_RESOURCE WHERE STATE=1 AND ID=5780;
      B. 执行更新操作:
      UPDATE T_RESOURE SET STATE=2, VERSION=27, UPDATE_TIME=NOW() WHERE RESOURCE=XXXXXX AND 
      STATE=1 AND VERSION=26
      C. 如果上述UPDATE语句真正更新影响到了一行数据,那就说明占位成功。如果没有更新影响到一行数据
      ,则说明这个资源已经被别人占位了。
    
  1. Некоторые недостатки оптимистической блокировки на основе таблиц базы данных:

(1) Таким образом, исходная операция ОБНОВЛЕНИЯ должна быть преобразована в 2 операции: ВЫБЕРИТЕ номер версии один раз и ОБНОВИТЕ один раз. Увеличено количество операций с базой данных.

(2) Если для обеспечения согласованности данных в бизнес-процессе в бизнес-сценарии необходимо использовать несколько ресурсов, то при использовании всех оптимистичных блокировок, основанных на таблицах ресурсов базы данных, каждый ресурс должен иметь таблицу ресурсов.Это определенно неудовлетворительно. в реальных сценариях использования. И все они основаны на операциях с базой данных, а в условиях высокого параллелизма накладные расходы на соединения с базой данных должны быть невыносимыми.

(3) Механизм оптимистической блокировки часто основан на логике хранения данных в системе, поэтому он может привести к обновлению грязных данных в базе данных. На этапе проектирования системы мы должны полностью учитывать возможность таких ситуаций и вносить соответствующие коррективы, например, реализовывать оптимистическую стратегию блокировки в хранимой процедуре базы данных, а во внешний мир открывать только метод обновления данных на основе этой хранимой процедуры. , а не база данных. Таблица напрямую выставлена ​​на всеобщее обозрение.

После разговора о реализации и недостатках оптимистической блокировки вы чувствуете, что боитесь использовать оптимистическую блокировку? ? ? Конечно, нет.В моем собственном бизнес-сценарии в начале статьи оба сценария 1 и 2 используют оптимистическую блокировку на основе таблиц ресурсов базы данных, что хорошо решило онлайн-проблему. Так что каждый должен выбирать техническое решение в соответствии с конкретным бизнес-сценарием, разве это не хорошее решение, просто найти техническое решение, достаточно сложное и достаточно модное для решения бизнес-задач? ! Например, если в моем сценарии 1 я использую zookeeper в качестве блокировки, я могу это сделать, но действительно ли это необходимо? ? ? Ответ - не надо! ! !

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

数据库实现分布式锁的优点
    直接借助数据库,容易理解。
数据库实现分布式锁的缺点
    会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂。
    操作数据库需要一定的开销,性能问题需要考虑。
    使用数据库的行级锁并不一定靠谱,尤其是当我们的锁表并不大的时候。

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

Используйте setnx() Redis для распределенных блокировок. (атомарность)

SETNX устанавливает значение ключа в значение тогда и только тогда, когда ключ не существует. Если данный ключ уже существует, SETNX ничего не делает.

• 返回1,说明该进程获得锁,SETNX将键 lock.id 的值设置为锁的超时时间,当前时间 +加上锁的有效时间。
• 返回0,说明其他进程已经获得了锁,进程不能进入临界区。进程可以在一个循环中不断地尝试 SETNX 操作,以获得锁。

тупиковая проблема

SETNX реализует распределенные блокировки, поэтому могут возникать взаимоблокировки. По сравнению с блокировкой в ​​автономном режиме, в распределенной среде необходимо не только обеспечить видимость процесса, но также необходимо учитывать сетевую проблему между процессом и блокировкой. После того, как поток получает блокировку, он отключается от Redis, блокировка не снимается вовремя, и другие потоки, конкурирующие за блокировку, зависают, что приводит к взаимоблокировке. Так что в этом случае необходимо установить тайм-аут для полученной блокировки, то есть setExpire, и блокировка автоматически снимается по истечении тайм-аута.

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

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

Общая идея такова:

每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的临时有
序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 
当释放锁的时候,只需将这个临时节点删除即可。同时,排队的节点需要监听排在自己之前的节点,这样能
在节点释放时候接收到回调通知,让其获得锁。zk的session由客户端管理,其可以避免服务宕机导致的锁无
法释放,而产生的死锁问题,不需要关注锁超时。

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

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

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

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

Распределенная блокировка, реализованная с помощью ZK, похоже, полностью соответствует всем нашим ожиданиям от распределенной блокировки в начале этой статьи. Однако это не так: распределенная блокировка, реализованная в Zookeeper, на самом деле имеет недостаток.那就是性能上可能并没有缓存服务那么高. Потому что каждый раз в процессе создания и снятия блокировки мгновенные узлы должны динамически создаваться и уничтожаться для реализации функции блокировки. Создание и удаление узлов в ZK возможно только через сервер-лидер, а затем данные синхронизируются на все машины-последователи.

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

Краткое изложение схем на основе ZK

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

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

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

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

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

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

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

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

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

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

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

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

От угла надежности (от высокого к низкому)

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