在actor层次结构中以原子方式传递可变对象

时间:2013-09-08 11:59:25

标签: java scala concurrency transactions akka

我已经为我的Scala / Java应用程序构建了一个适当的actor层次结构,它主要依赖于fire-forget语义。我现在面临着需要在actor之间原子地传递唯一的可变对象。特别是,我有三个演员,A,B和C:

  A
 / \
B   C

B和C都有自己的唯一对象 Map ,并且彼此不了解。在某个时间点,B将决定它需要摆脱对象 O 我正在寻找一种机制,允许将对象 O 添加到 C Map 并从 B中删除地图原子地

B 并未决定接收对象 O C :它最初所做的就是向演员发送处理请求 A 的。演员 A 可以允许或拒绝来自 B 的处理请求,并从那里引入或不引入 C 到演员 B 他们以自主和原子的方式完成交易。

修改

我原来的问题是标签错误和困惑。在我的系统中,消息不可变的:我在actor之间发送UUID,而不是引用对象。这些UUID是可变对象的私有每个actor映射的键。 B和C在其私有地图中持有的对象的使用在任何给定的时间点都是互斥的。

我更进一步确保B和C之间不会共享可变对象,也就是说,确保B的地图中的键K和C的地图中的键K指向不同的私有可变对象,这是微不足道的。 (比如Ob和Oc)具有相同的UUID。

一个目标是避免同时对B中的对象Ob和C中的Oc进行计算。这本身并不是一个问题(我不介意在从演员B到演员C的过渡期间偶尔浪费几个CPU周期)但是它成为一个问题,因为演员B和C将他们的模拟结果报告给一个第三方客户,我们可以拨打D。

D不知道A,B和C之间的关系,因此它可以从B和C接收关于相同UUID的结果,而不能分辨它应该听哪一个。由于模拟的性质,这些结果可能不同且相互矛盾。当然,演员B可以停止模拟对象Ob并向演员C发送消息,告诉它在对象Oc上开始模拟。这将阻止客户端D从B和C接收关于同一对象的消息,但是将存在一个时间帧,在该时间帧期间可以完全不存在该UUID。也许这对我的应用程序来说不是很重要,但我仍然需要验证这一点。我的理想选择是UUID的actor的同步切换。

3 个答案:

答案 0 :(得分:1)

Two phase commit protocol可用于进行原子更新。

B -> A tell ("initiate moving of O B->C") 

A -> B tell ("prepare remove O") //
A -> C tell ("prepare add O") // (A selects C)

C changes O to prepare-to-add state
C -> A tell ("ready to add O")

B changes O to prepared-to-remove state.
B -> A tell ("ready to remove O")

A waits for two "ready" messages and then:
A -> B, C tell ("commit")

if A receives timeout, then
A -> B, C tell ("rollback")

要使此协议可靠地运行,您需要在B,C中实现撤消/重做日志。

答案 1 :(得分:1)

ScalaSTM可以使协议的实施更容易。我不太确定,但以下内容可能会给出一些如何实现目标的提示:

class O { val owner = Ref(None:Option[ActorRef]) }

class A extends Actor {
  val listOfTransferedObject = mutable.ListBuffer()
  def move(O, to) {
    atomic { implicit txn =>
      val oldOwner = O.owner()
      O.owner() = self 
      listOfTransferedObject += O
      oldOwner.get ! NotifyRemoved(O) // you may prefer to use `ask` 
      O.owner() = to
      O.owner().get ! NotifyAdded(O) // you may prefer to use `ask` 
    }
  }
}

在接收B和C时,您只需要更新内部地图。

答案 2 :(得分:1)

我相信你可以使用Transactors完成你想要做的事情。

具体而言,B和C应包括Transactor特征。 B应该实现coordinate方法,这样当A发送一条消息将其引入C并告诉它继续并更新其状态时,B通过调用include(C)邀请C参与该事务。两个参与者都应该实施atomically方法来处理其内部状态的实际更新(即更新他们的地图)。

这样的事情:

type Key = // your map key type
type Value = // your map value type

case class AtomicallyTransferObject(transferTo : ActorRef, key : Key, value : Value)

class B extends Actor with Transactor
{
   private val myMap = mutable.Map[Key, Value]

   def coordinate = {
      case AtomicallyTransferObject(transferTo, key, value) => include(transferTo)
   }

   def atomically = {implicit txn =>
      case AtomicallyTransferObject(_, key, value) => myMap -= (key, value)
   }
}

class C extends Actor with Transactor
{
   private val myMap = mutable.Map[Key, Value]

   def atomically = {implicit txn =>
      case AtomicallyTransferObject(self, key, value) => myMap += (key, value)
   }
}

Actor A会向B发送AtomicallyTransferObject消息,这会导致B邀请C加入该事务,然后两个actor都会以原子方式处理该消息。

有关Transactors的更多信息,请访问:http://doc.akka.io/docs/akka/current/scala/transactors.html

请注意,对于非事务性消息,您应该实现receive,而不是实现normally,因为Transactor特征提供了委派给receive的{​​{1}}实现{1}}和normally