Akka Actors 应该做真正的处理任务吗?

Should Akka Actors do real processing tasks?

我正在编写一个应用程序来读取相对较大的文本文件、验证和转换数据(文本文件中的每一行都是一个单独的项目,大约有 100M items/file)并创建某种输出。已经存在一个多线程 Java 应用程序(在 Reading/Processing/Persisting 任务之间使用 BlockingQueue),但我想实现一个执行相同操作的 Scala 应用程序。

Akka 似乎是构建并发应用程序的一个非常受欢迎的选择。不幸的是,由于演员的异步性质,我仍然不明白单个演员能做什么或不能做什么,例如如果我可以使用演员作为传统工人进行某种计算。

一些文档说 Actors 永远不应该阻塞,我明白为什么。但是给定的阻塞代码示例总是只提到阻塞 file/network IO.. 让演员等待很短时间的事情,这当然是一件坏事。

但是如果 actor "blocking" 是因为它实际上做了一些有用的事情而不是等待呢?在我的例子中,单个 line/item 文本的处理和转换需要 80 毫秒,这是相当长的时间(纯处理,不涉及 IO)。这项工作可以由 actor 直接完成还是我应该使用 Future 代替(但是,如果无论如何我都必须使用 Futures,为什么首先要使用 Akka..)?

Akka 文档和示例表明,actors 可以直接完成工作。但似乎作者只做了非常简单的工作(例如在 String 上调用过滤器或递增计数器,仅此而已)。我不知道他们这样做是为了使文档简单明了,还是因为你真的不应该在一个 actor 中做更多的事情。

你会如何为我的用例设计一个基于 Akka 的应用程序(读取文本文件,处理每一行都需要相当长的时间,最终保存结果)?或者这是某种不适合 Akka 的问题?

看演员的类型

我使用这个经验法则:如果你不需要与这个演员交谈并且这个演员没有任何其他职责,那么可以阻止它做实际工作。您可以将其视为 Future,这就是我所说的 "worker"。

如果你阻塞一个不是叶节点(worker)的actor,即工作分配器,那么整个系统会变慢。

有一些模式涉及每个请求模型的工作 pulling/pushing 或参与者。这些中的任何一个都可能适合您的应用程序。你可以有一个经理为每件工作创建一个演员,当工作完成时演员将结果发送回经理并死亡。你也可以让一个演员活着,并要求那个演员做更多的工作。您还可以组合 actors 和 Futures。

有时,如果您的处理更复杂并且涉及多个阶段,您希望能够与工作人员交谈。在那种情况下,工作人员可以将工作委托给另一个参与者或未来。

总而言之,不要阻塞 manager/work 分发参与者。如果不会减慢您的系统速度,可以阻止工作人员。

免责声明:阻塞是指做实际工作,而不仅仅是忙着等待,这是永远不会好的。

执行 100 毫秒的计算对于 actor 来说是可以的。但是,您需要确保正确处理背压。一种方法是使用 work-pulling pattern,其中 CPU 绑定的 actors 在准备就绪时请求新工作,而不是在消息中接收新工作项。

也就是说,您的问题描述听起来像是一个处理管道,可能会受益于使用更高级别的抽象,例如 akka streams。基本上,生成要处理的文件名流,然后使用映射等转换来获得所需的结果。我在生产中有这样的东西,听起来与您的问题描述非常相似,并且如果各个处理块使用的数据不是太大,它工作得很好。

当然一个stream也会物化到多个actors。但是高级接口将更加类型安全并且更容易推理。