Правильное использование @Async, @Scheduled и thread-pool в Spring Boot - PullRequest
0 голосов
/ 05 сентября 2018

Я долго писал вопрос и долго. Но я старался показать как можно больше того, что я сделал, а что не ясно. Пожалуйста, закончите чтение и спасибо за ваше терпение!

Я перепробовал много экспериментов, напиши spring doc spring doc , (напиши вопросы на этом сайте) Но все равно не понимаю полную картину.

У меня есть задача реализовать несколько планировщиков на одном сервере весенней загрузки.

  1. Первый планировщик будет проверять данные в БД каждую 1 секунду и запускать некоторую логику.
  2. Второй планировщик будет отправлять запросы сторонним службам каждые 10 миллисекунд.

Южные планировщики должны работать с пулом потоков и иметь разные настройки. Например первый - 5 потоков, второй - 10 потоков. Хотя я понял, я попробовал несколько вариантов и, наконец, запутался, что выбрать и как правильно его использовать:

Для теста я создаю 2 компонента с логикой и каждый раз буду вызывать методы из этого компонента:

@Slf4j
@Component
public class TestBean {

    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
}

и

@Slf4j
@Component
public class TestBean2 {

    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("Second bean print");
    }
}

Я до сих пор не понимаю разницы, что и когда использовать - @Scheduled аннотация или TaskScheduler из кода. Я пытался создать метод с @Scheduled аннотацией:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;

    public MyScheduler(TestBean testBean, TestBean2 testBean2) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
    }

    @Scheduled(fixedRate = 1000L)
    public void test() {
        testBean.test();//call method from first bean every 1 sec
    }
}

Выходной журнал:

2018-09-05 13:17:28.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:17:37.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:17:46.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print

Обрабатывать одну нить и печатать журнал из первого компонента 9 с . После этого я добавляю TaskScheduler:

@Bean
ThreadPoolTaskScheduler taskScheduler(){
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(5);
    threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
    threadPoolTaskScheduler.setThreadNamePrefix("TASK_SCHEDULER_FIRST-");
    return threadPoolTaskScheduler;
}

И запустите приложение. Выход:

2018-09-05 13:21:40.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:21:49.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:21:58.973  INFO 7172 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:22:07.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print

Каждый 9 сек , но разные потоки печатают журнал из первого компонента. После этого я пытаюсь ввести TaskScheduler и запустить расписание другим способом:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;
    private final ThreadPoolTaskScheduler taskScheduler;

    public MyScheduler(TestBean testBean, TestBean2 testBean2, ThreadPoolTaskScheduler taskScheduler) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
        this.taskScheduler = taskScheduler;
    }

    @PostConstruct
    public void test() {
        taskScheduler.scheduleAtFixedRate(testBean::test, 1000L);
        testBean.test();
    }
}

Но получил похожий вывод:

2018-09-05 13:25:54.541  INFO 7044 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:03.541  INFO 7044 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:12.541  INFO 7044 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:21.541  INFO 7044 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print

После этого я прочитал, что мне нужно использовать аннотацию @Async и запустить метод bean в async:

@Slf4j
@Component
public class TestBean {

    @Async
    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
}

Выход:

2018-09-05 13:28:07.868  INFO 8608 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:07.868  INFO 8608 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:08.860  INFO 8608 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:09.860  INFO 8608 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:10.860  INFO 8608 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print

Каждые 1 сек начать новую тему. Это оно! Но что, если я верну @Scheduled аннотацию:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();//async method
}

Результат такой же, как и в предыдущей версии. именно то, что нужно!

Но теперь я хочу использовать второй компонент. Я превращаю метод во второй компонент Async и пытаюсь запустить:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();
    testBean2.test();
}

Выход:

2018-09-05 13:32:46.079  INFO 11108 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:32:46.079  INFO 11108 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:32:47.074  INFO 11108 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:32:47.074  INFO 11108 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:32:48.074  INFO 11108 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print

Оба метода используют ОДИН ThreadPoolTaskScheduler с 5 потоками. Но мне нужно начинать каждый метод с разных ThreadPoolTaskScheduler. Я создаю второй ThreadPoolTaskScheduler:

@Bean
ThreadPoolTaskScheduler taskScheduler2(){
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(9);
    threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
    threadPoolTaskScheduler.setThreadNamePrefix("TASK_SCHEDULER_SECOND-");
    return threadPoolTaskScheduler;
}

И начать:

