什么时候在akka / erlang中使用actor不好?


58

我每天与akka合作7-8个月。当我开始时,我将在应用程序上工作,并注意到actor基本上会在actor系统内部的任何地方用于大多数对象之间的通信。所以我也做同样的事情-为x / y / z旋转另一个演员。

在我看来,这可能太过随意了,在不需要的地方增加了复杂性-但是我找不到关于应该使用参与者与普通同步甚至是期货的异步逻辑的讨论。我的同事提到类似的内容后,我开始思考自己的立场。我最近才意识到有几种情况,我在考虑一个任务,然后避免创建另一个参与者,因为我可以在不可变的实现中安全地实现相同的结果-例如,从数据库或文件中获取配置值的地方,而这些地方很少访问并且会等待结果是实际用例。

尤其是在我看来,在任何情况下,如果您正在使用不可变状态,参与者都会造成复杂性并限制吞吐量-例如,对象中的纯函数可以在没有任何并行级别的情况下并发调用,但是演员一次只能处理一条消息。另一个考虑因素是,如果您需要等待结果,除非您开始使用期货,否则就将线程停放,但是在不需要担心异步消息传递或扩展的情况下,聘请演员似乎过大。

所以我的问题是-使用演员的时间不好吗?我很好奇erlang看起来如何并且真的希望别人的见识。或者,如果有一些有关演员使用的原则。


什么样的工作每天都有几个月的机会与Akka合作?

3
好的。:)或自己进行更改。
JasonG 2014年

我想具体了解在ask选拔演员和仅使用简单人物之间的权衡 Future
Max Heiber

2
几年后,我最终写了一本有关Akka的书,我想您可以这样总结:如果您有某种状态(例如计数器),则从该值读取/写入该值的多个线程最终会互相干扰没有同步和锁定。如果将该状态放入一个actor中,则可以突然确保可以安全地访问该状态,并且不会丢失写入或获取过时的读取。erlang和akka中的actor还假定状态可能变糟并且actor可能引发错误,因此您具有一些自我修复系统特征。如果您不需要突变,则期货会更简单
JasonG

许多年后,我是一名erlang / elixir程序员,并且继续以新的见解回到这一点:) Elixir与众不同,因为没有对象可以替代流程,因此始终在需要状态时构建流程。它恰好是并发的。这仍然是最好的启发式方法。
JasonG '18年

Answers:


23

这是我感兴趣的问题,我一直在进行一些研究。对于其他观点,请参阅Noel Walsh的博客文章或有关Stack Overflow的问题。我想提出一些意见:

  • 我认为Akka可以处理消息,因此可以鼓励“推送思维方式”。通常,为了并发,我会说这不是您想要的。拉动要安全得多。例如,分布式系统的一种常见模式是让一组工人在队列中处理信息。显然,这在Akka中是可能的,但不一定是人们尝试第一种方法。Akka还提供持久邮箱,但这又取决于您的使用方式-单个共享队列比每个工作人员队列灵活得多,可以平衡/重新分配工作。
  • 很容易陷入用演员替换班级的想法。实际上,有些人甚至通过说演员只能做一件事来主张这一点。逻辑上得出结论,这会像Jason所描述的那样增加代码的复杂性,因为如果每个类都是一个演员,那将带来很多额外的消息以及接收/发送块。由于失去了接口的形式性,这也使理解和测试代码变得更加困难-而且我不相信注释是解决此问题的方法。同样,尽管Akka具有传奇般的效率,但我怀疑演员的增长并不是明智的性能选择-当我们使用Java线程时,我们知道它们是宝贵的,因此可以保存它们。
  • 它与上一点有关,但是另一个烦人的地方是Noel和Pino强调的类型信息的丢失,对于我们许多人来说,这就是为什么我们使用Scala而不是其他语言(例如Python)的原因。有一些解决方法,但是它们不是标准的不推荐的实验性的
  • 最后,即使您有“让它崩溃”的心态,并发也很难。替代编程模型可以提供帮助,但是它们并不能使问题消失(可以解决问题),这就是为什么正式考虑它们的原因。这也是Joe Average开发人员可以使用现成的工具(例如RabbitMQ,Storm,Hadoop,Spark,Kafka或NoSQL数据库)的原因。Akka确实有一些预构建的工具和组件,这很酷,但是感觉还很低,因此,更多现成的分布式系统通用元素可以帮助开发人员并确保系统正确构建。

像杰森一样,我很想在这里听到其他人的见识。我该如何解决上述一些问题并更好地使用Akka?


1
我在ak
Mark Butler

“依赖于继承”对于依赖项注入仍然有用。例如,将依赖作为道具传递(使用构造函数参数)。那么问题将是监督-创建的演员将在演员以外的其他地方受到监督。可以使用路由器将监视策略包装在顶层创建的角色周围,但是这很复杂!我猜蛋糕将是一个更好的解决方案-例如,对于DB服务角色来说,角色创建具有特质。然后,只需在文本上下文中使用测试模拟/存根数据库服务特征即可。
JasonG 2014年

1
是的,确切地说,监督与依赖项注入不能很好地配合。还有我有必要注入一个工厂而不是一个类的实例,否则会有很多闭包问题-我猜是由于Akka对线程的处理。
Mark Butler

