У меня есть 2 экземпляра контейнеров с запланированным заданием, и я хотел бы защититься от запуска одной и той же задачи в каждом контейнере докеров. Я решил сделать распределенную блокировку с помощью SQL Server.
я добавил конфигурацию
@Bean
public DefaultLockRepository DefaultLockRepository(DataSource dataSource) {
return new DefaultLockRepository(dataSource);
}
@Bean
public JdbcLockRegistry jdbcLockRegistry(LockRepository lockRepository) {
return new JdbcLockRegistry(lockRepository);
}
И я пытаюсь получить блокировку следующим образом:
public void doSomething() throws InterruptedException {
boolean lockAcquired;
var lock = lockRegistry.obtain("main");
try {
lockAcquired = lock.tryLock();
if (lockAcquired == true) {
logger.info("Locked");
} else {
logger.info("Not locked");
}
} catch (Exception ex) {
lockAcquired = false;
logger.info("Not locked");
}
Thread.sleep(3600000);
lock.unlock();
}
Я запустил 2 экземпляра приложения с одинаковым кодом. Когда я вошел в метод первого экземпляра приложения, была получена блокировка - и я получил сообщение "Заблокировано" - так что все в порядке.
В базе данных я увидел такую запись:
LOCK_KEY REGION CLIENT_ID CREATED_DATE
fad58de7-3664-35db-8650-cfefac2fcd61 DEFAULT c0314b87-0bee-4bdd-8567-a939fd21f0bf 2024-03-16 13:20:43.2559575I saw that in datatabase I've got new entry like this:
Когда я запустил второй экземпляр приложения, я ожидал получить сообщение «Не заблокировано», поскольку блокировка была получена в первом приложении, которое все еще работает, но нет - у меня снова есть журнал «Заблокировано», и моя запись в базе данных была обновлена до:
LOCK_KEY REGION CLIENT_ID CREATED_DATE
fad58de7-3664-35db-8650-cfefac2fcd61 DEFAULT ad58971d-c991-4709-a03b-9acb39a38c54 2024-03-16 13:24:42.3002365
Итак, моя распределенная блокировка не работает.
Что я делаю не так? Как я могу защититься от выполнения запланированной задачи с помощью распределенной блокировки во многих контейнерах докеров.
//ОБНОВЛЯТЬ
Когда я выполняю метод в двух экземплярах за короткий промежуток времени (например, менее 5 с), он работает, но когда я выполняю метод в первом экземпляре и пытаюсь выполнить его во втором экземпляре через 30 с, это не работает.
//ОБНОВЛЕНИЕ2
Как предположил Алекс, это была проблема с TTL в DefaultLockRepository. Блокировка была установлена на время по умолчанию 10 секунд. После изменения кода следующим образом:
@Bean
public DefaultLockRepository DefaultLockRepository(DataSource dataSource) {
var repo = new DefaultLockRepository(dataSource);
repo.setTimeToLive(1000 * 100);
return repo;
}
Для блокировки установлено значение 100 секунд, и в течение следующих 100 секунд другой экземпляр службы не сможет заблокировать метод.
Вам нужно проверить, что возвращает tryLock. docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/…
Я проверил, и всегда получаю правду - я обновил свой пример в сообщении. В любом случае - когда я выполняю метод во втором экземпляре за очень короткое время (менее 5 секунд), блокировка срабатывает - так что, возможно, у меня проблема со временем блокировки.
@santal, когда ты говоришь, что использование большего временного интервала не работает, что происходит? Также ваш пример никогда не снимает блокировку?
Я думаю, вам следует изменить TTL в DefaultLockRepository, используя метод setTimeToLive, потому что его значение по умолчанию составляет 10 секунд.
@ Алекс, да, это решение! После изменения TTL в DefaultLockRepository все работает! Можете ли вы опубликовать свой комментарий в качестве ответа на вопрос?
Если это проблема взаимоблокировки, это предполагает, что логика вашего приложения может быть причиной проблемы.
Я не буду публиковать это как ответ, потому что использовать spring lock support в вашем случае — не лучшая идея. Если вам просто нужно предотвратить дублирование сервисов, достаточно использовать собственный общий lock в БД, проверяя его наличие. Я ответил, почему ваш код не работает во временных случаях




Я успешно использовал ShedLock. Добавление следующего в pom.xml (или соответствующий градиент)
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>${shedlock.version}</version>
</dependency>
Затем добавьте следующее в основной класс или класс конфигурации (пример в Kotlin, но та же концепция для Java):
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
class MainClass //...
@Bean
fun lockProvider(dataSource: DataSource): LockProvider {
return JdbcTemplateLockProvider(dataSource)
}
//...
Затем добавьте к методу следующую аннотацию:
@Service
class NeedingLockService //...
@Scheduled(fixedDelay = 5000) // using it at a schedule interval in my case but not relevant for lock
@SchedulerLock(name = "nameOfTheLock")
fun dispatch() { // name doesn't matter
//...
}
В этом примере Kotlin сервис будет запускаться каждые 5 секунд, но только в одном контейнере/поде, если у вас их несколько.
Примечания:
databaseChangeLog:
- changeSet:
id: 5
author: Geoffrey
changes:
- createTable:
tableName: shedlock
columns:
- column:
name: name
type: varchar(64)
constraints:
primaryKey: true
nullable: false
- column:
name: lock_until
type: timestamp
constraints:
nullable: false
- column:
name: locked_at
type: timestamp
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: false
- column:
name: locked_by
type: varchar(255)
constraints:
nullable: false
пожалуйста, проверьте, пытается ли lock.tryLock() вернуть true в обоих случаях.