如何从异步通信链中干净/安全地删除链接

时间:2013-12-15 20:00:25

标签: algorithm asynchronous akka actor chain

我有一系列Akka演员K - > L - > M,其中K向L发送消息,L向M发送消息,并且每个消息都可以使用接收消息的sender来回复其前任。

我的问题是:我们如何才能安全地取消链接L,以便在操作K之后将消息直接发送给M,M将K视为sender

如果每个演员都很稳定且长寿,我可以找出如何告诉K和M互相交谈。 L可以徘徊足够长的时间来转发已经在她的邮箱中的消息,直到得到K和M中的每一个不再与L交谈的信号。

然而,K和M也可能正在考虑取消连接,这就是一切都变得毛茸茸的。

是否有一个众所周知的协议可以安全地完成这项工作?到目前为止,我还没有找到合适的搜索字词。

我知道一种替代方法是冻结系统并将整个事物(减去L)复制到一个新的链中。但是,我想实现一个更实时,更少中断的解决方案。

我一直在考虑某种锁交换,其中K和M承诺在L完成取消链接并转发所有消息之前不会取消链接。但是,任何带有“lock”一词的解决方案在异步解决方案中似乎都很尴尬,即使演员不会被完全锁定(只是延迟他们自己的取消链接操作直到方便的时间)。

1 个答案:

答案 0 :(得分:1)

解决方案的一个粗略想法是在每个节点内保持前一个和下一个节点的状态,然后支持消息从链中解锁节点,并告诉节点它的下一个节点已经改变。当下一个节点发生变化时,向通知您的节点发送毒丸(假设它是未链接的节点)以优雅地停止它。在取消链接之前和停止之前,对可能出现的任何消息充当纯粹的直通。将所有这些放在一起,代码看起来像这样:

object ChainNode {
  case object Unlink
  case class Link(prev:Option[ActorRef], next:Option[ActorRef])
  case class ChangeNextNode(node:Option[ActorRef])
}

trait ChainNode extends Actor{
  import ChainNode._
  import context._

  override def postStop{
    println(s"${self.path} has been stopped")
  }

  def receive = chainReceive()

  def chainReceive(prevNode:Option[ActorRef] = None, nextNode:Option[ActorRef] = None):Receive = {
    case Unlink =>
      prevNode foreach{ node =>
        println(s"unlinking node ${self.path} from sender ${node.path}") 
        node ! ChangeNextNode(nextNode)
      } 
      become(unlinked(nextNode))

    case Link(prev, next) =>
      println(s"${self.path} is linking to $prev and $next")
      become(chainReceive(prev, next))

    case ChangeNextNode(newNext) =>
      println(s"${self.path} is changing next node to $newNext")
      become(chainReceive(prevNode, newNext))
      sender ! PoisonPill

    case other =>    
      println(s"${self.path} received message $other")
      val msg = processOther(other)
      nextNode foreach{ node =>
        println(s"${self.path} forwarding on to ${node.path}")
        node ! msg
      }
  }

  def unlinked(nextNode:Option[ActorRef]):Receive = {
    case any => 
      println(s"${self.path} has been unlinked, just forwarding w/o processing...")
      nextNode foreach (_ ! any)      
  }

  def processOther(msg:Any):Any
}

class NodeA extends ChainNode{
  def processOther(msg:Any) = "foo"
}

class NodeB extends ChainNode{
  def processOther(msg:Any) = "bar"
}

class NodeC  extends ChainNode{
  def processOther(msg:Any) = "baz"
}

然后,一个简单的测试场景,其中链接在中途改变:

object ChainTest{
  import ChainNode._  

  def main(args: Array[String]) {
    val system = ActorSystem("chain")
    val a = system.actorOf(Props[NodeA])
    val b = system.actorOf(Props[NodeA])
    val c = system.actorOf(Props[NodeA])

    a ! Link(None, Some(b))
    b ! Link(Some(a), Some(c))
    c ! Link(Some(b), None)

    import system.dispatcher
    Future{
      for(i <- 1 until 10){
        a ! "hello"
        Thread.sleep(200)
      }
    }

    Future{
      Thread.sleep(300)
      b ! Unlink
    }
  }
}

它并不完美,但它可以作为一个很好的起点。一个缺点是,UnlinkChangeNextNode等邮件仍将按照收到的顺序处理。如果邮箱中有大量邮件,则必须先在更改(例如取消链接)之前处理这些邮件。这可能导致进行更改时出现意外延迟。如果这是一个问题,那么您可能需要查看基于优先级的邮箱,其中UnlinkChangeNextNode等邮件的优先级高于其他正在处理的邮件。