序列化程序是从Akka消息中删除共享状态的正确位置吗?

时间:2017-06-21 08:14:34

标签: serialization akka akka-remote-actor akka-remoting

我正在研究分布式算法,并决定使用Akka在机器上进行扩展。机器需要非常频繁地交换消息,这些消息引用了每台机器上存在的一些不可变对象。因此,"压缩"似乎是明智的。消息,即不应在消息中序列化共享的复制对象。这不仅可以节省网络带宽,而且还可以避免在反序列化消息时在接收方创建重复对象。

现在,我的问题是如何正确地做到这一点。到目前为止,我可以想到两个选择:

  1. 在"业务层"上处理它,即将我的原始消息对象转换为某些引用对象,这些引用对象通过某些符号引用替换对共享的复制对象的引用。然后,我会发送那些引用对象而不是原始消息。可以把它想象成用URL替换一些实际的Web资源。这样做在编码方面似乎相当直接,但它也将序列化问题拖入实际的业务逻辑中。

  2. 编写了解共享的复制对象的自定义序列化程序。在我的例子中,这个解决方案可以通过序列化器将复制的共享对象作为全局状态引入actor系统。但是,Akka文档没有描述如何以编程方式添加自定义序列化程序,这对于使用序列化程序编织共享对象是必要的。此外,我可以想象有几个原因,为什么不鼓励这样的解决方案。所以,我在这里问。

  3. 非常感谢!

2 个答案:

答案 0 :(得分:1)

可以编写自己的自定义序列化程序并让它们执行各种奇怪的操作,然后您可以像往常一样在配置级别绑定它们:

class MyOwnSerializer extends Serializer {

  // If you need logging here, introduce a constructor that takes an ExtendedActorSystem.
  // class MyOwnSerializer(actorSystem: ExtendedActorSystem) extends Serializer
  // Get a logger using:
  // private val logger = Logging(actorSystem, this)

  // This is whether "fromBinary" requires a "clazz" or not
  def includeManifest: Boolean = true

  // Pick a unique identifier for your Serializer,
  // you've got a couple of billions to choose from,
  // 0 - 40 is reserved by Akka itself
  def identifier = 1234567

  // "toBinary" serializes the given object to an Array of Bytes
  def toBinary(obj: AnyRef): Array[Byte] = {
    // Put the code that serializes the object here
    //#...
    Array[Byte]()
    //#...
  }

  // "fromBinary" deserializes the given array,
  // using the type hint (if any, see "includeManifest" above)
  def fromBinary(
    bytes: Array[Byte],
    clazz: Option[Class[_]]): AnyRef = {
    // Put your code that deserializes here
    //#...
    null
    //#...
  }
}

但是这提出了一个重要的问题:如果你的消息都引用了已经在机器上共享的数据,你为什么要在消息中加入指向对象的指针(非常糟糕!消息应该是不可变的,和一个指针不是<!em>),而不是某种不可变的字符串objectId(有点你的选项1)?在保留消息的不变性方面,这是一个更好的选择,并且您的业务逻辑几乎没有变化(只需将包装器放在共享状态存储上)

有关详细信息,请参阅the documentation

答案 1 :(得分:0)

我终于选择了Diego提出的解决方案,并希望分享有关我的推理和解决方案的更多细节。

首先,我也支持选项1(处理业务层中消息的&#34;压缩&#34;),原因如下:

  1. 序列化程序是actor系统的全局。使它们成为有状态实际上是对Akka的哲学的最严重违反,因为它违背了演员中行为和国家的封装。
  2. 无论如何都必须预先创建序列化程序(即使添加它们&#34;以编程方式&#34;)。
  3. 在设计方面,人们可以争辩说&#34;消息压缩也不是序列化器的责任。从严格意义上讲,序列化仅仅是将运行时特定数据转换为紧凑,可交换的表示。但是,更改序列化的内容不是序列化程序的任务。
  4. 在确定了这一点之后,我仍然努力将信息压缩&#34;和演员中的实际业务逻辑。我想出了一个在Scala中做到这一点的简洁方法,我想在这里分享一下。基本的想法是使消息本身看起来像一个普通的案例类,但仍然允许这些消息“紧凑”#34;他们自己。这是一个抽象的例子:

    class Sender extends ActorRef {
       def context: SharedContext = ... // This is the shared data present on every node.
    
       // ...
    
       def someBusinessLogic(receiver: ActorRef) {
         val someData = computeData
         receiver ! MyMessage(someData)
       }
    }
    
    class Receiver extends ActorRef {
       implicit def context: SharedContext = ... // This is the shared data present on every node.
    
       def receiver = {
         case MyMessage(someData) =>
           // ...
       }
    }
    
    object Receiver {
      object MyMessage {
        def apply(someData: SomeData) = MyCompactMessage(someData: SomeData)
        def unapply(myCompactMessage: MyCompactMessage)(implicit context: SharedContext)
        : Option[SomeData] =
          Some(myCompactMessage.someData(context))
      }
    }
    

    正如您所看到的,发送方和接收方代码感觉就像使用案例类一样,事实上,MyMessage可能是一个案例类。 但是,通过手动实施applyunapply,可以插入自己的&#34; compactification&#34;逻辑并且还隐式地注入执行&#34; uncompactification&#34;所需的共享数据,而不触及发送者和接收者。为了定义MyCompactMessage,我发现Protocol Buffers特别适合,因为它已经是Akka的依赖,并且在空间和计算方面都很有效,但是任何其他解决方案都可以。