2018-09-05 13:35:31.152  INFO 14544 --- [           main] c.e.scheduling.SchedulingApplication     : Started SchedulingApplication in 1.669 seconds (JVM running for 2.141)
2018-09-05 13:35:40.134  INFO 14544 --- [cTaskExecutor-2] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:35:40.134  INFO 14544 --- [cTaskExecutor-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:41.127  INFO 14544 --- [cTaskExecutor-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:35:41.127  INFO 14544 --- [cTaskExecutor-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:42.127  INFO 14544 --- [cTaskExecutor-5] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:42.127  INFO 14544 --- [cTaskExecutor-6] com.example.scheduling.TestBean2         : Second bean print

Обе бины печатают журнал, но с cTaskExecutor и не используют tasckScheduler1 или tasckScheduler2

Это мой первый вопрос - почему? Как это может работать?

Теперь я попытался использовать эту реализацию:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;
    private final ThreadPoolTaskScheduler poolTaskScheduler1;
    private final ThreadPoolTaskScheduler poolTaskScheduler2;

    public MyScheduler(TestBean testBean, TestBean2 testBean2,
                       @Qualifier("first") ThreadPoolTaskScheduler poolTaskScheduler1,
                       @Qualifier("second") ThreadPoolTaskScheduler poolTaskScheduler2) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
        this.poolTaskScheduler1 = poolTaskScheduler1;
        this.poolTaskScheduler2 = poolTaskScheduler2;
    }

//    @Scheduled(fixedRate = 1000L)
    @PostConstruct
    public void test() {
        poolTaskScheduler1.scheduleAtFixedRate(testBean::test, 1000L);
        poolTaskScheduler2.scheduleAtFixedRate(testBean2::test, 1000L);
    }
}

Вывод: ничего не изменилось.

И В готово я возвращаю код:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();
    testBean2.test();
}

И использовать @Async с квалификатором:

@Async("first")
@Async("second")

Выход:

2018-09-05 13:44:11.489  INFO 7432 --- [EDULER_SECOND-1] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:11.489  INFO 7432 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:12.484  INFO 7432 --- [EDULER_SECOND-2] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:12.484  INFO 7432 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:13.484  INFO 7432 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:13.484  INFO 7432 --- [EDULER_SECOND-3] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:14.484  INFO 7432 --- [EDULER_SECOND-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:14.484  INFO 7432 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:15.484  INFO 7432 --- [EDULER_SECOND-5] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:15.484  INFO 7432 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:16.483  INFO 7432 --- [EDULER_SECOND-6] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:17.483  INFO 7432 --- [EDULER_SECOND-7] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:18.483  INFO 7432 --- [EDULER_SECOND-8] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:19.483  INFO 7432 --- [EDULER_SECOND-9] com.example.scheduling.TestBean2         : Second bean print

именно то, что нужно! Но я не понимаю, правильно ли я поступаю

Если я изменю ThreadPoolTaskScheduler на ThreadPoolTaskExecutor, все будет работать так же. Так что я должен использовать?

ThreadPoolTaskScheduler или ThreadPoolTaskExecutor @Scheduled или ThreadPoolTaskScheduler / ThreadPoolTaskExecutor из кода? @Scheduled с ThreadPoolTaskScheduler / ThreadPoolTaskExecutor из кода или @Async?

1 Ответ

0 голосов
/ 05 января 2019

Давайте рассмотрим ваши вопросы один за другим:

  1. У вас были пользовательские ThreadPoolTaskSchedulers (TaskExecutor Beans), но вы не настроили @Async должным образом. По умолчанию Spring использует SimpleAsyncTaskExecutor для выполнения вашей задачи. Вот почему вы видели только журнал с сокращенным именем потока [cTaskExecutor-1], [cTaskExecutor-2] и т. Д. Если вы хотите использовать taskScheduler1 или taskScheduler2, вам нужно настроить @Async с соответствующим именем.

    @Async("taskScheduler1")
    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
  2. Позже вы настроили имена исполнителей «first» и «second» для ваших bean-компонентов, чтобы ваши асинхронные задачи работали. @Async поднял голову и нашел исполнителя Beans с указанными именами.

  3. TaskScheduler предназначен для планирования задач, а TaskExecutor - для асинхронных задач. ThreadPoolTaskScheduler реализует как TaskScheduler, так и TaskExecutor. ThreadPoolTaskExecutor реализует TaskExecutor, а не TaskScheduler.

    Вы использовали задачу планирования для запуска асинхронных задач, поэтому ThreadPoolTaskScheduler и ThreadPoolTaskExecutor являются взаимозаменяемыми, если вы не хотите использовать Детальная конфигурация ThreadPoolTaskExecutor для пула потоков, такая как setCorePoolSize , setMaxPoolSize , .. Если вы используете более 1 задачи планирования, которую хотите реализовать ThreadPoolTaskScheduler, потому что по умолчанию все задачи @Scheduled выполняется в пуле потоков по умолчанию размера 1, созданном Spring.

...