序列化
Akka有内置的序列化扩展,你可以使用内置的序列化器,也可以自定义。
序列化机制可以在Akka内部用于序列化消息,也可以用于专门序列化。
用法
配置
Akka要想知道那个序列化器用于那些消息,你需要编辑配置"akka.actor.serializers"部分,你将序列化器的名字绑定到你希望使用的akka.serialization.Serializer的实现上,就像这样:
akka {
actor {
serializers {
java = "akka.serialization.JavaSerializer"
proto = "akka.remote.serialization.ProtobufSerializer"
myown = "docs.serialization.MyOwnSerializer"
}
}}
在将名字绑定到序列化器的实现后,你需要配置哪个类应该用哪个序列化器,这是通过配置"akka.actor.serialization-bindings"完成的:
akka {
actor {
serializers {
java = "akka.serialization.JavaSerializer"
proto = "akka.remote.serialization.ProtobufSerializer"
myown = "docs.serialization.MyOwnSerializer"
}
serialization-bindings {
"java.lang.String" = java
"docs.serialization.Customer" = java
"com.google.protobuf.Message" = proto
"docs.serialization.MyOwnSerializable" = myown
"java.lang.Boolean" = myown
}
}}
你只需要指定消息的接口或者抽象类的名字。如果出现模棱两可的情况,即消息实现几个配置类,那么将会使用最确定的配置类,即其它的候选类都是超类。如果这个条件不满足,例如因为java.io.Serializable和MyOwnSerializable都适用,并且这两个都说不是其它的子类型,akka会发出警告。
Akka默认提供了java.io.Serializable和protobuf com.google.protobuf.GeneratedMessage消息的序列化器(后面这个只有在依赖了akka-remote module才会与),所以正常情况在你不需要为这种消息添加配置,由于com.google.protobuf.GeneratedMessage实现了java.io.Serializable,protobuf消息总是使用protobuf协议序列化,除非被明确地覆写。为了禁止默认的序列化器,将它的标记类型映射为“none”:
akka.actor.serialization-bindings {
"java.io.Serializable" = none}
验证
如果你想要验证你的消息是可序列化的,你可以使能下面的配置选项:
akka {
actor {
serialize-messages = on
}}
警告
我们只推荐使用配置选项来打开什么时候运行测试。其它场景打开测试是没有意义的。
如果你想要验证你的Props是可序列化的,你可以使能下面的配置选项:
akka {
actor {
serialize-creators = on
}}
警告
我们只推荐使用配置选项来打开什么时候运行测试。其它场景打开测试是没有意义的。
编码实现序列化
如果你想要以编码方式实现序列化和反序列化,这儿有一些例子:
import akka.actor.*;
import akka.serialization.*;
ActorSystem system = ActorSystem.create("example");
// 获取序列化
ExtensionSerialization serialization = SerializationExtension.get(system);
// 要序列化的对象
String original = "woohoo";
// 找到对应的序列化器
Serializer serializer = serialization.findSerializerFor(original);
// 将对象转换为字节数组
byte[] bytes = serializer.toBinary(original);
// 将字节数组转换回对象,类和类加载器都是null
String back = (String) serializer.fromBinary(bytes);
// 瞧!
assertEquals(original, back);
要了解更多信息,请查看ScalaDoc学习akka.serialization._
自定义序列化器
如果你想要创建自己的序列化器,你看过上面配置示例docs.serialization.MyOwnSerializer吗?
创建新的序列化器
首先你需要创建序列化器的类定义,继承akka.serialization.JSerializer,就像这样:
import akka.actor.*;
import akka.serialization.*;
public class MyOwnSerializer extends JSerializer {
// 这个方法定义fromBinary是否需要"clazz"
@Override
public boolean includeManifest() {
return false;
}
// 选择序列化器的一个唯一标识符,你有数十亿的选择,但0 - 16 是为Akka自己保留的
@Override
public int identifier() {
return 1234567;
}
// "toBinary"将给定对象序列化为一个字节数组
@Override
public byte[] toBinary(Object obj) {
// Put the code that serializes the object here
// ... ...
}
// "fromBinary"反序列化指定的数组,使用类型提示(如果存在,参见includeManifest)
@Override
public Object fromBinaryJava(byte[] bytes, Class<?> clazz) {
// Put your code that deserializes here
// ... ...
}
}
然后你只需要填满空白的地方,在配置中将它绑定到一个名字上,然后在列出那些类应该使用它进行序列化。
序列化ActorRef
所有的ActorRef都使用JavaSerializer序列化,但是如果你正在编写自己的序列化器,你可能想要知道如何正确地序列化和反序列化它们。通常情况下,本地地址依赖于远程地址的类型,远程地址是序列化消息的接收者。使用Serialization.serializedActorPath(actorRef):
import akka.actor.*;import akka.serialization.*;
// 序列化// (底层是toBinary)
String identifier = Serialization.serializedActorPath(theActorRef);
// 然后直接序列化identifier
// 反序列化// (底层是fromBinary)
final ActorRef deserializedActorRef = extendedSystem.provider().resolveActorRef(
identifier);
// 然后直接使用ActorRef
这假设序列化发生在发送向远程端口消息的上下文中。序列化还有其它的用途,例如在actor应用外部(数据库等)存储actor应用。在这种情况下,记住actor路径是很重要的,它的地址决定了actor如何进行通信。存储本地的actor路径可能是一个正确的选择,如果读取发生在相同的逻辑上下文中,但是当在不同的网络主机上反序列化时这是远远不够的:因为它需要包含系统的远程传输地址。一个橘色系统不会限制一个系统只有一个远程传输端口,这使得问题变得有趣起来。为了在发送到远程地址时能够找到合适的地址,你可以使用ActorRefProvider.getExternalAddressFor(remoteAddr),就像这样:
public class ExternalAddressExt implements Extension {
private final ExtendedActorSystem system;
public ExternalAddressExt(ExtendedActorSystem system) {
this.system = system;
}
public Address getAddressFor(Address remoteAddress) {
final scala.Option<Address> optAddr = system.provider().getExternalAddressFor(remoteAddress);
if (optAddr.isDefined()) {
return optAddr.get();
} else {
throw new UnsupportedOperationException("cannot send to remote address " + remoteAddress);
}
}
}
public classExternalAddress extends AbstractExtensionId<ExternalAddressExt> implements ExtensionIdProvider {
public static final ExternalAddress ID = new ExternalAddress();
public ExternalAddress lookup() {
return ID;
}
public ExternalAddressExt createExtension(ExtendedActorSystem system) {
return new ExternalAddressExt(system);
}
}
public class ExternalAddressExample {
public String serializeTo(ActorRef ref, Address remote) {
return ref.path().toSerializationFormatWithAddress(
ExternalAddress.ID.get(system).getAddressFor(remote));
}
}
注意
如果地址不没有host和port部分,即它只是插入本地的地址信息,那么ActorPath.toSerializationFormatWithAddress与toString的结果是不同的。toSerializationFormatWithAddress也为actor添加了唯一id,当actor停止时这个id会改变,然后用相同的名字再次创建一个actor。向指向老actor的引用发送消息不会分发到新的actor。如果不想要这个行为,例如长期存储引用,你可以使用toStringWithAddress,这个方法不会包含唯一的id。
这需要你至少知道反序列化actor引用的系统支持的地址类型;如果没有具体的地址,你可以使用Address(protocol, "", "", 0)为协议创建一个虚拟的地址 (假设实际传送端口像Akka的RemoteActorRefProvider一样宽容)。
还有一个默认的远程地址,是由集群支持使用的(通常系统只有这一个),你可以这样获取它:
public class DefaultAddressExt implements Extension {
private final ExtendedActorSystem system;
public DefaultAddressExt(ExtendedActorSystem system) {
this.system = system;
}
public Address getAddress() {
return system.provider().getDefaultAddress();
}
}
public class DefaultAddress extends AbstractExtensionId<DefaultAddressExt> implements ExtensionIdProvider {
public staticfinal DefaultAddress ID = new DefaultAddress();
public DefaultAddress lookup() {
return ID;
}
public DefaultAddressExt createExtension(ExtendedActorSystem system) {
return new DefaultAddressExt(system);
}
}
深度序列化Actor
推荐使用Akka Persistence完成内部actor状态的深度序列化。
谈谈Java序列化
当你使用Java序列化,而没有采用JavaSerializer,你必须确保为动态变量JavaSerializer.currentSystem提供一个合法的ExtendedActorSystem。这将用于读取ActorRef的表示,将字符串表示转换为真正的引用。DynamicVariable是一个thread-local变量,所以当反序列化包含actor引用的消息时,确保设置了该变量。
外部Akka序列化器
Akka-protostuff by Roman Levenstein
Akka-quickser by Roman Levenstein