问题:
在Akka演员中积累状态的正确模式是什么?
上下文
我们说有一些服务都会返回数据。
class ServiceA extends Actor {
def receive = {
case _ => sender ! AResponse(100)
}
}
class ServiceB extends Actor {
def receive = {
case _ => sender ! BResponse("n")
}
}
// ...
我希望有一个控制/监督演员协调所有这些服务的谈话并跟踪他们的回复,然后将所有数据的回复发送回原始发件人。
class Supervisor extends Actor {
def receive = {
case "begin" => begin
case AResponse(id) => ???
case BResponse(letter) => ???
}
// end goal:
def gotEverything(id: Int, letter: String) =
originalSender ! (id, letter)
def begin = {
ServiceA ! "a"
ServiceB ! "b"
}
}
当服务响应进来时,如何将所有状态保持在一起?据我了解,如果我要将AResponse的值分配给var aResponse: Int
,那么随着收到不同的消息,var会不断变化,而且我不可能依赖{ {1}}等待var
消息时停留。
我意识到我可以使用BResponse
并且只是嵌套/平面地图ask
,但是从我读过的内容来看,这是一种糟糕的模式。没有未来,有没有办法实现这一切?
答案 0 :(得分:16)
由于不会同时从多个线程访问actor,因此您可以轻松地存储和更改所需的任何状态。例如,您可以这样做:
class Supervisor extends Actor {
private var originalSender: Option[ActorRef] = None
private var id: Option[WhateverId] = None
private var letter: Option[WhateverLetter] = None
def everythingReceived = id.isDefined && letter.isDefined
def receive = {
case "begin" =>
this.originalSender = Some(sender)
begin()
case AResponse(id) =>
this.id = Some(id)
if (everythingReceived) gotEverything()
case BResponse(letter) =>
this.letter = Some(letter)
if (everythingReceived) gotEverything()
}
// end goal:
def gotEverything(): Unit = {
originalSender.foreach(_ ! (id.get, letter.get))
originalSender = None
id = None
letter = None
}
def begin(): Unit = {
ServiceA ! "a"
ServiceB ! "b"
}
}
然而,还有更好的方法。您可以使用没有显式状态变量的actor模拟某种状态机。这是使用become()
机制完成的。
class Supervisor extends Actor {
def receive = empty
def empty: Receive = {
case "begin" =>
AService ! "a"
BService ! "b"
context become noResponses(sender)
}
def noResponses(originalSender: ActorRef): Receive = {
case AResponse(id) => context become receivedId(originalSender, id)
case BResponse(letter) => context become receivedLetter(originalSender, letter)
}
def receivedId(originalSender: ActorRef, id: WhateverId): Receive = {
case AResponse(id) => context become receivedId(originalSender, id)
case BResponse(letter) => gotEverything(originalSender, id, letter)
}
def receivedLetter(originalSender: ActorRef, letter: WhateverLetter): Receive = {
case AResponse(id) => gotEverything(originalSender, id, letter)
case BResponse(letter) => context become receivedLetter(originalSender, letter)
}
// end goal:
def gotEverything(originalSender: ActorRef, id: Int, letter: String): Unit = {
originalSender ! (id, letter)
context become empty
}
}
这可能稍微冗长一点,但它不包含显式变量;所有状态都隐含在Receive
方法的参数中,当需要更新此状态时,只需切换actor的接收函数以反映这种新状态。
请注意,上面的代码非常简单,当有许多“原始发件人”时,它将无法正常工作。在这种情况下,您必须为所有邮件添加一个ID,并使用它们来确定哪些响应属于哪个“原始发件人”状态,或者您可以为每个“原始发件人”创建多个参与者。
答案 1 :(得分:2)
我相信Akka的方式是使用actor-per-request模式。这样,每次收到请求时,都会创建一个新的actor,而不是确定哪个响应对应于什么。这非常便宜,事实上,每次你问()都会发生。
这些请求处理器(我称之为它们)通常具有简单的响应字段。并且只需要简单的空比较来查看请求是否已到达。
使用此方案,重试/失败也变得更加容易。超时也是如此。