Как обрабатывать синхронизацию кода при запуске приложения на нескольких экземплярах docker (Spring JPA @Lock, синхронизация Java, запланированные задания,…)?

#java #spring #spring-data-jpa #synchronization #locking

Вопрос:

Контекст

У нас есть приложение Spring boot (API, используемый угловым интерфейсом).

Он работает в контейнере docker. Он использует один экземпляр базы данных PostgreSQL.

У нашего приложения были некоторые проблемы с загрузкой, поэтому мы попросили нас масштабировать его. Мы сказали нам запустить наш API на нескольких контейнерах docker для этого.

У нас есть несколько вопросов / проблем, связанных с синхронизацией кода в нескольких экземплярах docker, выполняющих наш код.

Проблема 1

У нас есть несколько @Scheduled заданий, интегрированных и развернутых с помощью нашего кода API.

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

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

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html#CRON_DISABLED

Звучит ли это правильно?

Проблема 2

Другая проблема заключается в том, что мы используем @Lock аннотации Spring для некоторых методов репозитория.

 public interface IncrementRepository extends JpaRepository<IncrementEntity, UUID> {

    @Lock(LockModeType.PESSIMISTIC_FORCE_INCREMENT)
    Optional<IncrementEntity> findByAnnee(String pAnneeAA);

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    IncrementEntity save(IncrementEntity pIncrementEntity);

}
 

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

Если я правильно понял этот механизм блокировки :

  • if a process execute this code, the Spring JPA @Transaction will acquire a lock on the IncrementEntity (lock the database table).
  • when another process tries do do the same thing before the first lock has been released by the first transaction, it should have a PessimisticLockException and the second transaction will rollback
  • this is managed by Spring at application level, NOT directly at database level (right??)

So what will happen if we’re running our code on several containers ?

  • app running in container 1 sets a lock
  • app running in container 2 execute the same code and tries to set the same lock while the first one has not been released yet
  • each Spring application running in different containers will probably acquire the lock without problems as they don’t share the same information?

Please tell me if I correctly understood how it works, and if we will effectively have a problem running such code on several docker containers.

I guess that solution would be to set a lock directly on the database table, as we have only one instance of it?

Is there a way to easily set / release the lock at database level using Spring JPA code ?

Или, может быть, я неправильно понял и установка блокировки с помощью @Lock аннотации Spring устанавливает реальную блокировку базы данных ?

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

Проблема 3

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

     String numIncrement;
    synchronized (this.mutex) {
        try {
            numIncrement = this.incrementService.getIncrement(var);
        } catch (Exception e) {
            // rethrow custom technical exception
        }
    }
 

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

Я предполагаю, что здесь у нас также возникнут проблемы, поскольку экземпляры docker не используют одну и ту же JVM, поэтому синхронизация может работать только в области самого контейнера… верно?

Вывод

Для решения всех этих проблем, пожалуйста, скажите мне, есть ли у вас какие-либо решения для обхода / адаптации нашего кода, чтобы он был совместим с масштабированием приложений.

Ответ №1:

После ряда тестов я могу подтвердить эти моменты в отношении моего первоначального вопроса

Проблема 1

Мы можем отключить пружинный CRON со - значением

 @Scheduled(cron = "-")
 

Проблема 2

Аннотация JPa Spring @Lock устанавливает блокировку самой базы данных. Он не управляется программным обеспечением Spring.

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

Проблема 3

Синхронизированный код с использованием synchronized ключевого слова JAVA, очевидно, управляется JVM, поэтому взаимное исключение кода между контейнерами отсутствует.