برنامج جدولة المهام لـ Java الذي تم استلهامه من الحاجة إلى خدمة java.util.concurrent.ScheduledExecutorService
أبسط من الكوارتز.
على هذا النحو، موضع تقدير أيضًا من قبل المستخدمين (cbarbosa2، rafaelhofmann، BukhariH):
الصخور ليب الخاص بك! أنا سعيد للغاية لأنني تخلصت من الكوارتز واستبدلته بك وهو أمر أسهل في التعامل معه!
cbarbosa2
أنظر أيضا لماذا لا الكوارتز؟
< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler</ artifactId >
< version >15.0.0</ version >
</ dependency >
قم بإنشاء جدول scheduled_tasks
في مخطط قاعدة البيانات الخاص بك. راجع تعريف الجدول لـ postgresql أو Oracle أو mssql أو mysql.
قم بإنشاء مثيل وبدء تشغيل المجدول، والذي سيبدأ بعد ذلك أي مهام متكررة محددة.
RecurringTask < Void > hourlyTask = Tasks . recurring ( "my-hourly-task" , FixedDelay . ofHours ( 1 ))
. execute (( inst , ctx ) -> {
System . out . println ( "Executed!" );
});
final Scheduler scheduler = Scheduler
. create ( dataSource )
. startTasks ( hourlyTask )
. threads ( 5 )
. build ();
// hourlyTask is automatically scheduled on startup if not already started (i.e. exists in the db)
scheduler . start ();
لمزيد من الأمثلة، تابع القراءة. للحصول على تفاصيل حول الأعمال الداخلية، راجع كيف تعمل. إذا كان لديك تطبيق Spring Boot، فقم بإلقاء نظرة على Spring Boot Use.
قائمة المنظمات المعروفة بتشغيل db-scheduler في الإنتاج:
شركة | وصف |
---|---|
Digipost | مزود صناديق البريد الرقمية في النرويج |
مجموعة في | واحدة من أكبر مجموعات النقل في بلدان الشمال الأوروبي. |
حكيم | طريقة رخيصة وسريعة لإرسال الأموال إلى الخارج. |
بيكر للتعليم المهني | |
مونيوريا | خدمة مراقبة المواقع. |
محمل | اختبار التحميل لتطبيقات الويب. |
ستاتينز فيفيسن | إدارة الطرق العامة النرويجية |
سنة ضوئية | طريقة بسيطة وسهلة لاستثمار أموالك على مستوى العالم. |
الملاحة | إدارة العمل والرعاية الاجتماعية النرويجية |
ModernLoop | يمكنك التوسع بما يتناسب مع احتياجات التوظيف في شركتك باستخدام ModernLoop لزيادة الكفاءة في جدولة المقابلات والتواصل والتنسيق. |
ديفيا | شركة الصحة الإلكترونية النرويجية |
بجعة | يساعد Swan المطورين على دمج الخدمات المصرفية بسهولة في منتجاتهم. |
تومرا | TOMRA هي شركة نرويجية متعددة الجنسيات تقوم بتصميم وتصنيع آلات البيع العكسي لإعادة التدوير. |
لا تتردد في فتح العلاقات العامة لإضافة مؤسستك إلى القائمة.
انظر أيضًا الأمثلة القابلة للتشغيل.
حدد مهمة متكررة وقم بجدولة التنفيذ الأول للمهمة عند بدء التشغيل باستخدام طريقة منشئ startTasks
. عند الانتهاء، ستتم إعادة جدولة المهمة وفقًا للجدول المحدد (راجع أنواع الجداول المحددة مسبقًا).
RecurringTask < Void > hourlyTask = Tasks . recurring ( "my-hourly-task" , FixedDelay . ofHours ( 1 ))
. execute (( inst , ctx ) -> {
System . out . println ( "Executed!" );
});
final Scheduler scheduler = Scheduler
. create ( dataSource )
. startTasks ( hourlyTask )
. registerShutdownHook ()
. build ();
// hourlyTask is automatically scheduled on startup if not already started (i.e. exists in the db)
scheduler . start ();
بالنسبة للمهام المتكررة ذات المثيلات والجداول المتعددة، راجع المثال RecurringTaskWithPersistentScheduleMain.java.
مثيل مهمة لمرة واحدة له وقت تنفيذ واحد في وقت ما في المستقبل (أي غير متكرر). يجب أن يكون معرف المثيل فريدًا في هذه المهمة، ويمكن استخدامه لتشفير بعض بيانات التعريف (مثل المعرف). للحصول على حالة أكثر تعقيدًا، يتم دعم كائنات Java المخصصة القابلة للتسلسل (كما هو مستخدم في المثال).
حدد مهمة لمرة واحدة وابدأ المجدول:
TaskDescriptor < MyTaskData > MY_TASK =
TaskDescriptor . of ( "my-onetime-task" , MyTaskData . class );
OneTimeTask < MyTaskData > myTaskImplementation =
Tasks . oneTime ( MY_TASK )
. execute (( inst , ctx ) -> {
System . out . println ( "Executed! Custom data, Id: " + inst . getData (). id );
});
final Scheduler scheduler = Scheduler
. create ( dataSource , myTaskImplementation )
. registerShutdownHook ()
. build ();
scheduler . start ();
... وبعد ذلك في مرحلة ما (في وقت التشغيل)، تتم جدولة التنفيذ باستخدام SchedulerClient
:
// Schedule the task for execution a certain time in the future and optionally provide custom data for the execution
scheduler . schedule (
MY_TASK
. instanceWithId ( "1045" )
. data ( new MyTaskData ( 1001L ))
. scheduledTo ( Instant . now (). plusSeconds ( 5 )));
مثال | وصف |
---|---|
EnableImmediateExecutionMain.java | عند جدولة عمليات التنفيذ للتشغيل now() أو قبل ذلك، سيتم تلميح Scheduler المحلي حول هذا الأمر، و"الاستيقاظ" للتحقق من عمليات التنفيذ الجديدة في وقت أبكر مما هو معتاد (كما تم تكوينه بواسطة pollingInterval . |
MaxRetriesMain.java | كيفية وضع حد لعدد مرات إعادة المحاولة التي يمكن أن يتم تنفيذها. |
ExponentialBackoffMain.java | كيفية استخدام التراجع الأسي كإستراتيجية إعادة المحاولة بدلاً من التأخير الثابت كما هو افتراضي. |
الأسيBackoffWithMaxRetriesMain.java | كيفية استخدام التراجع الأسي كإستراتيجية إعادة المحاولة ووضع حد صارم على الحد الأقصى لعدد مرات إعادة المحاولة. |
تتبع التقدم RecurringTaskMain.java | قد تقوم المهام المتكررة بتخزين task_data كوسيلة لاستمرار الحالة عبر عمليات التنفيذ. يوضح هذا المثال كيف. |
SpawningOtherTasksMain.java | يوضح مثيلات جدولة المهام الخاصة بآخر باستخدام executionContext.getSchedulerClient() . |
جدولةClientMain.java | يوضح بعض إمكانيات SchedulerClient . الجدولة وجلب عمليات الإعدام المجدولة وما إلى ذلك. |
RecurringTaskWithPersistentScheduleMain.java | وظائف متكررة متعددة المثيلات حيث يتم تخزين Schedule كجزء من task_data . على سبيل المثال، مناسب للتطبيقات متعددة المستأجرين حيث يجب أن يكون لكل مستأجر مهمة متكررة. |
StatefulRecurringTaskWithPersistentScheduleMain.java | |
JsonSerializerMain.java | يتجاوز تسلسل task_data من تسلسل Java (افتراضي) إلى JSON. |
JobChainingUsingTaskDataMain.java | تسلسل المهام، أي "عند الانتهاء من تنفيذ هذا المثيل، قم بجدولة مهمة أخرى. |
JobChainingUsingSeparateTasksMain.java | التسلسل الوظيفي، كما هو مذكور أعلاه. |
InterceptorMain.java | استخدام ExecutionInterceptor لإدخال المنطق قبل وبعد التنفيذ لجميع ExecutionHandler . |
مثال | وصف |
---|---|
أمثلة أساسية | مهمة أساسية لمرة واحدة ومهمة متكررة |
وظيفة المعاملات المرحلية | مثال على تنظيم مهمة من خلال المعاملات، أي التأكد من تشغيل وظيفة الخلفية في حالة تنفيذ المعاملة (جنبًا إلى جنب مع تعديلات قاعدة البيانات الأخرى). |
LongRunningJob | تحتاج المهام طويلة الأمد إلى البقاء على قيد الحياة عند إعادة تشغيل التطبيق وتجنب إعادة التشغيل من البداية. يوضح هذا المثال كيفية استمرار التقدم عند إيقاف التشغيل، بالإضافة إلى أسلوب تقييد المهمة ليتم تشغيلها ليلاً. |
تتبع الحالة المتكررة | مهمة متكررة بحالة يمكن تعديلها بعد كل تشغيل. |
ParallelJobSpawner | يوضح كيفية استخدام وظيفة متكررة لإنتاج وظائف لمرة واحدة، على سبيل المثال للموازاة. |
JobChaining | وظيفة لمرة واحدة مع خطوات متعددة . تتم جدولة الخطوة التالية بعد اكتمال الخطوة السابقة. |
تكرار المثيلات المتعددة | يوضح كيفية إنجاز مهام متكررة متعددة من نفس النوع، ولكن من المحتمل أن تختلف الجداول الزمنية والبيانات. |
يتم إنشاء المجدول باستخدام منشئ Scheduler.create(...)
. لدى المنشئ إعدادات افتراضية معقولة، ولكن الخيارات التالية قابلة للتكوين.
.threads(int)
عدد المواضيع. الافتراضي 10
.
.pollingInterval(Duration)
عدد المرات التي يقوم فيها المجدول بفحص قاعدة البيانات بحثًا عن عمليات التنفيذ الواجبة. 10s
الافتراضية.
.alwaysPersistTimestampInUTC()
يفترض المجدول أن أعمدة الطوابع الزمنية المستمرة تستمر Instant
s، وليس LocalDateTime
s، أي ربط الطابع الزمني بطريقة ما بمنطقة ما. ومع ذلك، فإن بعض قواعد البيانات لديها دعم محدود لمثل هذه الأنواع (التي لا تحتوي على معلومات المنطقة) أو غيرها من المراوغات، مما يجعل "التخزين دائمًا بالتوقيت العالمي المنسق" بديلاً أفضل. في مثل هذه الحالات، استخدم هذا الإعداد لتخزين المحادثات الفورية دائمًا بالتوقيت العالمي المنسق (UTC). يتم اختبار مخططات PostgreSQL وOracle للحفاظ على معلومات المنطقة. مخططات MySQL و MariaDB لا تستخدم هذا الإعداد ويجب أن تستخدمه. ملحوظة: بالنسبة للتوافق مع الإصدارات السابقة، فإن السلوك الافتراضي لقواعد البيانات "غير المعروفة" هو افتراض أن قاعدة البيانات تحافظ على المنطقة الزمنية. بالنسبة لقواعد البيانات "المعروفة"، راجع فئة AutodetectJdbcCustomization
.
.enableImmediateExecution()
إذا تم تمكين هذا، فسيحاول المجدول تلميح Scheduler
المحلي بأن هناك عمليات تنفيذ سيتم تنفيذها بعد جدولتها للتشغيل now()
، أو في وقت في الماضي. ملحوظة: إذا حدث استدعاء schedule(..)
/ reschedule(..)
من داخل إحدى المعاملات، فقد يحاول المجدول تشغيلها قبل أن يصبح التحديث مرئيًا (لم يتم الالتزام بالمعاملة). ومع ذلك، فإنه لا يزال مستمرًا، لذلك حتى لو تم إغفاله، فسيتم تشغيله قبل polling-interval
التالية. يمكنك أيضًا تشغيل فحص مبكر برمجيًا لعمليات التنفيذ المستحقة باستخدام طريقة الجدولة جدولة. scheduler.triggerCheckForDueExecutions()
). افتراضي false
.
.registerShutdownHook()
يُسجل خطاف إيقاف التشغيل الذي سيستدعي Scheduler.stop()
عند إيقاف التشغيل. يجب دائمًا استدعاء التوقف من أجل إيقاف التشغيل بشكل سلس وتجنب عمليات الإعدام الميتة.
.shutdownMaxWait(Duration)
المدة التي سينتظرها المجدول قبل مقاطعة سلاسل خدمة المنفذ. إذا وجدت نفسك تستخدم هذا، ففكر في ما إذا كان من الممكن بدلاً من ذلك التحقق بشكل منتظم executionContext.getSchedulerState().isShuttingDown()
في ExecutionHandler وإجهاض المهمة طويلة الأمد. الافتراضي 30min
.
.enablePriority()
من الممكن تحديد أولوية لعمليات التنفيذ التي تحدد الترتيب الذي يتم به جلب عمليات التنفيذ الواجبة من قاعدة البيانات. سيتم تنفيذ التنفيذ ذو القيمة الأعلى للأولوية قبل التنفيذ ذو القيمة الأقل (من الناحية الفنية، سيتم order by priority desc, execution_time asc
). فكر في استخدام الأولويات في النطاق 0-32000 حيث يتم تعريف الحقل على أنه SMALLINT
. إذا كنت بحاجة إلى قيمة أكبر، قم بتعديل المخطط. في الوقت الحالي، هذه الميزة قابلة للاشتراك ، priority
العمود مطلوبة فقط من قبل المستخدمين الذين اختاروا تمكين الأولوية عبر إعداد التكوين هذا.
قم بتعيين الأولوية لكل مثيل باستخدام TaskInstance.Builder
:
scheduler . schedule (
MY_TASK
. instance ( "1" )
. priority ( 100 )
. scheduledTo ( Instant . now ()));
ملحوظة:
(execution_time asc, priority desc)
(استبدال execution_time asc
).null
للأولوية بشكل مختلف اعتمادًا على قاعدة البيانات (منخفضة أو عالية). إذا كنت تقوم بتشغيل أكثر من 1000 عملية تنفيذ/ثانية، فقد ترغب في استخدام استراتيجية استقصاء lock-and-fetch
لتقليل الحمل وإنتاجية أعلى (اقرأ المزيد). إذا لم يكن الأمر كذلك، فسيكون fetch-and-lock-on-execute
الافتراضي جيدًا.
.pollUsingFetchAndLockOnExecute(double, double)
استخدم استراتيجية الاستقصاء الافتراضية fetch-and-lock-on-execute
.
إذا كان الجلب الأخير من قاعدة البيانات عبارة عن دفعة كاملة ( executionsPerBatchFractionOfThreads
)، فسيتم تشغيل جلب جديد عندما يكون عدد عمليات التنفيذ المتبقية أقل من أو يساوي lowerLimitFractionOfThreads * nr-of-threads
. لا يتم قفل/اختيار عمليات التنفيذ التي تم جلبها، لذلك سيتنافس المجدول مع المثيلات الأخرى للقفل عند تنفيذها. بدعم من جميع قواعد البيانات.
الافتراضية: 0,5, 3.0
.pollUsingLockAndFetch(double, double)
استخدم استراتيجية الاقتراع lock-and-fetch
التي تستخدم select for update .. skip locked
لتقليل الحمل.
إذا كان الجلب الأخير من قاعدة البيانات عبارة عن دفعة كاملة، فسيتم تشغيل جلب جديد عندما يكون عدد عمليات التنفيذ المتبقية أقل من أو يساوي lowerLimitFractionOfThreads * nr-of-threads
. عدد عمليات التنفيذ التي يتم جلبها في كل مرة يساوي (upperLimitFractionOfThreads * nr-of-threads) - nr-executions-left
. تم بالفعل قفل/اختيار عمليات التنفيذ التي تم جلبها لمثيل المجدول هذا، وبالتالي حفظ عبارة UPDATE
واحدة.
للاستخدام العادي، اضبط على سبيل المثال 0.5, 1.0
.
للحصول على إنتاجية عالية (أي إبقاء الخيوط مشغولة)، اضبط على سبيل المثال 1.0, 4.0
. لا يتم تحديث نبضات القلب حاليًا لعمليات التنفيذ المختارة في قائمة الانتظار (ينطبق إذا كان upperLimitFractionOfThreads > 1.0
). إذا ظلوا هناك لأكثر من 4 * heartbeat-interval
(الافتراضي 20m
)، ولم يبدأوا التنفيذ، فسيتم اكتشافهم على أنهم ميتون ومن المحتمل أن يتم فتحهم مرة أخرى (يتم تحديده بواسطة DeadExecutionHandler
). مدعوم حاليًا بواسطة postgres . يدعم sql-server هذا أيضًا، لكن الاختبار أظهر أن هذا عرضة للتوقف التام وبالتالي لا يوصى به حتى يتم فهمه/حله.
.heartbeatInterval(Duration)
عدد مرات تحديث الطابع الزمني لنبضات القلب لتشغيل عمليات التنفيذ. الافتراضي 5m
.
.missedHeartbeatsLimit(int)
كم عدد نبضات القلب التي يمكن تفويتها قبل اعتبار الإعدام ميتاً. الافتراضي 6
.
.addExecutionInterceptor(ExecutionInterceptor)
يضيف ExecutionInterceptor
الذي قد يضخ المنطق حول عمليات التنفيذ. بالنسبة لـ Spring Boot، ما عليك سوى تسجيل Bean من النوع ExecutionInterceptor
.
.addSchedulerListener(SchedulerListener)
يضيف برنامج SchedulerListener
الذي سيتلقى الأحداث المتعلقة بالجدولة والتنفيذ. بالنسبة لـ Spring Boot، ما عليك سوى تسجيل Bean من النوع SchedulerListener
.
.schedulerName(SchedulerName)
اسم مثيل المجدول هذا. يتم تخزين الاسم في قاعدة البيانات عندما يتم اختيار التنفيذ بواسطة المجدول. الافتراضي <hostname>
.
.tableName(String)
اسم الجدول المستخدم لتتبع تنفيذ المهام. قم بتغيير الاسم في تعريفات الجدول وفقًا لذلك عند إنشاء الجدول. scheduled_tasks
الافتراضية.
.serializer(Serializer)
تطبيق برنامج التسلسل لاستخدامه عند إجراء تسلسل لبيانات المهمة. الافتراضي هو استخدام تسلسل Java القياسي، لكن db-scheduler يجمع أيضًا GsonSerializer
و JacksonSerializer
. راجع أمثلة لـ KotlinSerializer. راجع أيضًا الوثائق الإضافية ضمن المُسلسلات.
.executorService(ExecutorService)
إذا تم تحديدها، فاستخدم خدمة المنفذ المُدارة خارجيًا لتشغيل عمليات التنفيذ. من الناحية المثالية، يجب أن يتم توفير عدد سلاسل العمليات التي ستستخدمها (لتحسينات استقصاء المجدول). الافتراضي null
.
.deleteUnresolvedAfter(Duration)
الوقت الذي يتم بعده حذف عمليات التنفيذ ذات المهام غير المعروفة تلقائيًا. يمكن أن تكون هذه عادةً مهامًا متكررة قديمة لم تعد قيد الاستخدام. وهذا ليس صفرًا لمنع الإزالة غير المقصودة للمهام من خلال خطأ في التكوين (فقدان المهام المعروفة) والمشاكل أثناء الترقيات المتدرجة. الافتراضي 14d
.
.jdbcCustomization(JdbcCustomization)
يحاول برنامج db-scheduler اكتشاف قاعدة البيانات المستخدمة تلقائيًا لمعرفة ما إذا كانت هناك حاجة إلى تخصيص أي تفاعلات jdbc. هذه الطريقة عبارة عن فتحة هروب للسماح بتعيين JdbcCustomizations
بشكل صريح. الكشف التلقائي الافتراضي.
.commitWhenAutocommitDisabled(boolean)
بشكل افتراضي، لا يتم إصدار أي التزام على اتصالات مصدر البيانات. إذا تم تعطيل الالتزام التلقائي، فمن المفترض أن تتم معالجة المعاملات بواسطة مدير معاملات خارجي. قم بتعيين هذه الخاصية على true
لتجاوز هذا السلوك وجعل المجدول يصدر دائمًا عمليات الالتزام. افتراضي false
.
.failureLogging(Level, boolean)
يقوم بتكوين كيفية تسجيل فشل المهام، أي يتم طرح العناصر Throwable
من معالج تنفيذ المهمة. استخدم مستوى السجل OFF
لتعطيل هذا النوع من التسجيل بشكل كامل. WARN, true
.
يتم إنشاء المهام باستخدام إحدى فئات الإنشاء الموجودة في Tasks
. لدى المنشئين إعدادات افتراضية معقولة، ولكن يمكن تجاوز الخيارات التالية.
خيار | تقصير | وصف |
---|---|---|
.onFailure(FailureHandler) | انظر الوصف. | ماذا تفعل عندما يطرح ExecutionHandler استثناءً. افتراضيًا، تتم إعادة جدولة المهام المتكررة وفقًا Schedule وتتم إعادة محاولة المهام لمرة واحدة مرة أخرى خلال 5 دقائق. |
.onDeadExecution(DeadExecutionHandler) | ReviveDeadExecution | ما يجب فعله عند اكتشاف عملية إعدام ميتة ، أي عملية إعدام ذات طابع زمني قديم لنبضات القلب. افتراضيًا، تتم إعادة جدولة عمليات التنفيذ الميتة إلى now() . |
.initialData(T initialData) | null | البيانات التي سيتم استخدامها في المرة الأولى التي تتم فيها جدولة مهمة متكررة . |
تحتوي المكتبة على عدد من تطبيقات الجدول الزمني للمهام المتكررة. انظر Schedules
الصف.
جدول | وصف |
---|---|
.daily(LocalTime ...) | يعمل كل يوم في أوقات محددة. اختياريا يمكن تحديد منطقة زمنية. |
.fixedDelay(Duration) | وقت التنفيذ التالي هو Duration بعد آخر تنفيذ مكتمل. ملاحظة: يقوم هذا Schedule بجدولة التنفيذ الأولي لـ Instant.now() عند استخدامه في startTasks(...) |
.cron(String) | تعبير كرون بنمط الربيع (الإصدار 5.3+). النمط - يتم تفسيره على أنه جدول معطل. |
هناك خيار آخر لتكوين الجداول وهو قراءة أنماط السلسلة باستخدام Schedules.parse(String)
.
الأنماط المتوفرة حاليًا هي:
نمط | وصف |
---|---|
FIXED_DELAY|Ns | مثل .fixedDelay(Duration) مع ضبط المدة على N ثانية. |
DAILY|12:30,15:30...(|time_zone) | نفس .daily(LocalTime) مع منطقة زمنية اختيارية (مثل أوروبا/روما، UTC) |
- | جدول معطل |
يمكن العثور على مزيد من التفاصيل حول تنسيقات المنطقة الزمنية هنا.
يمكن وضع علامة على Schedule
على أنه معطل. لن يقوم المجدول بجدولة عمليات التنفيذ الأولية للمهام ذات الجدول الزمني المعطل، وسيقوم بإزالة أي عمليات تنفيذ موجودة لهذه المهمة.
قد تحتوي نسخة المهمة على بعض البيانات المرتبطة في الحقل task_data
. يستخدم المجدول Serializer
لقراءة هذه البيانات وكتابتها في قاعدة البيانات. افتراضيًا، يتم استخدام تسلسل Java القياسي، ولكن يتم توفير عدد من الخيارات:
GsonSerializer
JacksonSerializer
بالنسبة لتسلسل Java، يوصى بتحديد serialVersionUID
لتتمكن من تطوير الفئة التي تمثل البيانات. إذا لم يتم تحديده، وتغيرت الفئة، فمن المحتمل أن تفشل عملية إلغاء التسلسل باستخدام InvalidClassException
. في حالة حدوث ذلك، ابحث عن serialVersionUID
الحالي الذي تم إنشاؤه تلقائيًا وقم بتعيينه بشكل صريح. سيكون من الممكن بعد ذلك إجراء تغييرات غير منقسمة على الفصل.
إذا كنت بحاجة إلى الترحيل من تسلسل Java إلى GsonSerializer
، فقم بتكوين المجدول لاستخدام SerializerWithFallbackDeserializers
:
. serializer ( new SerializerWithFallbackDeserializers ( new GsonSerializer (), new JavaSerializer ()))
بالنسبة لتطبيقات Spring Boot، يوجد مشغل db-scheduler-spring-boot-starter
مما يجعل توصيلات المجدول بسيطة للغاية. (انظر المشروع المثال الكامل).
DataSource
عامل مع المخطط. (في المثال، يتم استخدام HSQLDB ويتم تطبيق المخطط تلقائيًا.)< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler-spring-boot-starter</ artifactId >
< version >15.0.0</ version >
</ dependency >
Task
الخاصة بك كفاصوليا الربيع. إذا كانت متكررة، فسيتم التقاطها وبدء تشغيلها تلقائيًا.Scheduler
في معلومات صحة المشغل، فأنت بحاجة إلى تمكين مؤشر صحة db-scheduler
. معلومات صحية في فصل الربيع. يتم التكوين بشكل أساسي عبر application.properties
. يتم تكوين اسم المجدول والمتسلسل وخدمة المنفذ عن طريق إضافة حبة من النوع DbSchedulerCustomizer
إلى سياق Spring الخاص بك.
# application.properties example showing default values
db-scheduler.enabled=true
db-scheduler.heartbeat-interval=5m
db-scheduler.polling-interval=10s
db-scheduler.polling-limit=
db-scheduler.table-name=scheduled_tasks
db-scheduler.immediate-execution-enabled=false
db-scheduler.scheduler-name=
db-scheduler.threads=10
db-scheduler.priority-enabled=false
# Ignored if a custom DbSchedulerStarter bean is defined
db-scheduler.delay-startup-until-context-ready=false
db-scheduler.polling-strategy=fetch
db-scheduler.polling-strategy-lower-limit-fraction-of-threads=0.5
db-scheduler.polling-strategy-upper-limit-fraction-of-threads=3.0
db-scheduler.shutdown-max-wait=30m
من الممكن استخدام Scheduler
للتفاعل مع عمليات التنفيذ المستقبلية المستمرة. بالنسبة للحالات التي لا تكون فيها هناك حاجة إلى مثيل Scheduler
كامل، يمكن إنشاء عميل جدولة أبسط باستخدام منشئه:
SchedulerClient . Builder . create ( dataSource , taskDefinitions ). build ()
سيسمح بعمليات مثل:
يتم استخدام جدول قاعدة بيانات واحد لتتبع عمليات تنفيذ المهام المستقبلية. عندما يحين موعد تنفيذ المهمة، يختارها برنامج db-scheduler وينفذها. عند الانتهاء من التنفيذ، يتم استشارة Task
لمعرفة ما يجب القيام به. على سبيل المثال، عادةً ما تتم إعادة جدولة RecurringTask
في المستقبل بناءً على Schedule
.
يستخدم المجدول القفل المتفائل أو التحديد للتحديث (اعتمادًا على استراتيجية الاستقصاء) لضمان حصول مثيل جدولة واحد فقط على اختيار تنفيذ المهمة وتشغيله.
يتم استخدام مصطلح المهمة المتكررة للمهام التي يجب تشغيلها بانتظام، وفقًا لبعض الجداول الزمنية.
عند انتهاء تنفيذ مهمة متكررة، تتم مراجعة Schedule
لتحديد الوقت التالي للتنفيذ، ويتم إنشاء تنفيذ مهمة مستقبلية لذلك الوقت (أي تتم إعادة جدولته ). الوقت المختار سيكون أقرب وقت وفقًا Schedule
، ولكنه لا يزال في المستقبل.
هناك نوعان من المهام المتكررة، المهمة المتكررة الثابتة العادية، حيث يتم تعريف Schedule
بشكل ثابت في التعليمات البرمجية، والمهام المتكررة الديناميكية ، حيث يتم تعريف Schedule
في وقت التشغيل ويستمر في قاعدة البيانات (لا يزال يتطلب جدولًا واحدًا فقط) .
تعد المهمة المتكررة الثابتة هي الأكثر شيوعًا ومناسبة لوظائف الخلفية العادية حيث يقوم المجدول تلقائيًا بجدولة مثيل للمهمة إذا لم يكن موجودًا ويقوم أيضًا بتحديث وقت التنفيذ التالي إذا تم تحديث Schedule
.
لإنشاء التنفيذ الأولي لمهمة متكررة ثابتة، يمتلك المجدول طريقة startTasks(...)
تأخذ قائمة بالمهام التي يجب "بدءها" إذا لم يكن لديها بالفعل تنفيذ موجود. يتم تحديد وقت التنفيذ الأولي بواسطة Schedule
. إذا كانت المهمة لديها بالفعل تنفيذ مستقبلي (على سبيل المثال، تم البدء بها مرة واحدة على الأقل من قبل)، ولكن Schedule
المحدث يشير الآن إلى وقت تنفيذ آخر، فستتم إعادة جدولة التنفيذ الحالي إلى وقت التنفيذ الجديد (باستثناء غير الحتمية جداول مثل FixedDelay
حيث يكون وقت التنفيذ الجديد في المستقبل).
قم بالإنشاء باستخدام Tasks.recurring(..)
.
تعد المهمة المتكررة الديناميكية إضافة لاحقة إلى db-scheduler وتمت إضافتها لدعم حالات الاستخدام حيث تكون هناك حاجة لمثيلات متعددة من نفس نوع المهمة (أي نفس التنفيذ) بجداول زمنية مختلفة. يتم الاحتفاظ Schedule
في task_data
إلى جانب أي بيانات عادية. على عكس المهمة المتكررة الثابتة ، لن تقوم المهمة الديناميكية بجدولة مثيلات المهمة تلقائيًا. الأمر متروك للمستخدم لإنشاء مثيلات وتحديث الجدول الزمني للمثيلات الموجودة إذا لزم الأمر (باستخدام واجهة SchedulerClient
). راجع المثال RecurringTaskWithPersistentScheduleMain.java لمزيد من التفاصيل.
قم بالإنشاء باستخدام Tasks.recurringWithPersistentSchedule(..)
.
يتم استخدام مصطلح المهمة لمرة واحدة للمهام التي لها وقت تنفيذ واحد. بالإضافة إلى تشفير البيانات في instanceId
مثيل تنفيذ المهمة، من الممكن تخزين بيانات ثنائية عشوائية في حقل منفصل لاستخدامها في وقت التنفيذ. افتراضيًا، يتم استخدام تسلسل Java لتنظيم/إلغاء تنظيم البيانات.
قم بالإنشاء باستخدام Tasks.oneTime(..)
.
بالنسبة للمهام التي لا تناسب الفئات المذكورة أعلاه، من الممكن تخصيص سلوك المهام بالكامل باستخدام Tasks.custom(..)
.
قد تكون حالات الاستخدام:
أثناء التنفيذ، يقوم المجدول بتحديث وقت نبضات تنفيذ المهمة بانتظام. إذا تم وضع علامة على التنفيذ على أنه قيد التنفيذ، لكنه لا يتلقى تحديثات لوقت نبضات القلب، فسيتم اعتباره تنفيذًا ميتًا بعد الوقت X. قد يحدث ذلك على سبيل المثال إذا خرج JVM الذي يقوم بتشغيل المجدول فجأة.
عند العثور على عملية إعدام ميتة، يتم استشارة Task
لمعرفة ما يجب القيام به. تتم عادةً إعادة جدولة RecurringTask
الميتة إلى now()
.
في حين أن db-scheduler كان يستهدف في البداية حالات استخدام ذات إنتاجية منخفضة إلى متوسطة، فإنه يتعامل مع حالات الاستخدام ذات إنتاجية عالية (1000+ عملية تنفيذ/ثانية) بشكل جيد جدًا نظرًا لحقيقة أن نموذج البيانات الخاص به بسيط للغاية، ويتكون من جدول واحد من عمليات الإعدام. لفهم كيفية أدائها، من المفيد النظر في عبارات SQL التي يتم تشغيلها لكل دفعة من عمليات التنفيذ.
ستقوم إستراتيجية الاستقصاء الأصلية والافتراضية، fetch-and-lock-on-execute
، بما يلي:
select
دفعة من عمليات الإعدام المستحقةupdate
التنفيذ إلى picked=true
لمثيل المجدول هذا. قد يغيب بسبب الجداول الزمنية المتنافسة.update
السجل أو delete
وفقًا للمعالجات.في المجموع لكل دفعة: 1 حدد، 2 * تحديثات حجم الدُفعة (باستثناء الأخطاء)
في الإصدار 10، تمت إضافة استراتيجية اقتراع جديدة ( lock-and-fetch
). إنه يستخدم حقيقة أن معظم قواعد البيانات لديها الآن دعم لـ SKIP LOCKED
في SELECT FOR UPDATE
(راجع مدونة 2ndquadrant). باستخدام مثل هذه الإستراتيجية، من الممكن جلب عمليات التنفيذ مقفلة مسبقًا، وبالتالي الحصول على عبارة واحدة أقل:
select for update .. skip locked
مجموعة من عمليات الإعدام المستحقة. سيتم اختيار هذه العناصر بالفعل بواسطة مثيل المجدول.update
السجل أو delete
وفقًا للمعالجات.في المجموع لكل دفعة: 1 تحديد وتحديث، 1 * تحديثات حجم الدُفعة (بدون أخطاء)
للحصول على فكرة عما يمكن توقعه من db-scheduler، راجع نتائج الاختبارات التي تم إجراؤها في GCP أدناه. تم إجراء الاختبارات باستخدام عدد قليل من التكوينات المختلفة، ولكن كل منها يستخدم 4 مثيلات جدولة متنافسة تعمل على أجهزة افتراضية منفصلة. TPS هو تقريبا. المعاملات في الثانية كما هو موضح في Google Cloud Platform.
جلب الإنتاجية (ex/s) | جلب TPS (تقديرات) | قفل وجلب الإنتاجية (ex/s) | قفل وجلب TPS (تقديرات) | |
---|---|---|---|---|
بوستجرس 4 كور 25 جيجا رام، 4xVMs (2 كور) | ||||
20 خيطًا، السفلي 4.0، العلوي 20.0 | 2000 | 9000 | 10600 | 11500 |
100 خيط، أقل 2.0، أعلى 6.0 | 2560 | 11000 | 11200 | 11200 |
بوستجرس 8 كور 50 جيجا رام، 4xVMs (4 كور) | ||||
50 خيطًا، السفلي: 0.5، العلوي: 4.0 | 4000 | 22000 | 11840 | 10300 |
ملاحظات لهذه الاختبارات:
fetch-and-lock-on-execute
lock-and-fetch
في الوقت الحالي، يتم تنفيذ استراتيجية lock-and-fetch
للاستقصاء فقط لـ Postgres. نرحب بالمساهمات التي تضيف الدعم لمزيد من قواعد البيانات.
هناك عدد من المستخدمين الذين يستخدمون db-scheduler لحالات الاستخدام ذات الإنتاجية العالية. انظر على سبيل المثال:
لا توجد ضمانات بأنه سيتم تنفيذ جميع اللحظات الموجودة في جدول زمني لمهمة RecurringTask
. تتم مراجعة Schedule
بعد انتهاء تنفيذ المهمة السابقة، وسيتم تحديد أقرب وقت في المستقبل لوقت التنفيذ التالي. يمكن إضافة نوع جديد من المهام في المستقبل لتوفير هذه الوظيفة.
سيتم تشغيل الأساليب الموجودة على SchedulerClient
( schedule
، cancel
، reschedule
) باستخدام Connection
جديد من DataSource
المقدم. لكي يكون الإجراء جزءًا من معاملة، يجب الاهتمام به بواسطة DataSource
المقدم، على سبيل المثال باستخدام شيء مثل Spring's TransactionAwareDataSourceProxy
.
حاليًا، تعتمد دقة جدولة db على pollingInterval
(العشرات الافتراضية) التي تحدد عدد مرات البحث في الجدول عن عمليات التنفيذ الواجبة. إذا كنت تعرف ما تفعله، فقد يُطلب من المجدول في وقت التشغيل "البحث مبكرًا" عبر scheduler.triggerCheckForDueExecutions()
. (راجع أيضًا enableImmediateExecution()
في Builder
)
راجع الإصدارات للاطلاع على ملاحظات الإصدار.
الترقية إلى 15.x
priority
العمود والفهرس priority_execution_time_idx
إلى مخطط قاعدة البيانات. راجع تعريفات الجدول لـ postgresql أو Oracle أو MySQL. وفي مرحلة ما، سيصبح هذا العمود إلزاميًا. سيتم توضيح ذلك في ملاحظات الإصدار/الترقية المستقبلية.الترقية إلى 8.x
boolean isDeterministic()
للإشارة إلى ما إذا كانت ستنتج دائمًا نفس اللحظات أم لا.الترقية إلى 4.x
consecutive_failures
إلى مخطط قاعدة البيانات. راجع تعريفات الجدول لـ postgresql أو Oracle أو MySQL. يتم التعامل مع null
على أنها 0، لذا لا داعي لتحديث السجلات الموجودة.الترقية إلى 3.x
Tasks
الترقية إلى 2.x
task_data
إلى مخطط قاعدة البيانات. راجع تعريفات الجدول لـ postgresql أو Oracle أو MySQL. المتطلبات الأساسية
اتبع الخطوات التالية:
استنساخ المستودع.
git clone https://github.com/kagkarlsson/db-scheduler
cd db-scheduler
البناء باستخدام Maven (تخطي الاختبارات بإضافة -DskipTests=true
)
mvn package
المواصفات الموصى بها
واجه بعض المستخدمين حالات فشل متقطعة في الاختبار عند التشغيل على أجهزة افتراضية أحادية النواة. لذلك، يوصى باستخدام الحد الأدنى من:
db-scheduler
عندما يكون هناك Quartz
؟ الهدف من db-scheduler
هو أن يكون غير جراحي وسهل الاستخدام، ولكنه لا يزال يحل مشكلة الاستمرارية ومشكلة تنسيق المجموعة. لقد كان يستهدف في الأصل التطبيقات ذات مخططات قاعدة البيانات المتواضعة، والتي قد تبدو إضافة 11 جدولًا إليها مبالغة بعض الشيء. التحديث: أيضًا، اعتبارًا من الآن (2024)، لا يبدو أن الكوارتز تتم صيانته بشكل نشط أيضًا.
قبلة. إنه النوع الأكثر شيوعًا لتطبيقات الحالة المشتركة.
يرجى إنشاء مشكلة مع طلب الميزة ويمكننا مناقشتها هناك. إذا كنت غير صبور (أو تشعر بالرغبة في المساهمة)، فإن طلبات السحب هي موضع ترحيب كبير :)
نعم. يتم استخدامه في الإنتاج في عدد من الشركات، ويعمل بسلاسة حتى الآن.