Как создать асинхронный и не параллельный планировщик весной?

#java #spring #asynchronous

Вопрос:

У меня есть в основном классе, который запускает приложение:

 @SpringBootApplication
@EnableAsync
public class ExperianRequestBotApplication extends RefApplication {

    public ExperianRequestBotApplication() throws RefException {
        super();
    }

    public static void main(String[] args) throws RefException {
        try {
            new ExperianRequestBotApplication().start(args);
        } catch (Exception e) {
            System.out.println(" ------- OFFLINE ------- ");
            System.out.println("La aplicación no esta disponible por :"   e);
        }

    }
}
 

и планировщик

 @Component
public class ScheduledTaskSincronizarContactos {

    @Autowired
    private ExperianRequestBotService experianRequestBotService;

    private final static Logger LOG = LoggerFactory.getLogger(ScheduledTaskSincronizarContactos.class);

    // Método Shedule encargado de sincronizar los usuarios modificados con Experian 
    @Async
    @Scheduled(cron = "0 */15 * ? * *")
    public void SincronizarContactos() throws Exception {
 

Я хочу предотвратить повторный запуск планировщика, если внутренний процесс занимает более 15 минут для повторного запуска задачи.

Я не могу найти дорогу. Я попытался реализовать @DisallowConcurrentExecution с помощью библиотеки quartz, но она все еще параллельна.

Есть какие-нибудь идеи?

Ответ №1:

Я столкнулся с аналогичной проблемой параллелизма при использовании весеннего планирования. У нас было много заданий, которые выполнялись на одном и том же сервисе и мешали друг другу. Мы перешли на использование кварцевого планирования — это казалось проще, чем многопоточный планировщик Spring с кучей других функций, которые мы хотели. Это репо было действительно полезным. https://gitlab.com/johnjvester/jpa-spec-with-quartz/-/blob/master/src/main/java/com/gitlab/johnjvester/jpaspec/config/QuartzConfig.java

Планирование Quartz также имеет то преимущество, что оно является постоянным — когда оно запускается, оно запускает все пропущенные задания. Можно также программно изменять операторы cron. Это может быть излишним для вашего варианта использования, но на это стоит взглянуть. 🙂 Кроме того, что было сказано, — используйте задержки, чтобы определить, когда задание должно выполняться по выражению cron, и вам гарантирован выигрыш даже с Spring!

Ответ №2:

По умолчанию все запланированные задачи выполняются в одном потоке в Spring boot, если только вы сами не определили реализацию пула потоков.

Вот ссылка для дополнительной справки, которая однажды мне пригодилась: https://crmepham.github.io/spring-boot-multi-thread-scheduling/

Я удивлен, увидев, что у вас возникла проблема с одновременным выполнением задач в первую очередь.

Как я в настоящее время выполняю запланированные задачи всякий раз, когда мне нужно, я помещаю аннотацию @EnableScheduling в основной класс и использую фиксированную скорость и начальную задержку.

Возможно, вы могли бы использовать фиксированную скорость и начальную задержку вместо cron, чтобы достичь того, что вы пытаетесь сделать.

Комментарии:

1. Проблема в том, что, когда процесс планировщика занимает более 15 минут, он снова запускает процесс и не ждет его завершения. То есть это происходит одновременно. Может ли проблема быть в асинхронной аннотации? Мне нужно, чтобы каждый процесс (их 4) выполнялся параллельно, но если один из них занимает более 15 минут, дождитесь его завершения, чтобы перезапустить. Спасибо за ответ 🙂

Ответ №3:

Удалить @Async из метода. @Async вызывает вызов метода отдельным исполнителем.

Ответ №4:

Проблема в том, что ваша работа может занять больше времени, чем запланировано для ее выполнения, и время между запусками. Одна из идей состояла бы в том, чтобы где-то хранить последнее время начала и последнее время окончания задания cron. Если последнее время окончания раньше, чем последнее время начала, то при следующем запуске игнорируйте все. То есть вам нужно что-то вроде этого:

 @SpringBootApplication
@EnableAsync
public class ExperianRequestBotApplication extends RefApplication {

    public ExperianRequestBotApplication() throws RefException {
        super();
    }

    public static void main(String[] args) throws RefException {
        try {
            if (ExperianRequestBotApplication.shouldRun()) {
                new ExperianRequestBotApplication().start(args);
            }
        } catch (Exception e) {
            System.out.println(" ------- OFFLINE ------- ");
            System.out.println("La aplicación no esta disponible por :"   e);
        }

    }
}
 

Обратите внимание, что условие с логическим валидатором обертывается вокруг основной части вашей работы. Это shouldRun должно проверить, меньше ли последняя дата начала, чем последняя дата окончания. Вам также нужно будет убедиться, что ваша работа управляет этими значениями. Это правда, что при таком подходе ваша работа по-прежнему выполняется каждые 15 минут, но, если последний запуск еще не завершен, текущий запуск будет чрезвычайно быстрым, он просто проверит, выполняется ли задание по-прежнему, и если да, то он ничего не будет делать. В противном случае он будет правильно выполнять свою работу. Это просто реализовать, защищает вас от выполнения одной и той же длительной работы несколько раз одновременно или последовательно и предоставит системе некоторую передышку для выполнения других операций.

Ответ №5:

Спасибо всем за ваши ответы, наконец-то мы решили использовать библиотеку quartz. Проще всего, используя @DisallowConcurrentExecution, мы можем иметь асинхронные задания, которые не выполняются одновременно.