Проблемы с использованием весны @Scheduled - PullRequest
7 голосов
/ 10 марта 2020

У меня есть три метода в моем проекте с аннотацией @Scheduled, один из них является выражением cron, а два других имеют фиксированную задержку. Аннотации выглядят так:

Метод 1:

@Scheduled(fixedDelay = 20000)
@Async
protected void checkBrokenEngines() {

Метод 2:

@Scheduled(fixedRate = 20000)
@Async
public void checkAvailableTasks() throws Exception {

Метод 3:

@Scheduled(cron = "0 0 2 * * ?")
protected void deleteOldData() {

Ранее у меня было проблема в том, что когда методы checkBrokenEngines и checkAvailableTasks выполнялись медленно, следующие выполнения не выполнялись, пока не закончился предыдущий. Читая документацию и некоторые темы из StackOverflow, я обнаружил, что в моем проекте были неправильные настройки размер пула и методы не были помечены asyn c. (Asyn c был для следующего запуска выполнения, даже если старый не заканчивается, так как это не вызывает никаких проблем в моем приложении)

Теперь возникла другая проблема, которая является моим вопросом:

Когда выполняется метод deleteOldData(), ни один из двух других методов не будет работать до его завершения. Убедившись, что этот метод блокирует выполнение двух других, я аннотировал метод как asyn c, и после этого, даже если для выполнения этого метода требуется время, другие два всегда вызываются корректно в течение оговоренного времени. Почему? Насколько я понимаю, этого не должно происходить, поскольку эти методы отмечены с помощью asyn c, и в пуле достаточно места для их выполнения.

PS: deleteOldData() не может быть asyn c. Он должен начаться сразу после завершения предыдущего выполнения.

РЕДАКТИРОВАТЬ 1:

Asyn c Конфигурация исполнителя:

@Override    
public AsyncTaskExecutor getAsyncExecutor() {
    log.debug("Creating Async Task Executor");
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(50);
    executor.setMaxPoolSize(50);
    executor.setQueueCapacity(10000);        
    return new ExceptionHandlingAsyncTaskExecutor(executor);
}

Ответы [ 4 ]

4 голосов
/ 21 марта 2020

Запланированные задачи обрабатываются ThreadPoolTaskScheduler, который имеет размер пула по умолчанию 1 . Только когда они аннотированы как @Async, выполнение передается в AsyncTaskExecutor, который для вас настроил выделенного исполнителя с большим размером пула.

Чтобы настроить ThreadPoolTaskScheduler в классе @Configuration :

@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
    scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
    scheduler.setPoolSize(50);
    return scheduler ;
}
3 голосов
/ 21 марта 2020

Все @Scheduled отмеченные вызовы будут использовать однопотокового исполнителя по умолчанию для планирования задач (asyn c или иным образом).

Все @Async задачи передаются различным aysn c Threadpool Executor для выполнения с перехватчиком AOP.

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

Поскольку у вас есть пул потоков по умолчанию (один поток) для запланированных задач, они планируются один за другим.

Другие методы, помеченные @ Asyn c, выполняются, даже если это заканчивается или нет. В некоторых случаях у меня есть два способа выполнения одновременно. Но когда deleteOldData выполняется, методы asyn c останавливаются, пока не завершатся. Это то, что я не понимаю, извините: / -

Это отличается от планирования - это когда ваш асин c исполнитель вступает в игру, и они запускаются одновременно.

Это можно исправить одним из двух способов:

Вы можете использовать spring.task.scheduling.pool.size=10 в свойствах приложения, чтобы установить размер пула планировщика задач.

В качестве альтернативы можно использовать разные планировщики задач. Продолжайте использовать планировщик по умолчанию для задачи @Scheduled и настройте что-то вроде ниже для asyn c задач (удалить запланированную аннотацию)

Требуется усовершенствование для передачи планировщика задач в аннотацию @Scheduled до тех пор, пока вы не получите для планирования задач вручную.

Зарегистрируйте новый планировщик задач для вызовов asyn c и запланируйте методы на этапе после создания. Что-то вроде

Конфигурация

@Bean("asyncTaskScheduler")
public TaskScheduler asyncTaskScheduler() {
  return new ThreadPoolTaskScheduler();
}

Сервисы

@Autowired
private TaskScheduler asyncTaskScheduler;

@PostConstruct
void schedule() {
  asyncTaskScheduler.scheduleAtFixedRate(this::checkAvailableTasks, 20000L);
  asyncTaskScheduler.scheduleAtFixedDelay(this::checkBrokenEngines, 20000L);
}

@Async
public void checkBrokenEngines() {...}

@Async
public void checkAvailableTasks() throws Exception {...}
3 голосов
/ 21 марта 2020

Это происходит из-за того, что задание @ Asyn c отправляется по умолчанию для исполнителя планирования и его размер по умолчанию равен 1.

Я изменил метод отправки AsyncTaskExecutor исполнителя:

  @Bean
    AsyncConfigurer asyncConfigurer() {
        return new AsyncConfigurer() {
            @Override
            public AsyncTaskExecutor getAsyncExecutor() {
                ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(){
                    @Override
                    public <T> Future<T> submit(Callable<T> task) {
                        System.out.println("async task was started by thread -- "+Thread.currentThread().getName());
                        return super.submit(task);
                    }
                };
                executor.setThreadNamePrefix("custom-async-exec");
                executor.setCorePoolSize(2);
                executor.setQueueCapacity(100);
                executor.initialize();
                return executor;
            }
        };
    } 

И вывод.

async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1

Так как в пул планировщиков по умолчанию входит 1 поток scheduling-1 и всякий раз, когда он занят, не может запускать / отправлять новые @Async задачи. определить @Bean ThreadPoolTaskExecutor или добавить spring.task.scheduling.pool.size=x.

РЕДАКТИРОВАТЬ

Вот простой тест для визуализации:

@Component
    public static class Jobs{
        @Scheduled(fixedDelay = 1500)
        @Async
        public void job1(){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        @Scheduled(fixedDelay = 1500)
        @Async
        public void job2(){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        @Scheduled(initialDelay = 10000, fixedDelay = 5000)
        public void blocking(){
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

визуализация из visualvm

enter image description here

Красными «стрелками» показана точка blocking() заданий, и пока scheduling-1 Thread заблокирован, нет способ подачи job1() и job2() также

2 голосов
/ 21 марта 2020

Просто «еще 1 аспект»: Cite: https://www.baeldung.com/spring-async#the -asyn c -аннотация

Сначала - давайте go по правилам - @Async имеет два ограничения:

  • оно должно применяться только к publi c только методы

  • самовывоз - вызов метода asyn c из того же класса - не будет работать

side примечание: <- те же правила применяются к аннотациям <code>@Transactional, ...

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

@Scheduled protected void ... (), с другой стороны, нормально, так как (снова * , thx to baeldung ):

Простые правила, которым необходимо следовать для аннотирования метода с помощью @Scheduled:

  • метод должен иметь тип возврата void
  • метод не должен принимать никаких параметров

Пожалуйста, проверьте, существуют ли какие-либо проблемы / недоразумения после применения правил @Async и рассмотрения других [правильных - очень хороших] ответов. (baeldung и @Nonika используют public методы!)

В статье Baeldung @Scheduled также предлагается :

... Обратите внимание, что запланировано задачи не запускаются параллельно по умолчанию. Поэтому, даже если мы использовали fixedRate, следующая задача не будет вызвана, пока не будет выполнена предыдущая.

Если мы хотим поддерживать параллельное поведение в запланированных задачах, нам нужно добавить @ Asyn c аннотация:

@EnableAsync
public class ScheduledFixedRateExample {
  @Async
  @Scheduled(fixedRate = 1000)
  public void scheduleFixedRateTaskAsync() throws InterruptedException {
    System.out.println(
      "Fixed rate task async - " + System.currentTimeMillis() / 1000);
    Thread.sleep(2000);
  }
}

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

моя заметка : Конечно асинхронные задания могут блокировать друг друга на каком-то "общем ресурсе" (file / object / db / lock / семафор / et c.) ... которые могут привести к проблемам / "полным пулам" / взаимоблокировкам ... вы должны очистить его, прежде чем принять решение о @Async.

И, так как больше никаких слов о конфигурации ... я "предполагаю / надеюсь", он работает "из коробки" / как задокументировано.

Итак, отсюда видно немного информации:

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