考虑我正在实施一个使用Akka处理传入任务的系统的场景。我有一个主要的角色接收任务并将它们分发给一些处理任务的工作者。
我的第一直觉是通过让调度员为每个传入任务创建一个actor来实现这一点。在worker actor处理任务后,它将停止。
这对我来说似乎是最干净的解决方案,因为它坚持“一个任务,一个角色”的原则。另一个解决方案是重用actor - 但这涉及清理和池管理的额外复杂性。
我知道阿卡的演员很便宜。但我想知道是否存在与重复创建和删除演员相关的固有成本。是否存在与Akka用于记录演员的数据结构相关的隐藏成本?
负载应该是每秒数十或数百个任务的量级 - 将其视为生产网络服务器,每个请求创建一个角色。
当然,正确的答案在于根据传入负载的类型对系统进行分析和微调。 但我想知道是否有人可以根据自己的经验告诉我一些事情?
稍后编辑:
我应该提供有关手头任务的更多细节:
注意: 我很欣赏我的问题的替代解决方案,我一定会考虑它们。但是,我还想回答关于在Akka中密集创建和删除演员的主要问题。
答案 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:
代码:
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反应流和使用扫描台来实现。