我认为这不是一个坏主意-谢谢马克-实际上,我可能会尝试写一些有关它并使其社交化。您可以注入一些可能给道具的东西吗?各种actor工厂,消费者可以在其中实例化actor。例如def method(arg)=从那个(psuedo?)工厂返回Props(new ActorWithArgs(arg)),以便您可以在正确的上下文中创建actor。对于di的蛋糕解决方案,这似乎是一个好主意,也是一个不错的目标。
JasonG 2014年

我刚刚开始学习此课程:coursera.org/course/posa尽管它主要针对Android编程人员,但它还是Java并发性的很好概述。因此,我想知道的一件事是:“ Akka是否只是一种带有花哨的事件循环的奇特形式(因为您可以将事件循环放在不同的线程上)?”
马克·巴特勒

21

值得考虑参与者模型的用途:参与者模型是

  1. 并发模型
  2. 避免同时访问可变状态
  3. 使用异步通信机制提供并发。

这很有价值,因为从多个线程使用共享状态非常困难,尤其是当共享状态的不同组件之间存在必须保持同步的关系时。但是,如果您具有以下域组件:

  • 您不允许并发,或者
  • 您不允许可变状态(例如在函数编程中),或者
  • 您必须依靠某种同步通信机制,

那么参与者模型将不会提供太多(如果有)收益。

希望能有所帮助。


谢谢-我想您真的必须评估两件事-可变状态和并发性?解决类是否为无状态或非并发没有问题。还有一个考虑因素-我认为容错是另一点?例如,如果您有一个不可变的redis客户端,但它在运行时可能会失败-那不是一个好用例吗?重新启动该演员可能是必要的?因为-尽管类可能纯粹是不可变的,但在不可变的actor外部有可能导致其失败的损坏状态是很可能的。
JasonG

我猜想负责与redis通信的actor会获得异常并重新启动。
JasonG

@JasonG在我看来,“容错”通常不是actor模型的一个方面-它是Erlang(也许是Akka?)中actor模型实现所附带的。尽管不得不承认,我无法与redis对话,但是“不变客户端”对我来说听起来很奇怪……如果某个软件组件可能发生故障,我看不出它怎么被认为是不变的。
艾丹·库利(Aidan Cully)2013年

容错和监督存在于akka中,它与erlang非常相似。我了解您在说不可变客户端,但是不可变意味着代码状态不会在任何地方更改。如果我在启动时初始化连接,则客户端代码可能会因在任何类型的故障上使用的重启策略而变得不可变,只需在遇到问题时重新初始化参与者即可。
JasonG

12

您的直觉是正确的,恕我直言。到处都使用演员就像拥有众所周知的锤子,只看到钉子一样。

Erlang最佳实践是对同时发生的所有活动使用流程/参与者。也就是说,就像现实生活中一样。有时很难找到正确的粒度,但是大多数情况下,您只是通过查看建模域并使用一些常识就知道了。恐怕我没有比这更好的方法了,但是我希望它会有所帮助。


很好,谢谢。我真的很想知道这次讨论会发生什么。
JasonG

0

为了输入/输出消息传递:

我最近遇到了一个基于akka的应用程序,其中actor模型实际上引起了并发问题,一个简单的模型在负载下就足够了。

问题在于传入的消息在不同的“通道”(通过不同的actor路径)中移动,但是代码假定消息将以到达的最终顺序到达它们的最终目的地。只要数据以足够大的间隔到达,就可以正常工作,因为只有一条冲突的消息会争夺目的地。当间隔减小时,它们开始混乱,并引起奇怪的行为。

使用较少的actor可以正确解决该问题,但这是过度使用它们时容易犯的错误。


这是基础且广泛相关的。您只能保证两个角色之间的顺序。doc.akka.io/docs/akka/current/scala/general / ... 您无法保证在扇出/扇入的任何系统中都能交付订单。andreasohlund.net/2012/01/18/dont-assume-message-ordering
JasonG

-1

我认为Actor有两个用例。共享资源(例如端口等)和大状态。到目前为止,讨论已经很好地涵盖了第一个,但是大状态也是一个正当的理由。

每个过程调用传递的大型结构可能会占用大量堆栈。可以将该状态放入一个单独的进程中,将结构替换为进程ID,然后根据需要查询该进程。

可以将诸如失忆症之类的数据库视为在查询过程外部存储状态。


1
你能澄清一下吗?您是说整个网络处于大状态吗?在本地过程中,传递对不可变结构的引用几乎没有成本,并且您不能传递可变结构。假设您正在谈论需要复制才能发送的大型可变结构?这基本上又是同一件事-共享状态。大小并没有真正改变任何东西。让我知道我是否误会了。
JasonG

那么,您如何通过参考?当然,这样做的方法是将结构放入流程中并传递processid。本地调用将把数据放在堆栈上,并且每个递归调用都会再次执行(尾尾递归除外)。可以使用列表将结构从一个调用转移到另一个调用来进行结构的递归处理,然后,该状态可以在另一个过程中引用该结构。
Tony Wallace
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.