Расписание Java для периодического запуска против Thread.sleep

Я столкнулся с конкретной проблемой. Я запланировал выполнение задачи каждый день в определенное время. Сама задача состоит из следующего:

  • Вызов базы данных, чтобы проверить, можно ли обновить данные.
  • Http-запрос к внешнему серверу, который возвращает список данных.
  • Проверка и преобразование каждого элемента списка в модель предметной области.
  • Снова вызов внешнего сервера для получения подсписка исходного списка данных для получения дополнительной информации (вот в чем настоящая проблема. И я никак не контролирую реакцию внешнего сервера).
  • Сохранение в базе данных списка результатов.
  • Сохранение метаданных о задаче в БД.

Проблема в том, что внешний сервер ограничивает количество запросов в минуту (об/мин) очень низким числом, скажем, 20. Таким образом, если я получу подсписок для дополнительных вызовов размером 100, то я смогу обновить только пятую часть этого количества, а остальное останется без дополнительной информации (учитывая общую скорость обработки задачи). В настоящее время я реализовал ограничитель скорости, который по сути приостанавливает задачу на минуту (например, когда количество запросов достигает 20), используя Thread.sleep, а затем продолжает снова и снова и т. д. В результате весь поток будет занят в зависимости от размера исходного списка примерно на 5-6 минут. Мне было интересно, могу ли я каким-то образом изменить способ вызова внешнего сервера, например, запланировать вызов запросов на основе ограничения количества оборотов в минуту. Любая помощь будет оценена по достоинству.

Редактировать. Я программно планирую задачу, используя Spring ThreadPoolTaskScheduler. Метод update — это задача, которую я выполняю.

public abstract class AbstractUpdateScheduler {
 protected ThreadPoolTaskScheduler taskScheduler;

 protected abstract void setTaskScheduler(ThreadPoolTaskScheduler taskScheduler);
 public abstract void update();
 public abstract String getCron();

 @PostConstruct
 private void execute() {
     taskScheduler.schedule(this::update, new 
  CronTrigger(getCron()));
 }
}

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

Iva_Only_Java 28.04.2024 18:23

ограничитель скорости гуавы может быть опцией guava.dev/releases/19.0/api/docs/index.html?com/google/commo‌​n/…

scigs 28.04.2024 21:26

Если вы не можете использовать этот ограничитель скорости Гуавы, как насчет использования ThreadPoolExecutor, но перехода к java.util.concurrent.DelayQueue — это скроет вещи в очереди до тех пор, пока не пройдет внутреннее delay.

AndrewL 28.04.2024 23:39
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
96
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Сон работает достаточно хорошо

Нет ничего плохого в том, чтобы использовать Thread.sleep в своих целях.

Вы сказали:

В результате весь поток будет занят в зависимости от размера исходного списка примерно на 5-6 минут.

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

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

Учитывая, что ваши подзадачи включают сетевые вызовы и взаимодействие с базой данных, они могут претендовать на выполнение в виртуальных потоках . Вы можете обрабатывать все объекты домена в каждой партии из двадцати одновременно, используя ExecutorService , поддерживаемый виртуальными потоками. Но учитывая ваши очень маленькие масштабы около сотни в день, я бы не стал заморачиваться. С таким же успехом они могли бы выполняться последовательно в одном потоке.

Для повседневной задачи используйте любой из:

  • Планировщик задач Spring, как показано в вашем коде. (урок на сайте Baeldung.com).
  • ScheduledExecutorService в Java SE.
  • Jakarta Concurrency в Jakarta EE.

В этой ежедневной задаче после создания экземпляра коллекции объектов домена, полученных из базы данных, зафиксируйте время начала. Затем обработайте каждый объект домена. Закончив эту партию, проверьте текущее время и поспите оставшуюся минуту. Проснитесь, повторяйте, пока не исчезнут объекты домена.

Класс коллекции, используемый для отслеживания объектов нашего домена, не обязательно должен быть потокобезопасным. Мы будем обрабатывать их в одном потоке, последовательно, поскольку мы никуда не торопимся (при условии, что мы уверены, что можем обрабатывать гораздо больше объектов домена в минуту, чем позволяет наша удаленная служба выполнять запросы в минуту).

