Обычно существует три способа реализации распределенных замков: 1. Оптимистическая блокировка базы данных; 2. Распределенная блокировка на основе Redis; 3. Распределенная блокировка на основе Zookeeper. Этот блог представит второй метод, который должен реализовать распределенную блокировку на основе Redis. Хотя в Интернете есть различные блоги, которые вводят реализацию распределенных замков Redis, их реализация имеет различные проблемы. Чтобы избежать вводящих в заблуждение детей, этот блог подробно расскажет, как правильно реализовать распределенные блокировки Redis.
надежность
Во -первых, чтобы убедиться, что распределенные замки были доступны, мы должны, по крайней мере, убедиться, что реализация блокировки соответствовала следующим четырем условиям одновременно:
Взаимное исключение. В любое время только один клиент может удерживать замок.
Никакого тупика не происходит. Даже если клиент вылетает во время периода удержания блокировки без активного разблокировки его, он может убедиться, что другие клиенты могут добавлять замки.
Отказоустойчивый. Пока большинство узлов Redis работают нормально, клиент может заблокировать и разблокировать.
Человек, который связал звонок, должен быть развязан. Блокировка и разблокировка должны быть одним и тем же клиентом, и клиент не может развязать замки, добавленные другими.
Реализация кода
Зависимости компонентов
Во -первых, нам нужно представить компоненты Jedis с открытым исходным кодом через Maven и добавить следующий код в файл pom.xml:
<depervice> <groupid> redis.clients </GroupId> <ArtifactId> jedis </artifactid> <serse> 2.9.0 </version> </depervice>
Код блокировки
Правильная осанка
Разговор дешево, покажи мне код. Сначала покажите код, а затем объясните, почему это реализовано:
Общедоступный класс Redistool {private Static Final String Lock_Success = "OK"; Private Static Final String SET_IF_NOT_EXIST = "nx"; Private Static Final String SET_WITH_EXPIRE_TIM успешно получен*/public static boolean trygetDistributedlock (jedis jedis, string lockkey, string requestid, int expiretime) {string result = jedis.set (lockkey, requestId, set_if_not_exist, set_with_expire_time, recireTime); if (lock_success. quequals (result) {return))Как вы можете видеть, мы просто добавляем одну строку кода: jedis.set (StringKey, StringValue, StringNxxx, StringExpx, Inttime). Этот метод set () имеет в общей сложности пять формальных параметров:
Первый - это ключ, мы используем ключ в качестве блокировки, потому что клавиша уникальна.
Второй - значение. То, что мы передаем, это запрос. Многие детские туфли могут не понять. Разве этого не достаточно, чтобы иметь ключ в качестве замка? Зачем нам все еще нужно использовать ценность? Причина в том, что когда мы говорили о надежности выше, распределенный замок должен соответствовать четвертому условию, чтобы расслабиться на звонок. Присвоив значение requestId, мы узнаем, какой запрос был добавлен в блокировку, и будет основана на разблокировке. requestId может быть сгенерирован с использованием метода uuid.randomuuid (). ToString ().
Третий - NXXX. Мы заполняем этот параметр nx, что означает setifnotexist, то есть, когда ключ не существует, мы выполняем операцию установки; Если ключ уже существует, операция не выполнена;
Четвертый - Expx. Мы передаем этот параметр px, что означает, что мы хотим добавить истекший настройка к этому ключу. Конкретное время определяется пятым параметром.
Пятое - время, которое повторяет четвертый параметр и представляет время истечения срока действия ключа.
В целом, выполнение метода set () выше приведет только к двум результатам: 1. В настоящее время не существует блокировки (ключ не существует), затем выполняется операция блокировки, и блокировка установлена на действительную для блокировки, а значение представляет блокированный клиент. 2. Здесь уже существует замок, и операция не выполнена.
Если вы осторожны, вы обнаружите, что наш код блокировки соответствует трем условиям, описанным в нашей надежности. Прежде всего, set () добавляет параметры NX, что может убедиться, что если ключ уже существует, функция не будет называться успешно, то есть только один клиент может удерживать блокировку, чтобы удовлетворить Mutex. Во -вторых, поскольку мы установили время истечения для блокировки, даже если держатель блокировки сбой в последующем сбое и не разблокируется, замок автоматически будет разблокирован, поскольку он достиг времена истечения (то есть ключ удален), и не будет тупика. Наконец, поскольку мы присваиваем значение requestId, которая представляет собой заблокированный идентификатор запроса клиента, тогда клиент может проверить, является ли это одним и тем же клиентом при разблокировке. Поскольку мы рассматриваем только сценарии развертывания Redis, мы не будем учитывать терпимость к неисправности на данный момент.
Пример ошибки 1
Общим примером ошибки является использование комбинации jedis.setnx () и jedis.expire () для достижения блокировки. Код заключается в следующем:
public static void offegetlock1 (jedis jedis, string lockkey, string requestid, int expiretime) {long result = jedis.setnx (lockkey, requestid); if (result == 1) {// Если программа внезапно разбивается здесь, время истечения не может быть установлено, и в тупик будет происходить джедис.Функция метода setnx () является setifnotexist, и метод истечения срока действия () состоит в том, чтобы добавить время истечения к блокировке. На первый взгляд, он, кажется, такой же, как и предыдущий метод Set (). Однако, поскольку это две команды Redis, они не атомны. Если программа внезапно вылетает после выполнения setNx (), блокировка не устанавливает время истечения срока действия. Тогда произойдет тупик. Причина, по которой некоторые люди реализуют это в Интернете, заключается в том, что нижняя версия Jedis не поддерживает метод мультипараметрического набора ().
Пример ошибки 2
Этот пример ошибки труднее найти проблемы, и реализация также сложнее. Идея реализации: используйте команду jedis.setnx () для реализации блокировки, где ключ - это блокировка, а значение - время истечения срока действия блокировки. Процесс выполнения: 1. Попробуйте добавить блокировку через метод setnx (). Если текущая блокировка не существует, блокировка будет успешно возвращен. 2. Если замок уже существует, приобретите время истечения срока действия блокировки и сравните его с текущим временем. Если срок действия блокировки истек, установите новое время истечения и верните, что блокировка успешно добавлена. Код заключается в следующем:
Public Static Boolean offegetLock2 (jedis jedis, String Lockkey, int expireTime) {long expires = System.currentTimeMillis () + ExpireTime; String expiresstr = string.valueof (истекает); // Если текущая блокировка не существует, блокировка успешно возвращается, если (jedis.setnx (lockkey, expresstrtrtrtstr) == expresstrtstr). Замок существует, время истечения срока действия блокировки получается String CurrentValuestR = jedis.get (lockkey); if (currentValuestR! = null && long.parselong (currentValuest) Expiresstr); if (oldvaluestest! = null && oldvaluest.equals (currentvaluest)) {// Рассматривая случай многопоточного параллелизма, только если значение настройки одного потока такое же, как и текущее значение, оно имеет право заблокировать возвращение;Так в чем же проблема с этим кодом? 1. Поскольку клиент генерирует само время истечения, необходимо заставить время каждого клиента синхронизировать под распределенным подходом. 2. Когда срок действия блокировки истекает, если несколько клиентов выполняют метод jedis.getset () одновременно, хотя только один клиент может заблокировать в конце, время истечения срока действия блокировки этого клиента может быть перезаписано другими клиентами. 3. У блокировки нет логотипа владельца, то есть любой клиент может разблокировать его.
Разблокировать код
Правильная осанка
Давайте сначала покажем код, а затем медленно объясним, почему это реализовано:
public class RedisTool {private static final long RELEASE_SUCCESS = 1L;/** * Release the distributed lock* @param jedis Redis client* @param lockKey lock* @param requestId Request ID* @return Whether the release was successful*/public static Boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {String script = "if redis.call('get', Ключи [1]) == argv [1] затем вернуть redis.call ('del', keys [1]) else return 0 end "; object result = jedis.eval (script, collections.singletonlist (lockkey), collections.singletonlist (requestId); if (release_success.equals (result)) {return true;} return false;}}}}}}}}}}}}}}}}}}Как видите, нам нужно только две строки кода, чтобы разблокировать его! В первой строке кода мы написали простой код скрипта LUA. В последний раз, когда мы видели этот язык программирования в «Хакер и художник», но мы не ожидали, что он будет использоваться на этот раз. Во второй строке кода мы передаем код LUA методу jedis.eval () и назначаем клавиши параметров [1] Lockkey и Argv [1] для запроса. Метод eval () должен передать код LUA на сервер Redis для выполнения.
Итак, какова функция этого кода LUA? На самом деле, это очень просто. Во -первых, получите значение, соответствующее блокировке, проверьте, равно ли оно requestId, и если оно равно, удалите блокировку (разблокировать). Так зачем использовать язык LUA для его реализации? Потому что необходимо убедиться, что вышеупомянутые операции являются атомными. Для каких проблем принесет атомность, вы можете прочитать [разблокировать код - пример ошибки 2]. Так почему я могу выполнить метод eval () обеспечить атомичность, которая происходит из характеристик Redis. Вот частичное объяснение команды Eval на официальном веб -сайте:
Проще говоря, когда команда Eval выполняет код LUA, код LUA будет выполнен в качестве команды, а Redis не выполнит другие команды, пока команда Eval не будет выполнена.
Пример ошибки 1
Наиболее распространенным кодом разблокировки является непосредственное использование метода jedis.del () для удаления блокировки. Этот метод разблокировки непосредственно без сначала оценки владельца блокировки заставит любого клиента разблокировать в любое время, даже если замок не является его.
public static void fronwreleaseLock1 (jedis jedis, string lockkey) {jedis.del (lockkey); }Пример ошибки 2
На первый взгляд, этот код разблокировки в порядке. Я даже чуть не реализовал это так раньше, что похоже на правильную осанку. Единственное отличие состоит в том, что он разделен на две команды для выполнения. Код заключается в следующем:
public static void ruperReleaseLock2 (jedis jedis, string lockkey, string requestid) {// определить, являются ли блокировка и разблокировка одним и тем же клиентом, если (requestId.equals (jedis.get (lockkey))) {// Если этот замок внезапно не от этого клиента, он будет неправильно разобрался, чтобы разблокировать jedis.del (lockkey);Что касается комментариев кода, проблема состоит в том, что если метод jedis.del () называется, блокировка будет разблокирован, когда он больше не принадлежит текущему клиенту. Так действительно ли такой сценарий? Ответ да. Например, клиент A блокирует, и через некоторое время, клиент A разблокирует. Перед выполнением jedis.del () замок внезапно истекает. В настоящее время Client B пытается успешно заблокировать, а затем Client A выполняет метод del (), а затем блокировка клиента B разблокирована.
Суммировать
Эта статья в основном представляет, как правильно реализовать распределенную блокировку Redis с использованием кода Java. Два классических примера ошибок приведены для блокировки и разблокировки. Фактически, не сложно внедрить распределенные замки через Redis, если это гарантированно соответствовать четырем условиям надежности.
В каком сценарии распределенные замки используются в основном? Там, где требуется синхронизация, например, вставка фрагмента данных требует заранее проверить, имеет ли база данных аналогичные данные. Когда несколько запросов вставлены одновременно, можно определить, что база данных не имеет аналогичных данных, и все они могут быть добавлены. В настоящее время требуется синхронная обработка, но таблица блокировки прямой базы данных слишком трудоемкой, поэтому используется распределенная блокировка Redis. В то же время, только один поток может выполнять операцию вставки данных, а другие потоки ждут.
Выше приведено все содержание этой статьи о языке Java, описывающем правильную реализацию распределенных замков Redis. Я надеюсь, что это будет полезно для всех. Заинтересованные друзья могут продолжать ссылаться на другие связанные темы на этом сайте. Если есть какие -либо недостатки, пожалуйста, оставьте сообщение, чтобы указать это. Спасибо, друзья, за вашу поддержку на этом сайте!