在Akka创建演员的成本是多少?

时间:2012-12-10 19:50:21

标签: performance scala akka

考虑我正在实施一个使用Akka处理传入任务的系统的场景。我有一个主要的角色接收任务并将它们分发给一些处理任务的工作者。

我的第一直觉是通过让调度员为每个传入任务创建一个actor来实现这一点。在worker actor处理任务后,它将停止。

这对我来说似乎是最干净的解决方案,因为它坚持“一个任务,一个角色”的原则。另一个解决方案是重用actor - 但这涉及清理和池管理的额外复杂性。

我知道阿卡的演员很便宜。但我想知道是否存在与重复创建和删除演员相关的固有成本。是否存在与Akka用于记录演员的数据结构相关的隐藏成本?

负载应该是每秒数十或数百个任务的量级 - 将其视为生产网络服务器,每个请求创建一个角色。

当然,正确的答案在于根据传入负载的类型对系统进行分析和微调。 但我想知道是否有人可以根据自己的经验告诉我一些事情?

稍后编辑:

我应该提供有关手头任务的更多细节:

  • 在某些时候只能运行N个活动任务。正如@drexin指出的那样 - 使用路由器很容易解决这个问题。但是,任务的执行并不是简单的运行,而是完成了类型的事情。
  • 任务可能需要其他参与者或服务的信息,因此可能需要等待并入睡。通过这样做,他们释放了一个执行槽。这个插槽可以由另一个现在有机会运行的等待角色扮演。您可以对在一个CPU上安排进程的方式进行类比。
  • 每个工作者都需要保留一些关于任务执行的状态。

注意: 我很欣赏我的问题的替代解决方案,我一定会考虑它们。但是,我还想回答关于在Akka中密集创建和删除演员的主要问题。

4 个答案:

答案 0 :(得分:19)

您不应该为每个请求创建一个actor,您应该使用路由器将消息分派给动态数量的actor。这就是路由器的用途。阅读本文档的这一部分以获取更多信息:http://doc.akka.io/docs/akka/2.0.4/scala/routing.html

编辑:

创建顶级actor(system.actorOf)是很昂贵的,因为每个顶级actor都会初始化一个错误内核,而且这些内核很昂贵。创建儿童演员(在演员{{​​1}}内)更便宜。

但我仍然建议你重新考虑一下,因为根据创作和删除演员的频率,你也会对GC施加压力。

edit2:

最重要的是,演员不是线程!因此,即使您创建了1M actor,它们也只会在池中运行尽可能多的线程。因此,根据配置中的吞吐量设置,每个actor将在线程再次释放到池之前处理n个消息。

请注意,阻止线程(包括休眠)不会将其返回到池中!

答案 1 :(得分:8)

将在创建后立即收到一条消息并在发送结果后立即死亡的演员可以被未来取代。期货比演员更轻巧。

完成后,您可以使用pipeTo接收未来结果。例如,你的演员启动计算:

def receive = {
  case t: Task => future { executeTask( t ) }.pipeTo(self)
  case r: Result => processTheResult(r)
}

其中executeTask是您的函数,Task返回Result

但是,我会通过路由器重用池中的actor,如@drexin的答案。

答案 2 :(得分:0)

我已经测试了一个由main actor创建的一些root上下文创建的10000个远程actor,与prod模块中创建单个actor的方案相同。 MBP 2.5GHz x2:

  • in main:main? root // main要求root创建一个actor
  • in main:actorOf(child)//创建一个孩子
  • in root:watch(child)//观看生命周期消息
  • root中的
  • :root?孩子//等待回应(连接检查)
  • 在孩子:孩子! root //响应(连接正常)
  • root中的
  • :root!主要//通知已创建

代码:

def start(userName: String) = {
  logger.error("HELLOOOOOOOO ")
  val n: Int = 10000
  var t0, t1: Long = 0
  t0 = System.nanoTime
  for (i <- 0 to n) {
    val msg = StartClient(userName + i)
    Await.result(rootActor ? msg, timeout.duration).asInstanceOf[ClientStarted] match {
    case succ @ ClientStarted(userName) => 
      // logger.info("[C][SUCC] Client started: " + succ)
    case _ => 
      logger.error("Terminated on waiting for response from " + i + "-th actor")
      throw new RuntimeException("[C][FAIL] Could not start client: " + msg)
    }
  }
  t1 = System.nanoTime
  logger.error("Starting of a single actor of " + n + ": " + ((t1 - t0) / 1000000.0 / n.toDouble) + " ms")
}

结果:

Starting of a single actor of 10000: 0.3642917 ms

有一条消息说“Slf4jEventHandler”在“HELOOOOOOOO”和“Starting of a single”之间启动,所以实验似乎更加真实(?)

Dispatchers是一个默认设置(PinnedDispatcher每次都会启动一个新线程),似乎所有这些东西都与Thread.start()一样,自Java 1以来很长一段时间 - 500K-1M周期左右^)

这就是我将循环内的所有代码更改为new java.lang.Thread().start()

的原因

结果:

Starting of a single actor of 10000: 0.1355219 ms

答案 3 :(得分:0)

Actor构成了出色的有限状态机,因此可以帮助您推动设计。如果每个请求只有一个actor,可以大大简化您的请求处理状态,则可以这样做。我发现,根据经验,参与者尤其擅长管理两个以上的状态。

虽然很常见,但是一个请求处理参与者从其维护的集合中引用请求状态作为其自身状态的一部分是一种常见的方法。注意,这也可以通过Akka反应流和使用扫描台来实现。