0

这是一个更一般的设计问题,关于如何为 akka 演员建模。这种情况在一个非常简单的例子中得到了解释,我想得到一个关于可能性和方法以及它们的优缺点的更一般的答案。

有一个用户对象应该做一些事情。可以说:doSomethingWithUser(user: User)。假设用户有avatar: Option[String]一个 url 属性。如果存在,则应在运行该doSomethingWithUser方法之前抓取 url 后面的实际图像。说 Akka,我会创建一个DoSomethingWithUserActor可以接收两条消息的 Actor:

case class NewUser(user: User)
case class NewUserWithImage(user: User, imageData: Array[Byte])

FetchImageActor抓取图像数据被实现为一个可以处理一条消息的 Actor :

case class FetchImage(url: String)

并产生一条消息:

case class GotImage(imageData: Array[Byte])

是根MainActor参与者,只接收一条消息NewUser,处理方式如下:

def receive {
  case newUser: NewUser => {
    newUser.avatar match {
      case Some(avatar) => {
        // here I would like to send a message to the FetchImageActor, 
        // wait for the response (GotImage) and once it's there send a
        // NewUserWithImage message to the DoSomethingWithUser actor.
        //
        // How can this be done?
        // Is it a good idea to use a Future here, and if so, how can this
        // be done?
        //
        // pseudocode:
        val gotImage: GotImage = // get it somehow
        doSomethingWithUserActor ! NewUserWithImage(newUser.user, gotImage.imageData)
      }
      case _ => doSomethingWithUserActor forward NewUser(newUser.user)
  }
}

DoSomethingWithUserActor处理消息NewUser和. NewUserWithImage也许是这样的:

def receive {
  case newUser: NewUser => doSomethingWithUser(newUser.user)
  case newUserWithImage: NewUserWithImage => {
    doSomethingWithImage(newUserWithImage.imageData)
    doSomethingWithUser(newUser.user)
  }
}

private def doSomethingWithUser(user: User) = { ... }
private def doSomethingWithImage(imageData: Array[Byte]) = { ... }

首先,我不知道如何在用户有头像的情况下进行异步调用,其次,我不知道通常以这种方式处理这个问题是否是一种好方法。

另一种方法可能是将 NewUser 消息转发到 FetchImageActor。这个actor然后检查用户是否设置了头像属性,如果是,它获取图像并将NewUserWithImage消息发送回MainActor,MainActor将此消息转发给DoSomethingWithUserActor,然后它实际上对包含的用户对象和图像做一些事情数据。我想,这会很糟糕,因为 FetchImageActor 需要有关用户的知识,但它仅用于获取图像。那是两个不同的独立方面,不应混为一谈。在这种情况下,FetchImage消息对象也需要用户属性(我不喜欢前面描述的)。

解决这个问题的正确或“好”策略是什么?

4

2 回答 2

1
  1. 如果您需要互连参与者,您可能会发现SynapseGrid库很有用。

  2. 对于异步调用Future是通常的方法。

    val gotImageFuture = new Future { fetchImageActor ? FetchImage(avatar) }
    gotImageFuture.onSuccess( (gotImage: GotImage) =>
      doSomethingWithUserActor ! NewUserWithImage(newUser.user, gotImage.imageData)
    )
    
  3. 如果您添加相关标记(或者User至少是url)来获取消息:

    GotImage(user:User, imageData: Array[Byte])
    FetchImage(url:String, user:User)
    

    那么你可以简单地使用fire-forget。主角将简单地处理GotImage

    ...
        case GotImage(user, imageData) => 
          doSomethingWithUserActor ! NewUserWithImage(user, imageData)
    

    这种方法是畅通无阻的。

PS一个小建议:

case class NewUser(user: User, imageData:Option[Array[Byte]])

可以使处理更容易一些。

于 2013-09-18T07:39:55.037 回答
1

一种对您的用例非常有用的模式是使用 ask-pipeTo。

您使用 ask 模式为您的请求获取未来,然后(可选)对结果进行一些转换(下面的示例结合了几个响应,您也可以在未来调用 map)并使用 pipeTo 模式发送结果未来给不同的演员。这与 Arseniy 在第 2 点中建议的非常相似,实际上 pipeTo 在未来使用 onSuccess 进行注册。

这是来自优秀的akka 文档的示例:

import akka.pattern.{ ask, pipe }
import system.dispatcher // The ExecutionContext that will be used
case class Result(x: Int, s: String, d: Double)
case object Request

implicit val timeout = Timeout(5 seconds) // needed for `?` below

val f: Future[Result] =
  for {
    x ← ask(actorA, Request).mapTo[Int] // call pattern directly
    s ← (actorB ask Request).mapTo[String] // call by implicit conversion
    d ← (actorC ? Request).mapTo[Double] // call by symbolic name
  } yield Result(x, s, d)

f pipeTo actorD // .. or ..
pipe(f) to actorD

在这种情况下我喜欢使用的另一种模式是使用临时演员。请记住,actor 与线程不同,它们非常轻量级,并且创建它们不会造成太多开销。

你可以这样做:

val tempActor = context.actorOf(Props(classOf[DoSomethingWithUserActor], newUser.user)
fetchImageActor.tell(FetchImage(image), tempActor)

这样,DoSomethingActor 已经有了对用户的引用,通过将其设置为发送者,FetchImageActor 可以只回复消息,而您的临时演员将收到图像。尝试一下这个想法,我发现正确使用时它非常强大。

于 2013-09-18T13:46:47.227 回答