JPA синхронизация между экземплярами микросервисов - PullRequest
0 голосов
/ 18 декабря 2018

У меня есть задание на основе таймера

@Component
public class Worker {

    @Scheduled(fixedDelay = 100)
    public void processEnvironmentActions() {
        Job job = pickJob();
    }

    public Job pickJob() {
        Job job = jobRepository.findFirstByStatus(Status.NOT_PROCESSED);
        job.setStatus(Status.PROCESSING);
        jobRepository.save(job);
        return job;
    }
}

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

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

РЕДАКТИРОВАТЬ: Я думаю, что люди запутались / сосредоточены на @Transactional, поэтому удалили его.Вопрос остается прежним.

Ответы [ 4 ]

0 голосов
/ 19 декабря 2018

@ Ответ Йенса Шаудера указывает мне правильное направление.Позвольте мне поделиться кодом, чтобы он направлял других людей.Вот как я решил свою проблему, я изменил свой класс работы, как показано ниже

@Entity 
public class Job {
   @Version
   private Long version = null; 
   // other fields omitted for bervity
}

Теперь давайте проследим следующий код

@Transactional
public Job pickJob() {
    Job job = jobRepository.findFirstByStatus(Status.NOT_PROCESSED);
    job.setStatus(Status.PROCESSING);
    Job saved jobRepository.save(job);
    return saved;
}

Примечание: убедитесь, что вы возвращаете объект saved, а не объект job.Если вы вернете объект задания, он не выполнится в течение второй операции save, поскольку счетчик версий, который был для job, будет отставать от счетчика для saved.

.

Service 1                                 Service 2 

1. Read Object (version = 1)              1. Read Object (version = 1)
2. Change the object and save 
      (changes the version)
3. Continues to process                   2. Change the object and save 
                                               (this operation fails as 
                                                the version that was read 
                                                was 1 but in the DB version is 2)
                                          3. Skip the job processing 

Таким образом, задание будет обработано только одним процессом.

0 голосов
/ 18 декабря 2018

Я согласен с @ Conffusion ответом, но, не зная о политике сброса, вы должны использовать метод jobRepository.saveAndFlush(job), чтобы быть уверенными, что операторы SQL передаются в базу данных.

см. также Разница между сохранением и сохранением AndFlush в данных Spring jpa

0 голосов
/ 18 декабря 2018

Но что произойдет, если есть два экземпляра микросервиса, выполняющих этот фрагмент кода в одно и то же время?

Как часто ответ таков: зависит.

Все это предполагает, что ваш код выполняется внутри транзакции

  1. Оптимистическая блокировка.

    Если ваша сущность Job имеет атрибут версии, то есть атрибут, помеченный @Version аннотация.Оптимистическая блокировка включена.Если процессы обращаются к одному и тому же заданию, можно заметить, что атрибут версии изменился при попытке сохранить измененный объект задания и завершиться с ошибкой OptimisticLockingException.Все, что вам нужно сделать, это обработать это исключение, чтобы ваш процесс не умер, а попытался снова получить следующую Job.

  2. Нет (уровень JPA) блокировки.

    Если объект Job не имеет атрибута версии, по умолчанию JPA не применяет никакой блокировки.Второй процесс, получающий доступ к Job, выдаст обновление, которое по сути является NOOP, поскольку первый процесс уже обновил его.Никто не заметит проблему.Вы, вероятно, хотите избежать этого.

  3. Пессимистическая блокировка

    Блокировка pessimistic_write не позволит никому прочитать сущность до того, как вы закончили читать и писать ее (по крайней мере, это моепонимание спецификации JPA).Следовательно, это не должно позволять второму процессу выбирать строку до того, как первый процесс завершит ее запись.Это, вероятно, блокирует весь второй процесс.Поэтому убедитесь, что транзакция, содержащая такую ​​блокировку, короткая.

    Чтобы получить такую ​​блокировку, аннотируйте метод хранилища findFirstByStatus с помощью @Lock(LockModeType.PESSIMISTIC_WRITE).

OfКонечно, могут быть библиотеки или фреймворки, которые обрабатывают такие детали для вас.

0 голосов
/ 18 декабря 2018

Я не знаком с Spring-Batch, но, видимо, Spring-Batch реализует оптимистическую блокировку, поэтому операция сохранения завершится неудачно, если другой поток уже выбрал такое же задание.

См. Горизонтальное масштабирование пакетной пружины

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...