Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Quartz Extension and utilities for cron-style scheduling in Akka

License

NotificationsYou must be signed in to change notification settings

enragedginger/akka-quartz-scheduler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status

Quartz Extension and utilities for true scheduling in Akka 2.6.x.

Current release is built for Scala 2.13.x and Akka 2.6.x and is available on Maven Central. If you would like supportfor a different combination of Scala and Akka, simply post your request on the issues page (as well as a reason as towhy the currently available versions won't work for you. I'm always curious about these things).

Why Akka and Quartz?

Note that this is named and targeted as akka-quartz-scheduler for a reason: it isnot a complete port of Quartz.Rather, we utilize the concepts of Quartz' scheduling system to provide a more robust and reliable scheduling componentto Akka than the one already available.

The goal here is to provide Akka with a scheduling system that is closer to what one would expect for Cron type jobs –set up long-running ActorSystems that can have certain events kicked off by Quartz.

There aren't currently any plans on having anything to do with the distributed transaction, persistence,clustering or any other nonsense anytime soon. This is for cron-ey type timing and scheduling.

There is the ability in Quartz to pass JobDataMaps around that accrue mutable state across job ticks;we currently do NOT support that to enforce integrity, but may expose a deeper actor structure later thatgives some ability to work around that, if need arises.

Why Not Use $OtherComparableTool Instead?

  1. What's wrong with Akka's existing Scheduler?As Viktor Klang points out, 'Perhaps the name "Scheduler" was unfortunate,"Deferer" is probably more indicative of what it does.'

    The Akka Scheduler is designed to setup events that happen based on durations from the current moment:You can say "fire this job in 15 minutes, every 30 minutes thereafter" but not "fire a job every day at 3pm".

    Furthermore, Akka's default scheduler is executed around aHashedWheelTimer –a potential precision loss for jobs, as it does not provide strong guarantees on the timeliness of execution.

  2. Why not just use the Quartz component inAkka's Camel Extension?

    1. To begin with, Akka's Camel extension wasnot available in Akka 2.0.x, only in 2.1+
    2. Camel brings with it a whole architecture change (Consumers,Producers, etc) and is not exactly "lightweight" to plug in if all you want is Quartz support.
    3. We wanted to bring the scheduling concept of Quartz into Akka as cleanly as possible with native configuration integration and a lightweight feel.
  3. What about that otherakka-quartz component up on GitHub?

    The interface to this aforementionedakka-quartz component is via Actors - one creates an instance of an Actor thathas its own Quartz Scheduler underneath it, and sends messages to that Actor to schedule jobs. Because it is an Actorwhich provides no "Singleton"-like guarantee, it becomes too easy for users to accidentally spin up multiple schedulerinstances, each of which is backed by its own threadpool.Instead, withakka-quartz-scheduler we use Akka's Extension system which providesa plugin model – we guarantee only one Quartz Scheduler isever spun up perActorSystem. This meanswe will never create anything but one single Thread Pool which you have control over the size of, forany givenActorSystem.

Finally, a common failure of the above listed alternatives is that configuration of things like a repeating scheduleshould be separated from code in a configuration file which an operations team (not the developers) cancontrol. Thus,akka-quartz-scheduler only allows specifying the following in code: the name of a job, what actor to sendthe tick to, and the message to send on a tick. The configuration of how frequently to 'tick' on a schedule isexternalised to the Akka configuration file; when a schedule request is made its name is matched up with an entryin the config which specifies the rules the actual scheduling should follow.

Thus, development can outline the skeleton of repeating jobs in their code, specifying only what to do WHEN a 'tick' ofthe schedule fires. Then, operations has complete control over how often a job runs and what rules it follows to determinethe schedule of firing.

This, among other things, prevents accidental mistakes such as changing a schedule in development for testing. Achange of that sort is fixable without Operations needing to require a recompilation of source code.

