【问题标题】:Akka Siblings Recursive ActorRef requirementAkka Siblings 递归 ActorRef 要求
【发布时间】:2016-12-16 11:14:24
【问题描述】:

请允许我抽象一下我经常面对演员的问题,以及解决这个问题的常见设计模式。假设我有以下演员层次结构:

── user
    └── ServiceActor
        ├── TicketManager
        │   ├── WorkerA
        │   ├── WorkerB
        │   └── WorkerC
        └── UserManager
            ├── WorkerA
            ├── WorkerB
            └── WorkerC

第一级之外的层次结构,即工单和用户管理器并不重要。我要求 TicketManager 向 UserManager 发送消息,反之亦然。 (请注意,这些只是我使用的示例名称,并不代表任何实际问题)

以下哪个最合适?

选项1:

TicketManager 构造函数将 UserManager 作为构造函数参数。 UserManager 构造函数将 TicketManager 作为构造函数参数。 但是,两者都不能相互递归调用,所以这可能无论如何都不可能。

选项 2:

UserManager 通过 actorSelection 引用 TicketManager 作为 actorSelection("../TicketManager") 等要求,或者仅在启动时引用一次并保留它。

选项 3:

在任何给定时间点,UserManager/TicketManager 都会向其父级 (ServiceActor) 请求对其他兄弟姐妹的引用,并在父级持有该引用时适当地回复 ActorRef。

选项 4:

这种情况不应该是这样吗?我不应该考虑兄弟姐妹互相交谈,因为它以某种方式使设计“复杂化”吗?应该有一个更像树的层次结构吗?如果是这种情况,有什么常见的设计模式可以帮助避免这种情况?

我希望我已经明确了要求,如果需要,请允许我进一步澄清?

【问题讨论】:

  • 虽然 Edmondo 建议的初始化策略很有见地,但我最终混合使用了选项 4 和选项 3。选项 4 来解开我的演员层次结构的混乱。除此之外,在任何时候需要兄弟姐妹互相交谈时,他们都会通过父母进行交谈。我认为这是一个合理的模式。

标签: scala design-patterns akka actor service-discovery


【解决方案1】:

如果依赖关系变得复杂,选项 4 可能是最好的方法,但是对于两个参与者,您可以简单地使用消息来初始化它们:参与者被设计为保持可变状态,所以如果你改变它就可以了。

所以我想说负责创建 TickerManager 和 ActorManager 的父演员可以执行以下操作

case class InitializeTicketManager(userManager:ActorRef)

case class InitializeUserManager(ticketManager:ActorRef)
val userManager = context.actorOf(...)
val ticketManager = context.actorOf(...)
userManager ! InitializeUserManager(ticketManager)
ticketManager ! InitializeTicketManager(userManager)

【讨论】:

  • 感谢您的回答。但是,如果我们有不止两个演员呢?如果我在顶层 ServiceActor 下的同一平面级别有工单、用户、信息亭等管理器,每个管理器没有足够的子职责来创建更深的树怎么办?您还建议通过 init 方法进行初始化吗?
【解决方案2】:

如果您使用多个 ActorSystem,您正在寻找的是某种 ServiceDiscovery。这些也会有所帮助(Cluster Routing、Cluster Sharding、ClusterRouterGroup)。

你的更简单,你只有一个 ActorSystem。我会使用带有惰性 val 和按名称调用的选项 1,即

package sumnulu

import akka.actor.{Actor, ActorLogging, ActorRef, Props}

/**
  * Created by sumnulu
  */
class RootActor extends Actor with ActorLogging {
  lazy val serviceA: ActorRef = context.actorOf(ServiceA.props(serviceB))
  lazy val serviceB: ActorRef = context.actorOf(ServiceB.props(serviceA))

  override def preStart() = {
    log info "Root Actor Ready"
    serviceA ! "test"
  }

  override def receive = Actor.ignoringBehavior
}

class ServiceA(serviceB: ActorRef) extends Actor {
  println("serviceA constructor")
  serviceB ! "Hi"


  override def receive: Receive = {
    case x => println(s"self:$self from:$sender msg:$x")
  }
}


class ServiceB(serviceA: ActorRef) extends Actor {
  println("serviceB constructor")
  serviceA ! "Hi"

  override def receive: Receive = {
    case x => println(s"self:$self from:$sender msg:$x")
  }

}

//call-by-name important is i.e `=>` 
object ServiceA {
  def props(serviceB: => ActorRef) = Props(new ServiceA(serviceB))
}

//call-by-name important is i.e `=>` 
object ServiceB {
  def props(serviceA: => ActorRef) = Props(new ServiceB(serviceA))
}

object RootActor {
  def props = Props[RootActor]
}

[info] [INFO] [02/18/2017 17:49:52.485] [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app] Root Actor Ready
[info] serviceA constructor
[info] serviceB constructor
[info] self:Actor[akka://Main/user/app/$a#1420483478] from:Actor[akka://Main/user/app#274353135] msg:test
[info] self:Actor[akka://Main/user/app/$b#1573183992] from:Actor[akka://Main/user/app/$a#1420483478] msg:Hi
[info] self:Actor[akka://Main/user/app/$a#1420483478] from:Actor[akka://Main/user/app/$b#1573183992] msg:Hi

【讨论】:

  • 哇哦!从来没有这样想过。虽然我确实理解按值调用和按名称调用的区别,但为什么这能解决问题呢?你能补充一些细节吗?
猜你喜欢
  • 1970-01-01
  • 2011-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多