依赖
要使用序列化,必须在项目中添加以下依赖项:
介绍
Akka actor相互发送的消息是JVM对象。actor在同一个JVM上传递消息很简单。通过引用传递完成。但是,消息跨越JVM,必须经过某种形式的序列化,才能到达在不同主机上运行的actor。(即对象转换为字节数组,或从字节数组恢复消息)。
Akka中的序列化机制允许您编写自定义序列化程序,并定义用哪个序列化程序。
在许多情况下,使用Jackson进行序列化是一个不错的选择,如果您没有其他选择,我们建议使用。
如果您想更好地控制消息的架构演变,则Google Protocol Buffers是很好的选择,但是它需要更多的工作来开发和维护序列化表示形式与领域表示形式的映射。
Akka本身使用Protocol Buffers来序列化内部消息(例如集群gossip消息)。
用法
配置
为了使Akka知道要使用哪个序列化程序,您需要编辑配置:“akka.actor.serializers”,将名称绑定到要使用的akka.serialization.Serializer的实现中,如下所示:
在将名称绑定到不同的序列化实现之后,需要指定哪个类使用哪个Serializer,这在“akka.actor.serialization-bindings”部分中完成:
您只需要指定消息的接口名称或抽象基类即可。在模棱两可的情况下,即消息实现了几个配置的类,将使用最具体的配置类,即所有其他候选者的超类。如果无法满足此条件,例如已配置用于序列化的两个标记接口都适用,并且都不是另一个的子类型,将发出警告。
注意
如果将Scala用于消息协议,并且消息包含在Scala object中,则为了引用这些消息,您将需要使用标准Java类名称。 对于包含在名为Wrapper的Scala object中的名为Message的消息,您需要将其引用为Wrapper$Message而不是Wrapper.Message。
Akka默认为几种原始类型和protobuf com.google.protobuf.GeneratedMessage(protobuf2)和com.google.protobuf.GeneratedMessageV3(protobuf3)提供序列化程序(后者仅取决于akka-remote模块),因此如果您将原始的protobuf消息作为actor消息发送,则无需为此添加配置。
## 程式化
如果要使用Akka序列化,以编程方式进行序列化/反序列化,请参见以下示例:
清单是一种类型提示,因此同一序列化程序可用于不同的类。
请注意,从字节反序列化时,需要清单和serializer的标识符。 重要的是,以这种方式使用序列化程序标识符来支持滚动更新,其中类的serialization-bindings可能已从一个序列化程序更改为另一个序列化程序。 因此,由字节,序列化ID和清单组成的三个部分应始终一起传输或存储,以便可以使用不同的序列化绑定配置对它们进行反序列化。
SerializationExtension是经典扩展,但可以与akka.actor.typed.ActorSystem一起使用,如下所示:
个性化
此页面上的第一个代码段包含一个配置文件,该配置文件引用了自定义序列化程序docs.serialization.MyOwnSerializer。 我们将如何创建这样的自定义序列化程序?
创建新的序列化器
自定义序列化程序必须继承akka.serialization.JSerializer,并且可以如下定义:
标识符必须是唯一的。选择要用于反序列化的序列化器时,将使用该标识符。如果您意外地配置了多个具有相同标识符的序列化程序,这些序列化程序将被检测到并阻止ActorSystem启动。它可以是硬编码的值,因为它必须保持相同的值以支持滚动更新。
清单是一种类型提示,因此同一序列化程序可用于不同的类。 fromBinaryJava中的manifest参数是被序列化的对象的类。在fromBinary中,您可以在类上进行匹配,并将字节反序列化为不同的对象。
然后,您只需要填写空白,将其绑定到配置中的名称,然后列出应使用其反序列化的类。
启动ActorSystem时,serializers将由SerializationExtension初始化,因此,序列化程序本身不能从其构造函数访问SerializationExtension。相反,它应该延迟访问SerializationExtension。
字符串清单的序列化器
上面说明的序列化程序支持基于类的清单(类型提示)。
对于需要随时间变化的数据进行序列化,建议使用SerializerWithStringManifest而不是Serializer,因为清单(类型提示)是String而不是Class。这意味着可以移动/删除该类,并且serializer仍可以通过匹配String来反序列化旧数据。这对于持久性特别有用。
清单字符串还可以编码一个版本号,该版本号可用于fromBinary中以不同方式反序列化,将旧数据迁移到新的领域对象。
如果数据最初是使用Serializer进行序列化的,并且在系统的更高版本中更改为SerializerWithStringManifest,则清单字符串将是完整的类名(如果您使用includeManifest = true),否则它将是空字符串。
这是SerializerWithStringManifest的样子:
您还必须将其绑定到配置中的名称,然后列出应由其序列化的类。
如果清单未知,建议将IllegalArgumentException或java.io.NotSerializableException放入fromBinary中。这样就可以引入新的消息类型,并将其发送到不知道它们的节点。在执行滚动升级时,即运行具有混合版本的集群一段时间后,通常需要这样做。这些异常在经典远程处理层中被视为暂时问题。该问题将被记录,并且消息被丢弃。其他异常将断开TCP连接,因为它可能表明传输的字节损坏。 Artery TCP将所有反序列化异常处理为暂时性问题。
序列化ActorRefs
actor引用通常包含在消息中。当与Jackson一起使用序列化时,所有ActorRef都可以序列化,但是如果您正在编写自己的序列化器,则可能想知道如何正确地序列化和反序列化它们。
要使用字符串对actor进行序列化、反序列化,可以使用ActorRefResolver。
例如,以下是Ping和Pong消息序列化:
经典ActorRef的序列化在Classic Serialization进行了描述。经典和Typed actor引用具有相同的序列化格式,因此可以互换。
actor的深度序列化
建议使用Akka Persistence,对内部actor状态进行深度序列化。
Akka消息的序列化
Akka正在使用Protobuf 3对Akka定义的消息进行序列化。这种依赖关系隐含在akka-protobuf-v3中,以便应用程序可以使用Protobuf的另一个版本。
应用程序应使用标准的Protobuf依赖关系,而不是akka-protobuf-v3。
Java序列化
众所周知,Java序列化速度很慢,并且容易受到各种类型的攻击-毕竟,它从来都不是为高吞吐量消息传递而设计的。可能有人认为网络带宽和延迟会限制远程消息传递的性能,但是序列化是一个更为典型的瓶颈。
注意
默认情况下,使用Java序列化的Akka序列化是禁用的,并且Akka本身不对其内部任何消息使用Java序列化。不建议在生产中启用Java序列化。
生产中禁用的Java序列化程序发出的日志消息,应视为序列化程序阻止的潜在攻击,因为它们可能表示外部操作试图发送旨在使用Java序列化作为攻击向量的恶意消息。尝试使用SECURITY标记记录。
但是,对于早期原型制作来说,使用起来非常方便。因此,为了与依赖Java序列化的旧系统兼容,可以使用以下配置启用它:
当使用Java序列化时,Akka仍会记录警告,添加以下内容关闭警告:
Java序列化兼容性
当使用Java序列化时,混合主要的Scala版本并不安全,因为Scala无法保证兼容性,这可能会导致非常令人惊讶的错误。
滚动升级
序列化的远程消息(或持久事件)由序列化器ID,清单和二进制有效载荷组成。反序列化时,仅查看serializer-id以选择要用于fromBinary的序列化器。消息类(绑定)不用于反序列化。该清单仅在Serializer中用于决定如何反序列化有效负载,因此一个Serializer可以处理许多类。
这意味着可以通过执行两个滚动升级步骤来切换到新的序列化程序来更改消息的序列化。
- 添加Serializer类,并在akka.actor.serializers配置中定义它,而不在akka.actor.serialization-bindings中定义它。为此更改执行滚动升级。这意味着序列化程序类存在于所有节点上并已注册,但仍未用于序列化任何消息。这很重要,因为在滚动升级过程中,旧节点仍然不了解新的序列化程序,因此无法反序列化具有该格式的消息。
- 第二个更改是用akka.actor.serialization-bindings配置中定义那些序列化程序,对某些类注册序列化程序。为此更改执行滚动升级。这意味着新节点在发送消息时将使用新的序列化器,而旧节点将能够反序列化新格式。发送消息时,旧节点将继续使用旧序列化程序,而新节点将能够反序列化旧格式。
作为可选的第三步,如果旧的序列化程序未用于持久性事件,则可以将其完全删除。仍然必须可以反序列化旧序列化程序存储的事件。
外部Akka序列化器
- Kryo serializer for Akka
- Twitter Chill Scala extensions for Kryo
验证
通常,在本地actor(即同一JVM)之间发送的消息不会进行序列化。对于测试,有时可能希望对所有消息(远程消息和本地消息)强制进行序列化。如果要执行此操作以验证消息可序列化,则可以启用以下配置选项:
通过扩展标记接口akka.actor.NoSerializationVerificationNeeded或在配置akka.actor.no-serialization-verification-needed-class-prefix中定义类名称前缀,可以将某些消息从验证中排除。
如果要验证Props是否可序列化,可以启用以下配置选项:
警告
我们建议仅在运行测试时打开这些配置选项。 在生产中打开这些选项是没有意义的,因为这会对本地消息传递的性能产生负面影响,而不会带来任何收益。