TODO

  • investigate supporting listeners, with actor hookarounds.
  • misfires and recovery model - play nice with supervision, deathwatch, etc[docs page 23 - very close to supervision strategy]

Usage

See CHANGELOG.md for a list of changes by release.

Usage of theakka-quartz-scheduler component first requires including the necessary dependency in your SBT project:

// For Akka 2.6.x and Akka Typed Actors 2.6.x and Scala 2.12.x, 2.13.x, 3.1.xlibraryDependencies+="com.enragedginger"%%"akka-quartz-scheduler"%"1.9.3-akka-2.6.x"// For Akka 2.6.x and Scala 2.12.x, 2.13.xlibraryDependencies+="com.enragedginger"%%"akka-quartz-scheduler"%"1.8.5-akka-2.6.x"// For Akka 2.5.x and Scala 2.11.x, 2.12.x, 2.13.xlibraryDependencies+="com.enragedginger"%%"akka-quartz-scheduler"%"1.8.1-akka-2.5.x"// For Akka 2.4.x and Scala 2.11.x, 2.12.xlibraryDependencies+="com.enragedginger"%%"akka-quartz-scheduler"%"1.6.0-akka-2.4.x"

Note: As of Akka 2.6,Scala 2.11 is no longer supported.If you wish to use newer versions of akka-quartz-scheduler, but you're stuck on Scala 2.11 for some reason, please open an issue / PR and we'll see what we can do.

//Older versions of the artifact for those that require pre Akka 2.3 or pre Scala 2.11//If you would like a current version of the artifact to be published for your required version//of Akka and Scala, simply file on issue on the project page.resolvers+="Typesafe Repository" at"http://repo.typesafe.com/typesafe/releases/"// For Akka 2.0.xlibraryDependencies+="com.typesafe.akka"%%"akka-quartz-scheduler"%"1.2.0-akka-2.0.x"// For Akka 2.1.xlibraryDependencies+="com.typesafe.akka"%%"akka-quartz-scheduler"%"1.2.0-akka-2.1.x"// For Akka 2.2.xlibraryDependencies+="com.typesafe.akka"%%"akka-quartz-scheduler"%"1.2.0-akka-2.2.x"// For Akka 2.3.x and Scala 2.10.4libraryDependencies+="com.typesafe.akka"%"akka-quartz-scheduler_2.10"%"1.4.0-akka-2.3.x"

Note that the version name includes the Akka revision (Previous releases included the Akka release in the artifact name, which broken Maven).

Akka Typed Actor (2.6.x)

If you use newer Akka Actor version then, from within your Akka project you can create and access a Typed Scheduler:

// Akka Typed Actors sample.importcom.typesafe.akka.extension.quartz.QuartzSchedulerTypedExtensionvalscheduler=QuartzSchedulerTypedExtension(typedSystem)

WheretypedSystem represents an instance of an Akka TypedActorSystem[-T] – note thatQuartzSchedulerTypedExtensionis scoped to thatActorSystem[-T] and there will only ever be one instance of it perActorSystem[-T].

The primary external method on thescheduler instance isschedule, used for scheduling a job:

defscheduleTyped[T](name:String,receiver:ActorRef[T],msg:T): java.util.Date

OR

defscheduleTyped[T](name:String,receiver:ActorRef[T],msg:T,startDate:Option[Date]): java.util.Date

The arguments to schedule are:

  • name: AString identifying the name of this schedule. Thismust match a schedule present in the configuration
  • receiver: A typedActorRef[T], who will be sentmsg each time the schedule fires
  • msg: An instance ofA, representing a message instance which will be sent toreceiver each time the schedule fires
  • startDate: An optionalDate, for postponed start of a job. Defaults to now.

InvokingscheduleTyped[A] returns an instance ofjava.util.Date, representing the first time the newly setup schedulewill fire.

Each time the Quartz schedule trigger fires, Quartz will send a copy ofmsg to yourreceiver actor.

