何时使用Actors和Futures?

38

我目前正在处理一个具有以下架构的Play!项目:

控制器 -> 服务(actors)-> 模型(常规案例类)

对于每个进来的请求,我们会像这样调用服务层:

Service ? DoSomething(request, context)

我们有一组这些服务执行者,它们位于 akka 路由器后面,在应用程序初始化期间创建,并且可以根据需要进行扩展。

在服务中,我们主要进行适度的数据操作或数据库调用:

receive = {
    case DoSomething(x, y) => {
           ...
           Model.doSometing(...)
           sender ! result
    }
}

我开始思考我们是否应该仅使用Futures而不是使用actor来作为我们的服务。

  1. 我们没有任何需要在服务actors中修改的内部状态,每个消息进来后都会经过一个函数并输出结果。这难道不是演员模型的强大之处吗?

  2. 我们正在执行许多看起来与演员模型有很大区别的任务。

  3. 我们不进行重计算,远程调用也没有意义,因为大部分工作都是针对数据库的,并且在远程actor中进行一些数据库调用的往返操作是不必要的。

  4. 我们确实使用了reactivemongo,因此每个数据库调用都是非阻塞的。我们可以进行许多此类调用。

在我看来,去除akka并仅使用Futures可以使我们的生活更加轻松��而且我们实际上不会失去任何东西。

4个回答

37

关于演员应该做什么或不应该做什么的问题,存在着大量意见,例如这两篇文章:

http://noelwelsh.com/programming/2013/03/04/why-i-dont-like-akka-actors/

http://www.chrisstucchio.com/blog/2013/actors_vs_futures.html

我认为在这个问题上不会有绝对的答案,除了情况是因情况而异的,取决于你的喜好和你的问题。我可以提供我的观点,这是基于我们现在已经实施Akka约2年的经验。

对我而言,我喜欢把Akka看作一个平台。我们用Actor Model来构建服务组件架构,但是我们留下所有其他优秀的平台部分,如Clustering/Remoting、FSM、Routing、Circuit Breaker、Throttling等等。我们正在将这些服务部署成集群,因此我们利用诸如Location Transparency和Routing等功能,让服务消费者(本身也可能是另一个服务)找到并使用服务,无论它在哪里部署,并以高可用方式。基于平台工具,Akka让整个过程变得非常简单。

在我们的系统中,我们有一个我称之为基础服务的概念。这些服务非常简单(例如特定实体的基本查找/管理服务)。这些服务通常不会调用其他服务,在某些情况下仅执行数据库查找。这些服务是池化的(路由器),通常没有任何状态。它们与您所描述的一些服务非常相似。然后,我们开始在这些基础服务之上构建越来越复杂的服务。大多数这些服务是短期的(为了避免请求),有时是基于FSM的,收集基础服务的数据,然后进行计算并产生结果。尽管这些基础服务本身非常简单,有人会说它们不需要actor,但我喜欢灵活性,因为当我将它们组合成更高级别的服务时,我可以查找它们,并且它们可以位于我的群集中的任何位置(位置透明),有任何数量的实例可用(路由)供使用。

对于我们来说,选择以一个actor作为基线设计是一种设计决策,作为一种类似微型服务的东西,它在我们的集群中可供任何其他服务消费,无论该服务有多简单。我喜欢通过异步粗粒度接口与这些服务通信,无论它们在哪里。这些原则的许多方面是构建良好SOA的要素。如果您的目标是这样做,那么我认为Akka可以帮助实现这个目标。如果您不打算做这样的事情,那么也许您对使用Akka进行服务有所疑虑是正确的。正如我之前所说,真正取决于您从架构角度尝试做什么,然后设计您的服务层以满足这些目标。


8

我认为你的想法是正确的。

我们的服务演员没有任何需要修改的内部状态,无论什么消息进来都会传递给一个函数并输出结果。这难道不是Actor模型的重要优势吗?

我发现Chris Stucchio的博客(由@ cmbaxter提到)非常有趣。我的情况非常简单,架构考虑不是一个有效的观点。就像你一样,只有Spray路由和大量的数据库访问,没有状态。因此使用Future,代码更简单。


1

如果需要具有可修改状态的长期存在的东西,应该创建Actor。在其他情况下,尤其是非类型化的情况下,没有任何从Actor中受益的好处。 - 每次进行模式匹配 - 控制Actor的生命周期 - 记住不应该在线程之间传递的事物 为什么要做这些事情,当你可以拥有简单的Future呢? 有一些任务非常适合使用Actor,但并不是所有任务都适合。


0
我也曾经有同样的疑问,我们决定使用Akka来进行数据访问,效果非常好,而且易于测试(已经测试过),并且非常便携。
我们创建了长期存在的Actor仓库,用于启动我们的应用程序:(FYI,我们正在使用slick进行数据库访问,但对于我们的MongoDB需求也有类似的设计)
val subscriptionRepo = context.actorOf(Props(new SubscriptionRepository(appConfig.db)), "repository-subscription")
现在我们能够发送一个“请求”消息以获取数据,例如:
case class SubscriptionsRequested(atDate: ZeroMillisDateTime)
该Actor将响应以下内容:
case class SubscriptionsFound(users: Seq[UserSubscription])
或者Failure(exception)
在我们的情况下(spray应用程序,但也包括CLI),我们将这些调用封装在一个短暂存在的Actor中,该Actor接收上下文并在接收到请求后完成并关闭自身。(您可以在这些Actor中处理特定于域的逻辑,使其扩展另一个管理其生命周期和异常的Actor,因此您只需要为您的需求指定部分函数,并让抽象Actor处理超时,常见异常等。)
我们还有一些情况需要在启动角色中完成更多的工作,这时向存储库发送x条消息非常方便,让您的角色在消息到达时将它们存储起来,等待所有消息都到齐后执行某些操作,然后向发送者(例如)返回完成信息并关闭自身。
由于这种设计,我们拥有一个非常反应迅速的存储库,它完全独立于我们的应用程序之外,并使用Akka TestKit和H2进行了全面测试,完全与数据库无关。从我们的数据库访问数据非常简单(我们从不使用ASK,只使用Tell:告诉存储库,告诉发送者,完成或x个Tells到存储库,根据预期结果进行模式匹配,告诉发送者)。

1
在使用Actor实现Repo时,请三思。Repo仅与数据库交互,其中状态应该被保留。如果您正在使用非类型化的Actor,则可以免费获得每个repo方法的类型映射;对于所有东西都可以使用mapTo。 - Igor Yudnikov
你好,Igor。我的留言是在2016年写的,你说得对,我们几个月后就完全改变了模式,正是出于这个原因 :) - David Puleri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接