Сейчас я работаю с LockRegistryLeaderInitiator:
org.springframework.integration.support.leader.LockRegistryLeaderInitiator
и JdbcLockRegistry:
org.springframework.integration.jdbc.lock.JdbcLockRegistry
и я вижу проблему, когда узлы, не являющиеся ведущими, выполняют вызовы базы данных каждые 100 мс.
Думаю, я понимаю, что происходит. Инициатор-лидер входит в замкнутый цикл, когда блокировка не удерживается:
while (isRunning()) {
...
// We always try to acquire the lock, in case it expired
boolean acquired = this.lock.tryLock(LockRegistryLeaderInitiator.this.heartBeatMillis,
TimeUnit.MILLISECONDS);
if (!this.locked) {
if (acquired) {
// Success: we are now leader
this.locked = true;
handleGranted();
}
else if (isPublishFailedEvents()) {
publishFailedToAcquire();
}
}
...
}
Тайм-аут вызова tryLock(...) управляет скоростью, с которой этот код зацикливается, блокируя на период одного тактового сигнала, если блокировка не может быть получена.
Проблема возникает из-за того, что tryLock записан в некоторых реализациях LockRegistry. Возьмем, к примеру, JdbcLockRegistry:
while (true) {
try {
while (!(acquired = doLock()) && System.currentTimeMillis() < expire) { //NOSONAR
Thread.sleep(100); //NOSONAR
}
...
}
...
}
Метод tryLock будет вращаться, неоднократно делая запросы к БД каждые 100 мс, пока не истечет время ожидания. Таким образом, пока инициатор блокировки не удерживает блокировку, он будет повторно выполнять вызовы каждые 100 мс до бесконечности.
Мне удалось решить эту проблему с помощью следующего изменения метода LockRegistryLeaderInitiator call():
while (isRunning()) {
try {
...
//We always try to acquire the lock, in case it expired
boolean acquired = this.lock.tryLock(); // # Make a single attempt to acquire the lock
if (!this.locked) {
if (acquired) {
// Success: we are now leader
this.locked = true;
handleGranted();
} else if (isRunning()) {
// Wait before trying again.
Thread.sleep(LockRegistryLeaderInitiator.this.heartBeatMillis); // # Make the heartbeat an explicit thread sleep.
}
}
...
}
...
}
Это намеренное поведение или ошибка?
Редактировать: LockRegistryLeaderInitiator имеет два свойства конфигурации для управления реакцией выборов:
/**
* Time in milliseconds to wait in between attempts to re-acquire the lock, once it is
* held. The heartbeat time has to be less than the remote lock expiry period, if
* there is one, otherwise other nodes can steal the lock while we are sleeping here.
* If the remote lock does not expire, or if you know it interrupts the current thread
* when it expires or is broken, then you can extend the heartbeat to Long.MAX_VALUE.
*/
private long heartBeatMillis = DEFAULT_HEART_BEAT_TIME;
/**
* Time in milliseconds to wait in between attempts to acquire the lock, if it is not
* held. The longer this is, the longer the system can be leaderless, if the leader
* dies. If a leader dies without releasing its lock, the system might still have to
* wait for the old lock to expire, but after that it should not have to wait longer
* than the busy wait time to get a new leader. If the remote lock does not expire, or
* if you know it interrupts the current thread when it expires or is broken, then you
* can reduce the busy wait to zero.
*/
private long busyWaitMillis = DEFAULT_BUSY_WAIT_TIME;
... и похоже, что busyWaitMillis должен быть задействован, когда блокировка не удерживается, и ее невозможно получить, но, похоже, в этом случае он не используется.




Я вижу, что есть аргумент в пользу того, чтобы сделать его настраиваемым; но это компромисс между отзывчивостью и активностью БД. Если задержка слишком велика, вы никогда не получите блокировку.
Лично я не фанат такой реализации, особенно с JDBC.
Не стесняйтесь открывать Проблема с улучшением JIRA, чтобы настроить сон.
Взносы приветствуются.