Note that you can use the same logic for scheduling Quartz with Akka Typed Actor as the older version.

Akka Actor (2.5.x)

If you use old Akka Actor version then, from within your Akka project you can create and access a Scheduler:

// Akka Actors sample.importcom.typesafe.akka.extension.quartz.QuartzSchedulerExtensionvalscheduler=QuartzSchedulerExtension(system)

Wheresystem represents an instance of an AkkaActorSystem – note thatQuartzSchedulerExtension is scopedto thatActorSystem and there will only ever be one instance of it perActorSystem.

The primary external method on thescheduler instance isschedule, used for scheduling a job:

defschedule(name:String,receiver:ActorRef,msg:AnyRef,startDate:Option[Date]): java.util.Date

OR

defschedule(name:String,receiver:ActorSelection,msg:AnyRef,startDate:Option[Date]): java.util.Date

The arguments to schedule are:

  • name: AString identifying the name of this schedule. Thismust match a schedule present in the configuration
  • receiver: AnActorRef orActorSelection, who will be sentmsg each time the schedule fires
  • msg: AnAnyRef, representing a message instance which will be sent toreceiver each time the schedule fires
  • startDate: An optionalDate, for postponed start of a job. Defaults to now.

Invokingschedule returns an instance ofjava.util.Date, representing the first time the newly setup schedulewill fire.

Each time the Quartz schedule trigger fires, Quartz will send a copy ofmsg to yourreceiver actor.

Here is an example, using a schedule calledEvery30Seconds, which sends aTick message to aCleanupActor (which does hand wavy cleanup things):

caseobjectTickvalcleaner= system.actorOf(Props[CleanupActor])QuartzSchedulerExtension(system).schedule("Every30Seconds", cleaner,Tick)

Where theTick message is handled normally inside the Actor's message loop. If one wanted to ensure that schedulemessages were dealt with more immediately than "normal" actor messages, they could utilizePriority Mailboxes.

The details on the configuration of a job is outlined below in the section 'Schedule Configuration'.

Returning scheduled Fire Time

There are situations where the fire time is helpful. For example, when an error occurs, we know which job trigger isbeing processed and can be recovered.

Here is an example using the case classMessageRequireFireTime wrapping theTick message. This will send aMessageWithFireTime(Tick, previousFireTime, scheduledFireTime, nextFireTime) message to aWorkerActor:

caseobjectTickvalworker= system.actorOf(Props[WorkerActor])QuartzSchedulerExtension(system).schedule("Every30Seconds", worker,MessageRequireFireTime(Tick))

Configuration of Quartz Scheduler

All configuration ofakka-quartz-scheduler is done inside of the Akka configuration file in anakka.quartz configblock. Like Akka's configuration file, this follows theHOCON Configuration Format.Thus, any entries specified asfoo.bar.baz = x can also be expressed asfoo { bar { baz = x } }.

At the top level of the configuration, optional values may be set which override the defaults for:

  • defaultTimezone -[String] represents the timezone to configure all jobs to run in.DEFAULTS TO UTCmust be parseable byjava.util.TimeZone.getTimeZone(),
  • threadPool.threadCount -[Int] The number of threads to allocate to the internal Quartz threadpool.DEFAULTS TO 1 - you may wish to up this number if you have a large number of schedulesbeing executed. With only 1 thread, each trigger will queue up and you may not get responsive schedule notifications.
  • threadPool.threadPriority -[Int] A number, between 1 (Lowest priority) and 10 (Highest priority), specifying the priority to assign to Quartz' threadsDEFAULTS TO 5
  • threadPool.daemonThreads -[Boolean] A boolean indicating whether the threads Quartz creates should execute asDaemon Threads or not.DEFAULTS TO TRUE

There are two 'primary' sub-blocks of theakka.quartz configuration, which areschedules andcalendars.

Schedule Configuration

