Делаем распределенные Локи в Java приложение

Не здоровоюсь (с). Наверное, распределенные локи (distributed locks), а также распределенные транзакции (distributed transactions, или XA transactions) — это главные желанные фичи у разработчиков, кто мигрирует свои приложения из монолитов в микросервисную среду. На самом деле, ни то, ни другое чаще всего вам не нужно. А если нужно — наверное, проблема в архитектуре. Но, порой, без распределенных транзакций и локов — не обойтись. Поэтому, их нужно уметь готовить.

С транзакциями мы разбиремся, пожалуй, в другой раз. Это крайне большая тема, которая затрагивает и двух-фазный коммит (two phase commit), и Saga, да и про алгоритмы консенсуса в этой теме тоже обычно любят говорить (RAFT и PAXOS).

Распределенные локи. Когда они вообще могут понадобиться? Ну, ответ простой — когда есть какой-то ресурс, с которым может работать одновременно лишь один клиент. В системе с правильно архитектурой такого быть, скорей всего, не должно. Но, бывают исключения. Самое известное мне — это, если ваше приложение имеет некоторый граф переходов состояний (условно говоря, Finite-state machine), и вам нужно обеспечить синхронизированный доступ к выполнению состояния.

На самом деле, в случае с состоянием есть и другое решение. Можно воспользоваться Optimistic locks. Оптимистичные блокировки вы сможете реализовать прямо в том сторедже, в котором вы и храните состояние. Кроме того, Optimistic locks реализованы прямо в Спринге — https://www.baeldung.com/jpa-optimistic-locking.

Однако в некоторых случаях вы можете предпочесть Pessimistic locks (пессимистичные блокировки). Тут есть два случая — вам нужна блокирока на какой-то объект базы данных (например, на строку некоторой таблицы), или вы хотите получить распределенный Pessimistic Лок на объект, который не отображается 1 в 1 на базу данных.

Если вы хотите пессимистичную блокировку на какую-то строку БД, и у вас современная база — то всё очень просто. У нас есть SELECT FOR UPDATEhttps://www.postgresql.org/docs/9.4/explicit-locking.html.

В случае, если вам нужна распределенная блокировка на объект, который не связан на прямую с базой данных, всё сложнее. Конечно же, в PostgreSQL для этого случая есть Advisory Locks. Но у них есть один фатальный недостаток — нет тайм-аута на максимальное время блокировки. Поэтому, можно случайно сделать лок, потом рестартануть приложение, и не разблокировтаь объект. Придется городить дополнительные механизмы для решения этой проблемы.

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

Концептуально, идея очень простая — давайте будем хранить в сторедже список активных локов, и время, до которого они активны. При вставки нового лока будем проверять — есть ли Лок с таким именем, который еще активен. Если Лока в сторедже не было — просто вставим. Если был — обновим время, до которого данный Лок активен. А процесс снятия Лока — это просто обновление времени, до которого активен лог, на текущее. Вместе с транзакциями базы данных, мы получим вполне надежное решение Распределенного лока поверх классической Реляционная база данных.

Кроме локов на основе RDBMS, обычно есть реализации и поверх Redis, Hazelcast, Apache Ignite, Consul, и других популярных стореджей. В каждой из реализаций могут использоваться свою трюки — ведь не везде есть транзакции. Но концептуально все решения, в целом, похожи.

В случае, если вы используте Java, уже есть несколько готовых реализаций Локов. Например, мне нравится https://github.com/lukas-krecan/ShedLock. ShedLock предоставляем довольно свободный API (можно напрямую брать и освобождать Локи, а можно просто отправлять задачи в Executor, где задача автоматически выполнится в Локе), а также все возможные реализации Стореджей, которые только могут быть.

Категории: Программирование