История эволюции распределенных замков Redis

Redis база данных

История развития распределенной блокировки Redis

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

Тем не менее, я видел различные версии реализации распределенной блокировки Redis на работе и в Интернете.Каждая реализация имеет некоторые неточности и даже может быть неправильной реализацией, в том числе в коде.Если распределенную блокировку нельзя использовать правильно, что может привести к серьезным сбои производственной среды В этой статье в основном систематизированы различные распределенные блокировки, встречающиеся в настоящее время, и их дефекты, а также даны рекомендации по выбору подходящей распределенной блокировки Redis.

Различные версии распределенных замков Redis

  • V1.0
tryLock(){  
    SETNX Key 1
    EXPIRE Key Seconds
}
release(){  
  DELETE Key
}

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

Одна проблема с этой схемой заключается в том, что каждый раз, когда отправляется запрос Redis, если приложение работает ненормально или перезапускается после выполнения первой команды, срок действия блокировки не истекает.Схема улучшения заключается в использовании сценариев Lua (включая команды SETNX и EXPIRE), но если произойдет сбой Redis или произойдет переключение ведущий-ведомый после выполнения только одной команды, блокировка все равно не будет иметь срока действия, что в конечном итоге приведет к невозможности освобождения.

Другая проблема заключается в том, что в процессе снятия распределенных блокировок многие студенты находятся в затруднительном положении.finallyЭто неправильное использование замков, эта проблема будет решена в следующей версии V3.0.

Решение проблемы невозможности снятия блокировки основано наGETSETкоманда для реализации

  • V1.1 на основеGETSET
tryLock(){  
    NewExpireTime=CurrentTimestamp+ExpireSeconds
    if(SETNX Key NewExpireTime Seconds){
         oldExpireTime = GET(Key)
          if( oldExpireTime < CurrentTimestamp){
              NewExpireTime=CurrentTimestamp+ExpireSeconds
              CurrentExpireTime=GETSET(Key,NewExpireTime)
              if(CurrentExpireTime == oldExpireTime){
                return 1;
              }else{
                return 0;
              }
          }
    }
}
release(){  
        DELETE key
    }

Идеи:

  1. SETNX(Key,ExpireTime) получает блокировку

  2. Если получение блокировки не удалось, проверьте, истек ли срок действия блокировки, по отметке времени, возвращаемой GET(Key)

  3. GETSET(Key,ExpireTime) Изменить значение на NewExpireTime

  4. Проверить старое значение, возвращенное GETSET, если оно равно значению, возвращенному GET, блокировка считается успешной.

    Уведомление: в этой версии удалена команда EXPIRE и используется значение метки времени Value для определения срока действия.

проблема:

  1. 在锁竞争较高的情况下,会出现Value不断被覆盖,但是没有一个Client获取到锁
  2. 在获取锁的过程中不断的修改原有锁的数据,设想一种场景C1,C2竞争锁,C1获取到了锁,C2锁执行了GETSET操作修改了C1锁的过期时间,如果C1没有正确释放锁,锁的过期时间被延长,其它Client需要等待更久的时间
  • Версия 2.0 основана наSETNX
tryLock(){  
    SETNX Key 1 Seconds
}
release(){  
  DELETE Key
}

После версии Redis 2.6.12 SETNX добавил параметр срока действия срока действия, который решил проблему, что атомность двух команд не может быть гарантирована. Но представьте следующий сценарий:
1. C1 Успешно приобрел блокировку, то C1 вызвал задачу выполнить слишком долго из-за ожидаемых или неизвестных причин GC, и, наконец, C1 не активно не отпускает блокировку до истечения замка. 2. C2 приобрел замок после замка C1 OUT и начал выполнять, в это время как C1, так и C2 одновременно выполняются, что может вызвать несоответствие данных из-за повторного выполнения.

приблизительная блок-схема

Существует проблема:

1. 由于C1的停顿导致C1 和C2同都获得了锁并且同时在执行,在业务实现间接要求必须保证幂等性
2. C1释放了不属于C1的锁
  • V3.0
tryLock(){  
    SETNX Key UnixTimestamp Seconds
}
release(){  
    EVAL(
      //LuaScript
      if redis.call("get",KEYS[1]) == ARGV[1] then
          return redis.call("del",KEYS[1])
      else
          return 0
      end
    )
}

Эта схема позволяет избежать проблемы, связанной с освобождением C1 блокировки, удерживаемой C2, упомянутой в версии 2.0, путем указания Value в качестве метки времени и проверки того, является ли значение блокировки значением получения блокировки при освобождении блокировки; несколько операций Redis, и, учитывая проблему параллелизма модели Check And Set, сценарии Lua используются, чтобы избежать проблем параллелизма.

