我正在研究分布式算法,并决定使用Akka在机器上进行扩展。机器需要非常频繁地交换消息,这些消息引用了每台机器上存在的一些不可变对象。因此,"压缩"似乎是明智的。消息,即不应在消息中序列化共享的复制对象。这不仅可以节省网络带宽,而且还可以避免在反序列化消息时在接收方创建重复对象。
现在,我的问题是如何正确地做到这一点。到目前为止,我可以想到两个选择:
在"业务层"上处理它,即将我的原始消息对象转换为某些引用对象,这些引用对象通过某些符号引用替换对共享的复制对象的引用。然后,我会发送那些引用对象而不是原始消息。可以把它想象成用URL替换一些实际的Web资源。这样做在编码方面似乎相当直接,但它也将序列化问题拖入实际的业务逻辑中。
编写了解共享的复制对象的自定义序列化程序。在我的例子中,这个解决方案可以通过序列化器将复制的共享对象作为全局状态引入actor系统。但是,Akka文档没有描述如何以编程方式添加自定义序列化程序,这对于使用序列化程序编织共享对象是必要的。此外,我可以想象有几个原因,为什么不鼓励这样的解决方案。所以,我在这里问。
非常感谢!
答案 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;),原因如下:
在确定了这一点之后,我仍然努力将信息压缩&#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
可能是一个案例类。
但是,通过手动实施apply
和unapply
,可以插入自己的&#34; compactification&#34;逻辑并且还隐式地注入执行&#34; uncompactification&#34;所需的共享数据,而不触及发送者和接收者。为了定义MyCompactMessage
,我发现Protocol Buffers特别适合,因为它已经是Akka的依赖,并且在空间和计算方面都很有效,但是任何其他解决方案都可以。