Schedules are our abstraction over Quartz' Job & Trigger concepts. They allow you to define a named schedule,which will fire a schedule event (sending a message to an actor, as specified in code) each time the Quartz trigger fires.

Currently, you can only specify "Cron" schedules, which followQuartz' CronExpression Language,which is designed to match the standard Unix cron syntax with a few nice additions.

The schedule name in the configuration will be used to match it up with a requested job whenschedule is invoked;case does not matter as the "Is there a matching job?" configuration lookup is case insensitive.

The configuration block for schedules is inakka.quartz.schedules, with sub-entries being specified inside of a namedblock, such that the configuration for a schedule namedEvery30Seconds would have its configuration values specified insidethe configuration blockakka.quartz.schedules.Every30Seconds.

The entries that can be placed inside of a schedule configuration are:

  • expression -[String][required] a validQuartz' CronExpression,which describes when this job should trigger. e.g.expression = "*/30 * * ? * *" would fire every 30 seconds, on every date (however,the firing schedule created by this expression is modified by thecalendars variable, defined below)
  • timezone -[String][optional] the timezone in which to execute the schedule,DEFAULTS TOakka.quartz.defaultTimezone, WHICH DEFAULTS TOUTCmust be parseable byjava.util.TimeZone.getTimeZone()
  • description -[String][optional] a description of the job.DEFAULTS TO null. Mostly for human friendlinesswhen they read your configuration aka "what this schedule is for", but set in Quartz as well for if you dump the scheduler contentsfor debug.
  • calendar -[String][optional] An option String which is the name of a configured Calendar. This Calendar is appliedto this schedule as "exemptions" (Any times/dates falling in the Calendar will be excluded from the schedule firing - i.e.a Calendar that excludes all Mondays would keep a schedule configured to trigger every hour, from triggeringat all on Mondays.NOTE: In versions 1.3.x and prior, this property was "calendars" and supported a list of Strings. However, Quartz, and by proxy, this librarynever actually supported multiple calendars for one schedule. Therefore, in versions 1.4.x and beyond this property has been renamed to "calendar"and is an optional String.DEFAULTS TONone[String]

An example schedule calledEvery30Seconds which, aptly, fires off every 30 seconds:

akka {  quartz {    schedules {      Every30Seconds {        description = "A cron job that fires off every 30 seconds"        expression = "*/30 * * ? * *"        calendar = "OnlyBusinessHours"      }    }  }}

This Schedule specifies a Cron Expression which executes every 30 seconds of every day, but is modified by the calendar"OnlyBusinessHours", which excludes triggers from firing outside of between 8am and 6pm (and is detailed below).

Calendar Configuration

Calendars in theakka-quartz-scheduler mirror the concept ofQuartz' Calendars –most specifically, they allow you to specifyexclusions that override a schedule.

Calendars are configured globally, in theakka.quartz.calendars configuration block. The definition of a calendar and what it excludesis made within this block. By default, no Calendars are applied to a Schedule. Instead, you must reference a named Calendarinside thecalendars array of a Schedule's configuration, as outlined above.

The configuration block for calendars is inakka.quartz.calendars, with sub-entries being specified inside of a namedblock, such that the configuration for a calendar namedOnlyBusinessHours would have it's configuration values specified insidethe configuration blockakka.quartz.calendars.OnlyBusinessHours.

There are several types of Calendar, each with its own specific configurations. The configuration values which are common toall types of Calendar are:

  • type -[String][required] a valid type of Calendar. Currently either: Annual, Holiday, Daily, Monthly, Weekly, and Cron
  • timezone -[String][optional] the timezone in which to execute the calendar,DEFAULTS TOakka.quartz.defaultTimezone, WHICH DEFAULTS TOUTCmust be parseable byjava.util.TimeZone.getTimeZone()
  • description -[String][optional] a description of the calendar.DEFAULTS TO null. Mostly for human friendlinesswhen they read your configuration aka "what this calendar is for", but set in Quartz as well for if you dump the scheduler contentsfor debug.

Each specific Calendar type and its particular configuration entries are...

'Annual' Calendar

An annual calendar excludes specific days of a given year, e.g. bank holidays which fall on the same date every year (Christmas and Gregorian New Year's for example)This calendardoes not take year into account, and will apply to all years.

It has only one configuration entry:

  • excludeDates -[Seq[String]][required] This is a list of strings which are dates in the MM-DD format, representing which dates to exclude.For example, "Exclude Christmas and New Years" would read asexcludeDates = ["12-25", "01-01"]

An example:

WinterClosings {  type = Annual  description = "Major holiday dates that occur in the winter time every year, non-moveable (The year doesn't matter)"  excludeDates = ["12-25", "01-01"]}
'Holiday' Calendar

A holiday calendar excludes specific dates, with a fully month, day, year taken into account. It is mostly useful formoving Bank Holidays (e.g. President's Day) and Moveable Feasts (e.g. Easter, which is based on a Lunar calendar).

It has only one configuration entry:

  • excludeDates -[Seq[String]][required] This is a list of strings in the ISO-8601 date format (YYYY-MM-DD), representing which dates(with year taken into account) to exclude. For example, excluding the the next 5 years' Easter holidays would read asexcludeDates = ["2013-03-31", "2014-04-20", "2015-04-05", "2016-03-27", "2017-04-16"]

An example:

Easter {  type = Holiday  description = "The easter holiday (a moveable feast) for the next five years"  excludeDates = ["2013-03-31", "2014-04-20", "2015-04-05", "2016-03-27", "2017-04-16"]}
'Daily' Calendar

A daily calendar excludes a specified time range each day. Itmay not cross daily boundaries, and Quartz will enforce this.i.e. You cannot specify "11PM to 1AM" – to do that you'll need to specify two separate daily calendars.

Exclusions in a Daily calendar are specified in aexclude with astartTime andendTime entry. Each of these fieldsfollows a time format ofHH:MM[:SS[:mmm]] where:- HH is the hour of the specified time using military (24-hour) time, and must be in the range 0-23- MM is the minute of the specified time, and must be in the range 0-59- SS is theoptional second of the specified time, and must be in the range 0-59- mmm is theoptional millisecond of the specified time, and must be in the range 0-999

An example, which doesn't allow jobs to run between 3AM and 5AM during the PST Timezone:

HourOfTheWolf {  type = Daily  description = "A period every day in which cron jobs are quiesced, during night hours"  exclude {    startTime = "03:00"    endTime   = "05:00:00"  }  timezone = PST}
'Monthly' Calendar

A monthly calendar excludes a set of days of the month, i.e. "Don't run a job on the 1st or 15th days of the month"

It has only one configuration entry:

  • excludeDays -[Seq[Int]][required] This is a list of Ints, between 1 and 31, representing a day of the month.

An example:

FirstAndLastOfMonth {  type = Monthly  description = "A thinly veiled example to test monthly exclusions"  excludeDays = [1, 31]}
'Weekly' Calendar

A weekly calendar excludes a set of days of the week.By default, Saturday and Sunday are always excluded

The configuration entries:

  • excludeDays -[Seq[Int]][required] This is a list of Ints, between 1 and 7 – where 1 is Sunday and 7 is Saturday – representing days of the week to exclude.
  • excludeWeekends -Booleandefaults to TRUE Whether weekends should be excluded automatically by this scheduler or not. Note thatexcludeWeekends is overridenbyexcludeDays – if you specifyexcludeWeekends = false butexcludeDays includes Sunday (1) or Saturday (7), then a configuration error will be thrown.

An example, which excludes jobs from running on any Monday:

MondaysSuck {  type = Weekly  description = "Everyone, including this calendar, hates mondays as an integer"  excludeDays = [2]  excludeWeekends = false}

Note that by default,excludeWeekends would be true and thusexcludeDays would implicitly be[1, 2, 7]

'Cron' Calendar

A cron calendar excludes the set of times expressed by a givenQuartz CronExpression.

It has only one configuration entry:

  • excludeExpression -[String][required] A validQuartz CronExpression, which will beused to specify what times a jobcannot run in (the opposite of a Cron Schedule).

An example Calendar, which specifies an exclusion set of00:00 - 07:59 and18:00 - 23:59 (thereby only allowing jobs to run from08:00 - 17:59):

OnlyBusinessHours {  type = Cron  excludeExpression = "* * 0-7,18-23 ? * *"}

Dynamic create, update, deleteJobSchedule operations

TheseJobSchedule operations let you programatically manage job and job schedulingall at once.For working examples please check test section:"The Quartz Scheduling Extension with Dynamic create, update, delete JobSchedule operations"incom.typesafe.akka.extension.quartz.QuartzSchedulerFunctionalSpec

Create JobSchedule

createJobSchedule let you create a new job and schedule it all at once:

valscheduleJobName:String="myJobName_1"valmessageReceiverActor:ActorRef= myActorRefvalmessageSentToReceiver:AnyRef= myCaseClassMessagevalscheduleCronExpression:String="*/10 * * ? * *"// Will fire every ten secondstry {   scheduler.createJobSchedule(  name= scheduleJobName,   receiver= messageReceiverActor,   msg= messageSentToReceiver,   cronExpression= scheduleCronExpression)}catch {caseiae:IllegalArgumentException=> iae// Do something useful with it.}

In addition you can specify the following optional description, calendar and timezone parameters:

valscheduleJobDescriptionOpt:Option[String]=Some("Scheduled job for test purposes.")valaCalendarName:Option[String]=Some("HourOfTheWolf")valaTimeZone: java.util.TimeZone= java.util.TimeZone.getTimeZone("UTC")try {   scheduler.createJobSchedule(  name= scheduleJobName,   receiver= messageReceiverActor,   msg= messageSentToReceiver,   cronExpression= scheduleCronExpression,   description=Some(job.description),   calendar= aCalendarName,   timezone= aTimeZone  )}catch {caseiae:IllegalArgumentException=> iae// Do something useful with it.}

Update JobSchedule

updateJobSchedule has exactely the same signature as create JobSchedule but tries to performan update of an existingscheduleJobName

try {   scheduler.updateJobSchedule(  name= scheduleJobName,   receiver= messageReceiverActor,   msg= messageSentToReceiver,   cronExpression= scheduleCronExpression,   description=Some(job.description),   calendar= aCalendarName,   timezone= aTimeZone  )}catch {caseiae:IllegalArgumentException=> iae// Do something useful with it.}

Delete JobSchedule

try {if (scheduler.deleteJobSchedule(name= scheduleJobName)) {// Do something if deletion succeeded  }else {// Do something else if deletion failed  }}catch {casee:Exception=>// Take action in case an exception is thrown}

Mass operations suspendAll, resumeAll, deleteAll

These mass operations let you manage manyJobSchedules at once.For working examples please check test section:"The Quartz Scheduling Extension with Dynamic mass methods"incom.typesafe.akka.extension.quartz.QuartzSchedulerFunctionalSpec

Suspend All Schedules

Suspends (pauses) all jobs in the scheduler.

try {  scheduler.suspendAll()}catch {casee:SchedulerException=>// Take action in case an exception is thrown}

Resume All Schedules

Unpauses all paused jobs in the scheduler.

try {  scheduler.resumeAll()}catch {casee:SchedulerException=>// Take action in case an exception is thrown}

Delete All Schedules

Deletes all jobs in the scheduler without shutting down the scheduler.

try {  scheduler.deleteAll()}catch {casee:SchedulerException=>// Take action in case an exception is thrown}

About

Quartz Extension and utilities for cron-style scheduling in Akka

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors34


[8]ページ先頭

©2009-2025 Movatter.jp