Существует проблема:

  如果在并发极高的场景下,比如抢红包场景,可能存在UnixTimestamp重复问题,另外由于不能保证分布式环境下的物理时钟一致性,也可能存在UnixTimestamp重复问题,只不过极少情况下会遇到。
  • V3.1
tryLock(){  
    SET Key UniqId Seconds
}
release(){  
    EVAL(
      //LuaScript
      if redis.call("get",KEYS[1]) == ARGV[1] then
          return redis.call("del",KEYS[1])
      else
          return 0
      end
    )
}

После Redis 2.6.12SETNX также предоставляет параметр, эквивалентный команде SETNX, напоминающий об обратной стороне официальной версии документа, который, вероятно, будет удален.SETNX, SETEX, PSETEX, и вместо этого используйте команду SET.Другая оптимизация заключается в использовании самоувеличивающегося уникального UniqId вместо метки времени, чтобы избежать проблемы с часами, упомянутой в версии 3.0.

Это решение в настоящее время является лучшим решением для распределенной блокировки, но если в среде кластера Redis все еще есть проблемы:

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

Распределенная блокировка Redis: Redlock

Версия V3.1 безопасна только в сценарии с одним экземпляром.Среди зарубежных экспертов по распределенным системам ведутся горячие дискуссии о том, как реализовать распределенные блокировки Redis.antirezПредлагаемый алгоритм распределенной блокировки Redlock, вdistlockПодробное описание Redlock можно увидеть под темой.Далее китайское описание алгоритма Redlock (цитата)

Предположим, что есть N независимых узлов Redis

  1. Получить текущее время в миллисекундах.

  2. Последовательно выполните операцию получения блокировок для N узлов Redis. Эта операция получения аналогична предыдущему процессу получения блокировок на основе одного узла Redis, включая случайные строки.my_random_value, включая время истечения срока действия (например,PX 30000, то есть эффективное время блокировки). Для того, чтобы алгоритм мог продолжать работать, когда определенный узел Redis недоступен, операция получения блокировки также имеет тайм-аут (time out), который намного меньше эффективного времени блокировки (десятки миллисекунд). . Если клиенту не удается получить блокировку от узла Redis, он должен немедленно попробовать следующий узел Redis. Сбой здесь должен включать в себя любой тип сбоя, например, узел Redis недоступен или блокировка узла Redis уже удерживается другими клиентами (Примечание. В исходном тексте Redlock упоминается только случай, когда узел Redis недоступен, но Другие отказы также должны быть включены).

  3. Рассчитайте, сколько времени весь процесс приобретения замка принимает всего. Метод расчета должен вычтете время, записанное на шаге 1 от текущего времени. Если клиент успешно приобретает блокировку из большинства узлов Redis (> = N / 2 + 1), и общее время, потраченное на получение блокировки, не превышает время действия блокировки, то клиент учитывает окончательное приобретение замок; в противном случае Считается, что финальный захват блокировки не удался.

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

  5. Если окончательное получение блокировки не удается (возможно, из-за того, что количество узлов Redis, которые получают блокировку, меньше N/2+1, или весь процесс получения блокировки занимает больше времени, чем начальное время действия блокировки), то клиент должен немедленно отправить все. Узел Redis инициирует операцию снятия блокировки (то есть сценарий Redis Lua, представленный ранее).

  6. блокировка разблокировки: давсеУзел Redis инициирует операцию снятия блокировки

тем не мениеMartin KleppmannПредлагается для этого алгоритмавопроспредлагается строить на основе механизма токенов ограждения (требуется проверка токена для каждой операции над ресурсом)

  1. Redlock在系统模型上尤其是在分布式时钟一致性问题上提出了假设,实际场景下存在时钟不一致和时钟跳跃问题,而Redlock恰恰是基于timing的分布式锁
  2. 另外Redlock由于是基于自动过期机制,依然没有解决长时间的gc pause等问题带来的锁自动失效,从而带来的安全性问题。

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

Для задачи РедлокаРаспределенный замок Redis безопасности в конце егоПодробное описание китайского и предлагаемого анализа алгоритма проблемы редка.

Суммировать

Будь то одноэкземплярная распределенная блокировка Redis на основе версии SETNX или распределенная блокировка Redlock, она должна обеспечивать следующие характеристики.

  1. 安全性:在同一时间不允许多个Client同时持有锁
  2. 活性
    死锁:锁最终应该能够被释放,即使Client端crash或者出现网络分区(通常基于超时机制)
    容错性:只要超过半数Redis节点可用,锁都能被正确获取和释放

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

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

Efficiency(Эффективность): Для завершения операции без повторного выполнения требуется только один Клиент.Это свободная распределенная блокировка, и необходимо гарантировать только активность блокировки;

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

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