阿卡接口
这是一系列关于同步客户端集成与异步系统(第五柱1, 2, 3, 4 )。 在这里,我们将看到如何管理参与者的生命周期,以便我们的服务可以有效地使用可用资源。
生命周期
角色,线程,对象,资源……它们在生命周期中都具有不同的状态。 其中一些状态是内部的,它们在受到外部刺激时会影响实体的行为。 但是,状态转换应由外部实体处理:创建,错误恢复和处置。
演员池
很久以前,依赖项注入向我们表明,对象不应该负责创建其依赖项。 将这些问题外部化到工厂中可使代码更具可测试性,松散耦合和可读性。 无论使用DI框架还是穷人的依赖注入,我们都需要不同的创建策略。 Guice Scopes是一个很好的示例,它说明了如何为每个应用程序创建一个实例(@Singleton)或为每个范围创建一个实例(@SessionScoped和@RequestScoped)。
通常,系统资源稀缺,创建成本高或负载过重。 这意味着我们既负担不起Singleton的负担,也负担不起Spring中的Prototype之类的无限创造策略。 一个很好的例子是线程池 。
使用工作线程可以最大程度地减少线程创建所带来的开销。 线程对象使用大量内存,在大型应用程序中,分配和取消分配许多线程对象会产生大量内存管理开销。
Akka Actor非常轻巧。 如果我们知道可以Swift处理掉它们,那么我们可以负担得起创造数百万个它们的费用。 但是,用例不是这种情况:
如您所见,有限状态机(FSM)参与者将等待,直到Items服务完成删除项目为止。 重要的是要注意,参与者正在等待,但没有阻止。 Actor附加到内部具有线程池的Dispatcher 。 这些线程池具有有限数量的线程。 阻止其中一个参与者将意味着很快耗尽线程。 在此特定示例中,我们要限制参与者的数量,而不是线程。 也许是矫kill过正,但是本系列文章的重点主要是教育性的。 让我们看一下具有适当抽象级别的服务图。
Akka HTTP
Akka HTTP是一个基于Spray的库,用于创建HTTP集成层。 正如您在我们的代码中看到的那样,Akka HTTP提供了非常强大且易于阅读的路由DSL:
val deleteItem = get {
path("item" / JavaUUID) { itemId =>
onComplete(deleteItem(itemId)) {
case Success(Result(Right(_))) =>
complete(StatusCodes.OK)
case Success(Result(Left(Timeout(errorMessage)))) =>
complete(StatusCodes.GatewayTimeout)
// many other cases we won't cover here
}
}
}
private def deleteItem(itemId: UUID)= itemCoordinator ask ItemReported(itemId)
在此片段中,我想强调一下问与说模式 。 只是为了澄清,这是Akka模式,与惊人的“告诉,不要问”原理无关 。 告诉模式涉及触发消息并忘记响应。 在这种方法中,信息沿一个方向流动。 但是对于我们的情况,我们对Ask模式感兴趣。 当我们通过询问模式向演员发送消息时,将来会以超时为最终响应,从而创建了未来。 如文档所述:
与ask一样,使用ask将向接收方Actor发送一条消息,并且接收方actor必须使用sender()进行回复! 进行回复以完成返回的Future值。 ask操作涉及创建一个内部actor来处理此回复,该回复需要有一个超时时间,之后该超时时间必须销毁以防止泄漏资源。 请参阅下文。
First actor ->
val future = myActor.ask("hello")(5 seconds)
Second actor ->
sender() ! result
协调演员
让我们看一下Item Coordinator
代码:
override def receive: Receive = {
case itemReported: ItemReported =>
if (actorPool.isEmpty)
sender() ! Result(Left("No actors available"))
else {
actorPool.get() forward ItemReported
}
case Result(_) =>
sender() ! FlushItemFSM
actorPool.putBack(sender())
}
sender
方法公开发送消息的ActorRef
。 如果角色池已用尽,我们需要与Route角色进行通信,我们将无法满足该请求。 否则,我们将检索池中的参与者之一,然后转发收到的消息。 转发允许我们保留原始发件人。 FSM将其工作结果传达给正在等待由问号模式限制的Route角色。
FSM需要协调器的引用,因为它负责管理FSM的生命周期。 协调器向FSM发送一条消息,告知其刷新其内部状态。 同时将FSM放回池中。
创造演员
请记住,FSM如何通知其工作已完成:
private def finishWorkWith(message: Any) = {
replyTo ! message
coordinator ! message
goto(Idle)
}
replyTo
是在接收第一条ItemReported
消息时使用sender()
初始化的变量。 请记住,邮件是转发的,因此发件人是Route Actor。 coordinator
是在创建角色时已注入的ActorRef。 我们为此目的使用穷人的依赖注入。 让我们看一下actor工厂的定义:
lazy val itemFSMFactory: (ActorContext, ActorRef) => ActorRef =
(context, self) => {
val itemFSM = ItemFSM.props(itemReportedProducer, itemDeletedBus, coordinator = self)
context.actorOf(itemFSM)
}
创建Actor Pool时,该工厂将被调用n次(具体取决于池的大小)。
监督演员
我们的协调员负责资源库以及FSM以预期方式完成时的操作。 但是,我们没有谈论演员失败时会发生什么。 Akka有一个内置的机制来处理故障,称为监督 。 有不同的恢复策略,我们将采用“ Restart
策略:
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 15 seconds) {
case e: Exception =>
actorPool.putBack(sender())
SupervisorStrategy.Restart
}
正如Restart
策略文档所述:
丢弃旧的Actor实例,并用新实例替换它,然后恢复消息处理。
该策略已在协调器中定义。 我们如何指定该协调员是要监督的FSM的父级? 记住我们是如何创建FSM的:
context.actorOf(itemFSM)
我们来看一下actorOf的actorOf
:
创建新演员作为此上下文的子演员
在协调器范围内调用itemFSMFactory
。 每个参与者都有一个上下文,其中包括发送者,自身和不同的元对象。 使用协调者的上下文创建一个actor会将那些actor绑定为父子。
摘要
多亏了Akka的构造和模式,以及Scala的功能和异步优势,我们可以轻松编写协调代码。 在下一篇文章中,我们将介绍如何测试Akka。
翻译自: https://www.javacodegeeks.com/2016/06/coordination-in-akka.html
阿卡接口