我对Akka完全不熟悉。当我将以前的类方法/行为拆分成akka消息时,我很难掌握。许多示例将收到的消息显示为一行 - println(“Howdy”)。
akka专家在他们的头脑中使用什么指导来解决这个问题?我会在这里将每个步骤作为单独的消息而不是几个方法调用吗?在我的脑海中,我唯一会使用akka消息的是#1(每条消息)和#6(阻止http调用)。这将使我对每个句子的处理实际上执行了相当多的工作(但非阻塞工作)。它是否类似于我决定使用异步而不是使用异步?对我来说,只有在我有机会进行阻止操作时才会这样。
答案 0 :(得分:2)
我假设您要在actor中跟踪的状态是每个正则表达式的匹配数。
在这种情况下,如果您没有很多正则表达式,那么您的初始方法是有效的。如果你有很多,并且每个句子都经过每个表达式,你将在actor线程上执行大量的工作。这项工作在没有I / O的意义上是非阻塞的,但它实际上阻止了发送给这个actor的其他消息的进展,所以它在这个意义上是阻塞的。演员是单线程的,所以如果你有很多传入的句子,演员的邮箱就会开始增长。如果您使用无界邮箱(默认情况下),您最终将使用OOM。
一种解决方案是将正则表达式匹配发送到Future
。但是,您不能与该未来共享actor状态(每个正则表达式的匹配计数),因为(通常情况下)它将导致竞争条件。为解决这个问题,未来的结果将向您的演员发送另一条消息,其中包含需要更新的计数。
case class ProcessSentence(s: String)
case class ProcessParseResult(hits: mutable.Map[Regex,Int], s: String)
case class Publish(s: String)
class ParseActor {
val regexHits = Map("\\s+".r -> 0, "foo*".r -> 0)
def receive = {
case ProcessSentence(s) => Future(parseSentence(s, regexHits.keys)).pipeTo(self)
case ProcessParseResult(update, s) =>
// update regexHits map
if(update.values.sum > 0)
self ! Publish(s)
case Publish(s) => Future(/* send http request */)
}
def parseSentence(s: String, regexes: Seq[Regex]): Future[ProcessParseResult] =
Future{ /* match logic */}
}
答案 1 :(得分:1)
我建议使用Akka进行设计的方法是首先确定问题中的状态。
让我们从那里开始:
如果你有状态,那么一个actor可能是合适的,因为它可以帮助确保你安全地处理对actor中状态的并发访问,并且它的容错机制将帮助你的应用程序恢复,如果该状态变得腐败导致应用程序错误。
在你的情况下,一个或两个简单的计数器可以使用Java的原子整数来处理,所以我实际上建议你使用基本类而不是actor。它比使用演员要简单得多。如果你想返回未来,可以使用Java8s CompletableFuture或Scala的concurrent.Future,结果将比使用actor更简单。
所有这些都说,如果你想使用演员,设计可能不会保证多个演员,因为你只有一个状态。
作为一般规则,你的演员应该只有一个责任 - 就像良好的面向对象设计一样。虽然您可能在单个actor中接受消息,但您可能仍决定将逻辑拆分为多个类。因此,您可能拥有“RegexActor”,然后您可能会使用策略模式实现“MatchBehavior”,以便在构造时传递RegexActor。
全部,我不认为你需要建立一堆演员 - 演员给你事物之间的异步界限,但你仍然可以从演员所具有的行为中使用优秀的OO中受益。演员有一条消息 - 处理书中的行 - 但它有一些行为可以在几个不同的类中组成。我会为该行为使用基本类,并在接收消息后让actor委托给其他类。
您希望事情变得简单 - 在使用actor时会丢失类型安全性并增加代码,因此我建议您确保使用它的充分理由 - 存在于并发环境中的状态或分发