Akka中的调度器介绍及实例

437 阅读3分钟

Akka Actor系统提供了Akka Scheduler来管理任务的定期执行。在这篇博客中,我们将看到如何使用Akka Scheduler安排任务。

依赖关系

让我们把Akka-actor的依赖关系添加到我们的项目中:

libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % "2.6.8"

单一执行调度器

单一执行调度器让我们可以推迟任务的执行。该任务将在配置好的延迟后执行。

让我们看看如何创建一个单次执行调度器。

在本教程中,我们可以创建一个简单的_Actor_ 作为:

case class Greet(to: String, by: String) 
case class Greeted(msg: String)
class Greetings extends Actor {
  override def receive: Receive = {
    case greet: Greet =>
    sender ! Greeted(s"${greet.by}: Hello, ${greet.to}")
  }
}

同时,我们需要初始化一个_ActorSystem_:

val schedulerActorSystem = ActorSystem("akka-scheduler-system")

之后,我们可以为我们的_Greeting_actor创建一个_ActorRef_:

val greeter = schedulerActorSystem.actorOf(Props(classOf[Greetings]))
val greeting = Greet("Detective","Lucifer")
schedulerActorSystem.scheduler.scheduleOnce(5.seconds, greeter, greeting)

_scheduleOnce_方法需要三个参数:

  • 持续时间(延迟),任务将在此后执行
  • 要发送消息的_角色_
  • 要发送的_消息_

在上面的例子中,调度器将在5秒后向actor_greeter_发送_greet_消息。我们需要记住为执行提供一个隐含的_ExecutionContext_。

另外,我们也可以通过使用_Runnable_接口来执行上述任务。

我们将在_run_方法中实现任务的细节,它将在配置的延迟后执行:

system.scheduler.scheduleOnce(5.seconds, new Runnable {
  override def run(): Unit = greeter ! greet
})

周期性执行

使用调度器,我们可以创建周期性任务并轻松执行。

例如,我们可以在100毫秒的初始延迟后,每秒向_Greetings_角色发送一次_问候_信息:

system.scheduler.schedule(100.millis, 1.seconds, greeter, greet)

另外,我们可以使用_Runnable_接口来创建一个周期性时间表:

system.scheduler.schedule(10.millis, 250.millis, new Runnable {
  override def run(): Unit = greeter ! greet
})

延迟的类型

Akka Scheduler提供两种类型的调度延迟:固定延迟执行和固定速率执行。

固定延时

在固定延迟执行中,后续执行之间的延迟将始终至少是给定的间隔值。下一次的执行时间只有在执行完当前的执行后才会被计算。如果正在进行的任务是一个长期运行的任务,那么下一次的执行将会延迟。因此,两个后续执行之间的时间差可能不是恒定的。

从Akka 2.6版本开始, schedule_方法被废弃了,Akka建议我们使用_scheduleWithFixedDelay来代替。

所以,我们可以重写前面的例子,使用_scheduleWithFixedDelay_:

system.scheduler.scheduleWithFixedDelay(10.millis, 250.millis, greeter, greet)

固定速率(Fixed-Rate

如果正在进行的任务需要更多的时间,固定速率执行将调整后续任务的延迟

例如,假设执行之间的预定延迟是500毫秒,而当前的执行需要200毫秒。那么下一次执行将在300毫秒(500ms-200ms)后发生。

对于创建一个固定速率的调度器,我们可以使用_scheduleAtFixedRate_方法。

system.scheduler.scheduleAtFixedRate(10.millis, 500.millis)(new Runnable {
  override def run(): Unit = greeter ! greet
})

取消一个调度程序

当我们创建一个日程表时,它会返回一个_Cancellable_实例。我们可以使用这个实例来取消一个活动的调度器。

val schedulerInstance:Cancellable = system.scheduler.schedule(
  100.millis, 1.seconds, greeter, greet)
schedulerInstance.cancel()

_代理人_定时器

如果调度是 在一个_Actor_ 中完成的 _,Akka建议使用_Actor Timer_而不是_Scheduler。当actor重新启动时,_Actor Timer_将负责调度工作。

通过使用_Scheduler_,消息的生命周期会很难管理。我们可以通过实现_akka.actor.Timer_特性来创建一个_Timer_。

class TimerActor(replyTo: ActorRef) extends Actor with Timers {
  override def preStart(): Unit = {
    timers.startPeriodicTimer(PeriodicTimerKey, PeriodicTick, 200.millis)
    super.preStart()
  }
}

调度器的准确性

Akka Scheduler是为消息的高吞吐量而设计的。然而,它不是很精确。不能保证任务会在我们提供的准确时间执行,所以调度器可能会在预定延迟的几毫秒后执行。

总结

在这篇博客中,我们看到了如何使用Akka Schedulers为未来的时间安排一些任务。