Мы можем использовать реализацию Queue в качестве нашей коллекции, например LinkedList , извлекая по одному объекту домена за раз, пока ни одного не останется. Для получения дополнительной информации см. Руководство по интерфейсу очереди Java.

package work.basil.example.threading;

import java.time.Duration;
import java.time.Instant;
import java.util.*;

public class RateLimiting
{
    public static void main ( String[] args )
    {
        RateLimiting app = new RateLimiting( );
        app.demo( );
        System.out.println( "INFO - Demo done at " + Instant.now( ) );
    }

    private void demo ( )
    {
        // Our daily task. In real work, we would likely put in the `run` method of a class implementing `Runnable`.
        Collection < Person > persons = this.fetchDomainObjectsFromDatabase( );
        Queue < Person > queue = new LinkedList <>( persons );
        System.out.println( "queue = " + queue );
        final int REQUESTS_PER_MINUTE = 20;
        while ( !queue.isEmpty( ) )
        {
            int nthPerson = 0;
            Instant start = Instant.now( );
            while ( ( nthPerson < REQUESTS_PER_MINUTE ) && ( !queue.isEmpty( ) ) )
            {
                nthPerson = nthPerson + 1;
                Person person = queue.remove( );  // Should always succeed (not throw exception) as we already checked for not-empty.
                this.processPerson( person );
            }
            if ( !queue.isEmpty( ) )
            {
                Duration duration = Duration.between( Instant.now( ) , start.plus( Duration.ofMinutes( 1 ) ) ); // Determine how much of the one minute remains at this point.
                System.out.println( "💤 Sleeping until next minute… duration = " + duration );
                try { Thread.sleep( duration ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
            }
        }
    }

    private Collection < Person > fetchDomainObjectsFromDatabase ( )
    {
        // Simulate retrieval of domain objects from database.
        // Produces a collection of 86 distinct names.
        return
                Arrays.stream(
                                "Abigail,Alexandra,Alison,Amanda,Amelia,Amy,Andrea,Angela,Anna,Anne,Audrey,Ava,Bella,Bernadette,Carol,Caroline,Carolyn,Chloe,Claire,Deirdre,Diana,Diane,Donna,Dorothy,Elizabeth,Ella,Emily,Emma,Faith,Felicity,Fiona,Gabrielle,Grace,Hannah,Heather,Irene,Jan,Jane,Jasmine,Jennifer,Jessica,Joan,Joanne,Julia,Karen,Katherine,Kimberly,Kylie,Lauren,Leah,Lillian,Lily,Lisa,Madeleine,Maria,Mary,Megan,Melanie,Michelle,Molly,Natalie,Nicola,Olivia,Penelope,Pippa,Rachel,Rebecca,Rose,Ruth,Sally,Samantha,Sarah,Sonia,Sophie,Stephanie,Sue,Theresa,Tracey,Una,Vanessa,Victoria,Virginia,Wanda,Wendy,Yvonne,Zoe"
                                        .split( "," )
                        )
                        .map( ( String name ) -> new Person( UUID.randomUUID( ) , name ) )
                        .toList( );
    }

    private void processPerson ( Person person )
    {
        // Run your logic.
        // Record result in database.
        // Simulate this work with a Thread.sleep.
        System.out.println( "Processing person = " + person + " at " + Instant.now( ) );
        try { Thread.sleep( Duration.ofSeconds( 1 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
    }

    record Person( UUID id , String name ) { }
}

Этот код работает. При запуске:

queue = [Person[id=2abd94b3-7f4c-4e1a-956b-4f2b03356517, name=Abigail], Person[id=f408fdc5-2122-4485-ab74-7863d0bf2eb9, name=Alexandra], Person[id=a7e1205b-edec-466a-b6d6-fbcd7396a011, name=Alison], Person[id=14dcac0d-d51c-4b9e-9be3-34212c22d42e, name=Amanda], Person[id=0871d471-6a89-4fba-b8d7-4cc098468c90, name=Amelia], Person[id=a59614ee-d6d9-487b-ab2b-5bf50c1de139, name=Amy], Person[id=2796a781-fc01-48ab-a6aa-f19f470475f1, name=Andrea], Person[id=72a59b88-db60-4655-b201-d00892f0b3a3, name=Angela], Person[id=1e70459e-5deb-4ae2-9760-7d6bae098723, name=Anna], Person[id=214e9d8d-ee25-44d1-afff-1de193accecf, name=Anne], Person[id=5f8e525b-d6db-439e-aa6f-ca6e2fafc6d0, name=Audrey], Person[id=285cf471-a923-4407-b29e-4e45e73f232b, name=Ava], Person[id=fde7c58b-cce5-4750-9f03-463cd09171ed, name=Bella], Person[id=94ac09f5-a9c9-4b3b-be80-2f149d580e48, name=Bernadette], Person[id=7a396ebb-90db-45a2-a6ee-68cb791f4f3a, name=Carol], Person[id=3b04dcf5-86bc-416c-bc85-7c394cc036c2, name=Caroline], Person[id=da0be5e5-1211-4f0a-81cd-22cd4cd3765d, name=Carolyn], Person[id=89739b35-6465-43b7-bc40-955aa4c2fbfa, name=Chloe], Person[id=0110a410-5e37-4d4e-a830-7de636098f2e, name=Claire], Person[id=c20c70c7-5c4f-4fb8-97c6-b6035bb9e80e, name=Deirdre], Person[id=b3da74b8-2eb3-4007-b5c7-d7c222a185c0, name=Diana], Person[id=978021ef-37bd-4c26-85c7-c14732769926, name=Diane], Person[id=f40283bb-cbcc-4028-9c80-68aeea543b97, name=Donna], Person[id=41eb1f13-f696-4a7e-b9ed-2dd42cfdc95a, name=Dorothy], Person[id=b37c05c8-e141-4164-a14c-3f7c17873adf, name=Elizabeth], Person[id=4ddf6940-f15a-4bd4-8a24-aa867797ecc2, name=Ella], Person[id=57d88971-8a87-412f-859a-0e41c42f7e63, name=Emily], Person[id=55fd4051-dba9-4240-b600-614caa500b7a, name=Emma], Person[id=8b0f84d7-6f5e-4f46-8ee9-2e44748f116c, name=Faith], Person[id=9815a52c-0d24-4c88-8001-f42857297568, name=Felicity], Person[id=1315156f-0f4a-480d-8b1d-90b2bc2b00a4, name=Fiona], Person[id=9ad0d8c4-26a5-4209-820e-2eea7a1d12f2, name=Gabrielle], Person[id=cfd23971-7ea8-4c75-9eb9-96fc1e0a753a, name=Grace], Person[id=9f8d5da8-2957-4442-a9dd-3d3b5002acd8, name=Hannah], Person[id=7b5db86c-2b37-4c21-8dde-96fdccf38767, name=Heather], Person[id=4b679c2d-da1e-47ba-b96a-af866a5a98c4, name=Irene], Person[id=b083bb06-dfb9-4af5-80ca-fe8a5149b1af, name=Jan], Person[id=94fa0163-406b-4833-9498-e1e32db3d48f, name=Jane], Person[id=c4751c2e-6a9d-4be3-906f-dd2c6312c9be, name=Jasmine], Person[id=7b26cfa5-7eda-4b2b-8bdd-fbe08e962344, name=Jennifer], Person[id=6cb7428a-c2a0-4dae-80f2-c0b7841623ed, name=Jessica], Person[id=df32d55b-5b5e-4d5b-af92-3781edc21bcc, name=Joan], Person[id=81f19b9d-2229-4737-b539-caef61af58d4, name=Joanne], Person[id=fa3d8405-8900-4dcb-95f6-398308aa0d09, name=Julia], Person[id=7b789596-3755-48ad-97e7-22c1f3128576, name=Karen], Person[id=f86e0170-f386-4380-af1a-de8c830a4909, name=Katherine], Person[id=01ef3665-02ef-4ff9-84a5-aa383b87cdde, name=Kimberly], Person[id=c9b913e0-7077-4948-a47c-2b0eea997c9c, name=Kylie], Person[id=91bae483-e878-4c2d-8944-852289821ff8, name=Lauren], Person[id=cbe7c1a1-6911-4374-a915-aece18e0d83a, name=Leah], Person[id=1be50aab-c86c-481a-9a16-50aab8077c14, name=Lillian], Person[id=cd213071-74bb-4230-b32d-5d79b4de135d, name=Lily], Person[id=c68f490e-2020-43e5-9f89-010d3749f4d5, name=Lisa], Person[id=5ee64335-6bac-4b9e-9f7a-b6e1cb98a3f7, name=Madeleine], Person[id=43ba0c0a-3867-4b0d-8d20-617499487023, name=Maria], Person[id=ef368f8a-d613-4352-91ee-d38a864610f3, name=Mary], Person[id=772cae4d-5548-48cb-9007-5a99146eb368, name=Megan], Person[id=2d883cdb-1b56-463d-8cb5-b276132857bf, name=Melanie], Person[id=08bc8117-8ee6-4508-b52e-62832f1013d7, name=Michelle], Person[id=ec816887-5b4b-4eff-8455-87faedeabacf, name=Molly], Person[id=08f0f026-23bb-4cc6-92b8-77d3a67f9828, name=Natalie], Person[id=fc0a535e-53ee-41d7-b870-a50010c412c2, name=Nicola], Person[id=9ea3a368-00f2-4fde-85ed-86e1f25cc6e3, name=Olivia], Person[id=3c3bac1f-e4f6-49f3-9e2a-5083cda3e93a, name=Penelope], Person[id=2b6d1deb-ea5b-4e2e-aa60-0e617b5fa545, name=Pippa], Person[id=59d91b9f-5bc0-4f48-969b-482b997ca9e9, name=Rachel], Person[id=e292a0b3-2fb7-4b58-8e6a-c4b817db1406, name=Rebecca], Person[id=6bb49fdb-72cb-472d-8485-f1926e00add2, name=Rose], Person[id=4c2ccfcf-0657-438f-ab81-ca92e1382d9f, name=Ruth], Person[id=65f27ae9-b138-4309-9785-54eaa9f61146, name=Sally], Person[id=a548e13a-0d8e-4be8-b9b4-6b812154b25f, name=Samantha], Person[id=9c1f84d7-c430-4bf7-b0de-ef842f65cf08, name=Sarah], Person[id=7f50c02a-9121-402c-8aa3-10be6ae40ee3, name=Sonia], Person[id=eeb039cf-f23c-4c3c-9f1d-fc62491b6ca1, name=Sophie], Person[id=a00a14cf-48d7-44f9-9f75-f2c114b2682c, name=Stephanie], Person[id=1d7667e3-b733-42a9-963f-8da753568240, name=Sue], Person[id=bdd4037d-57f7-4b2a-bfe9-3f930d14d28b, name=Theresa], Person[id=a7b1af71-c6e1-4b49-936d-7b264105cbfc, name=Tracey], Person[id=7288a804-0184-40ec-a7de-989562a720a1, name=Una], Person[id=b5e61cae-1e6b-4b24-ba08-c7421266cc61, name=Vanessa], Person[id=bb52b99d-923e-4ef2-aba5-34e0c632b311, name=Victoria], Person[id=5e289a65-a9e9-4cb8-a1b8-f84f06878f22, name=Virginia], Person[id=29200823-f72e-4d49-9c35-e6d4beeff989, name=Wanda], Person[id=60d9b5a1-802b-4e7d-8098-bd9a92ee289a, name=Wendy], Person[id=d8d83e5f-c6d1-4175-9665-0ac913af776b, name=Yvonne], Person[id=b180ad21-b004-45c3-9acd-18402f41a960, name=Zoe]]
Processing person = Person[id=2abd94b3-7f4c-4e1a-956b-4f2b03356517, name=Abigail] at 2024-04-28T22:06:32.444050Z
Processing person = Person[id=f408fdc5-2122-4485-ab74-7863d0bf2eb9, name=Alexandra] at 2024-04-28T22:06:33.448366Z
Processing person = Person[id=a7e1205b-edec-466a-b6d6-fbcd7396a011, name=Alison] at 2024-04-28T22:06:34.454235Z
…
Processing person = Person[id=0110a410-5e37-4d4e-a830-7de636098f2e, name=Claire] at 2024-04-28T22:06:50.518077Z
Processing person = Person[id=c20c70c7-5c4f-4fb8-97c6-b6035bb9e80e, name=Deirdre] at 2024-04-28T22:06:51.522513Z
💤 Sleeping until next minute… duration = PT39.915957S
Processing person = Person[id=b3da74b8-2eb3-4007-b5c7-d7c222a185c0, name=Diana] at 2024-04-28T22:07:32.454997Z
…
Processing person = Person[id=c4751c2e-6a9d-4be3-906f-dd2c6312c9be, name=Jasmine] at 2024-04-28T22:07:50.553563Z
Processing person = Person[id=7b26cfa5-7eda-4b2b-8bdd-fbe08e962344, name=Jennifer] at 2024-04-28T22:07:51.558814Z
💤 Sleeping until next minute… duration = PT39.890573S
Processing person = Person[id=6cb7428a-c2a0-4dae-80f2-c0b7841623ed, name=Jessica] at 2024-04-28T22:08:32.458726Z
Processing person = Person[id=df32d55b-5b5e-4d5b-af92-3781edc21bcc, name=Joan] at 2024-04-28T22:08:33.462090Z
…
Processing person = Person[id=08bc8117-8ee6-4508-b52e-62832f1013d7, name=Michelle] at 2024-04-28T22:08:50.540338Z
Processing person = Person[id=ec816887-5b4b-4eff-8455-87faedeabacf, name=Molly] at 2024-04-28T22:08:51.545666Z
💤 Sleeping until next minute… duration = PT39.906484S
Processing person = Person[id=08f0f026-23bb-4cc6-92b8-77d3a67f9828, name=Natalie] at 2024-04-28T22:09:32.464408Z
Processing person = Person[id=fc0a535e-53ee-41d7-b870-a50010c412c2, name=Nicola] at 2024-04-28T22:09:33.470762Z
…
Processing person = Person[id=7288a804-0184-40ec-a7de-989562a720a1, name=Una] at 2024-04-28T22:09:50.565962Z
Processing person = Person[id=b5e61cae-1e6b-4b24-ba08-c7421266cc61, name=Vanessa] at 2024-04-28T22:09:51.571917Z
💤 Sleeping until next minute… duration = PT39.890426S
Processing person = Person[id=bb52b99d-923e-4ef2-aba5-34e0c632b311, name=Victoria] at 2024-04-28T22:10:32.468806Z
Processing person = Person[id=5e289a65-a9e9-4cb8-a1b8-f84f06878f22, name=Virginia] at 2024-04-28T22:10:33.472915Z
Processing person = Person[id=29200823-f72e-4d49-9c35-e6d4beeff989, name=Wanda] at 2024-04-28T22:10:34.475635Z
Processing person = Person[id=60d9b5a1-802b-4e7d-8098-bd9a92ee289a, name=Wendy] at 2024-04-28T22:10:35.477096Z
Processing person = Person[id=d8d83e5f-c6d1-4175-9665-0ac913af776b, name=Yvonne] at 2024-04-28T22:10:36.478467Z
Processing person = Person[id=b180ad21-b004-45c3-9acd-18402f41a960, name=Zoe] at 2024-04-28T22:10:37.484562Z
INFO - Demo done at 2024-04-28T22:10:38.490110Z

Делегируйте ограничение скорости

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

Чтобы узнать о различных подходах к ограничению скорости, см. этот пост Реализация ограничения скорости в Java с нуля — реализация Leaky Bucket и Token Bucket. Как указывают комментарии, вы можете обнаружить очередь с ограничением скорости от стороннего поставщика, например Google Guava.

java.util.concurrent.DelayQueue<E>

Как было отмечено , в Java есть одна реализация Queue, ограничивающая скорость, которая могла бы это сделать, DelayQueue.

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

Загвоздка с DelayQueue заключается в том, что его элементы должны реализовывать Delayed. Таким образом, вам придется либо (а) добавить эту функциональность к вашему объекту домена, либо (б) обернуть ваш объект домена в другой класс, обеспечивающий другую функциональность. Я мог бы предпочесть использовать приведенный выше код вместо того, чтобы беспокоиться об этом.

Будет ли Java Timer еще одной подходящей альтернативой?

gidds 29.04.2024 14:10

@gidds java.util.Timer и TimerTask были заменены платформой Executor в Java 5, как отмечено в Javadoc.

Basil Bourque 29.04.2024 17:56

Другие вопросы по теме