- 后端
- 微服务
【免费下载链接】akka.net
Canonical actor model implementation for .NET with local + distributed actors in C# and F#.
在 Akka.NET 中,任何跨进程消息(远程 Actor 通信、集群内部协议、持久化事件与快照、分布式数据复制)都必须经过序列化才能在线路上传输或写入存储。为了让接收方准确还原发送方使用的序列化格式,每个序列化器都绑定了一个全局唯一的整数 ID,并随消息一起在协议中传递。本文以官方序列化器 ID 码表(docs/articles/serialization/codes.md)为骨架,结合仓库源码中各个模块的序列化器注册实现,系统讲解 ID 1–100 保留区间的内部序列化器归属、它们通过 HOCON 配置或模块初始化器注入 ActorSystem 的两种方式,以及当某个序列化器缺失时如何通过配置 fallback 与扩展方法补全。读完本文,你将能看懂 Akka.NET 内部序列化 ID 的分配规则,并能自行排查"找不到序列化器"类错误。
1. 为什么需要序列化器 ID 码表
Akka.NET 的消息可能跨越进程边界:Akka.Remote负责把消息打包到 TCP/Artery 传输层,Akka.Cluster通过 Gossip 协议交换成员状态,Akka.Persistence把事件与快照写入 Journal 和 Snapshot Store,Akka.DistributedData则在节点间复制 CRDT 数据。所有这些场景都依赖Akka.Serialization.Serialization组件,按消息类型找到合适的序列化器,把对象变成字节数组,并把序列化器的数字 ID 编码进消息帧,让对端能反查 ID 对应的序列化器来反序列化。
ID 采用整数而不是类名字符串,是为了节省线缆字节数并加速查找:接收端只需查一张进程内的 ID → 序列化器映射表,无需传输冗长的类型全名。正因为 ID 是跨节点协议的一部分,它必须全局唯一且稳定——一旦某个 ID 在运行时被不同序列化器占用,集群内不同节点将无法互相解码消息。
基于这一前提,Akka.NET 规定:ID 范围 1–100 保留给 Akka.NET 官方内部序列化器,应用开发者自定义的序列化器应使用 100 以外的 ID。码表给出了每个保留 ID 属于哪个包(Package)、对应哪个序列化器类,以及如何把它注入 ActorSystem 的配置。
2. Akka.NET 内部序列化器完整 ID 码表
下表完整对应官方码表(docs/articles/serialization/codes.md),并补充了各序列化器在仓库中的源码位置与注册别名,便于读者直接跳转核实:
| Id | Serializer Class Name | Package | Direct Access Method | Injected By |
|---|---|---|---|---|
| 1 | NewtonSoftJsonSerializer | Akka | ConfigurationFactory.Default() | ActorSystem.Create() |
| 2 | ProtobufSerializer | Akka.Remote | RemoteConfigFactory.Default() | 模块自动注册 |
| 3 | DaemonMsgCreateSerializer | Akka.Remote | RemoteConfigFactory.Default() | 模块自动注册 |
| 4 | ByteArraySerializer | Akka | ConfigurationFactory.Default() | ActorSystem.Create() |
| 5 | ClusterMessageSerializer | Akka.Cluster | ClusterConfigFactory.Default() | ClusterSharding.Get() |
| 6 | MessageContainerSerializer | Akka.Remote | RemoteConfigFactory.Default() | 模块自动注册 |
| 7 | PersistenceMessageSerializer | Akka.Persistence | Persistence.DefaultConfig() | Persistence.Instance.Apply() |
| 8 | PersistenceSnapshotSerializer | Akka.Persistence | Persistence.DefaultConfig() | Persistence.Instance.Apply() |
| 10 | ClusterMetricsMessageSerializer | Akka.Cluster.Metrics | ClusterMetrics.DefaultConfig() | ClusterMetrics.Get() |
| 11 | ReplicatedDataSerializer | Akka.DistributedData | DistributedData.DefaultConfig() ClusterSharding.DefaultConfig() | ClusterSharding.Get() DistributedData.Get() |
| 12 | ReplicatorMessageSerializer | Akka.DistributedData | DistributedData.DefaultConfig() ClusterSharding.DefaultConfig() | ClusterSharding.Get() DistributedData.Get() |
| 13 | ClusterShardingMessageSerializer | Akka.Cluster.Sharding | ClusterSharding.DefaultConfig() | ClusterSharding.Get() |
| 14 | ClusterSingletonMessageSerializer | Akka.Cluster.Tools | DistributedPubSub.DefaultConfig() ClusterSingletonProxy.DefaultConfig() ClusterSingletonManager.DefaultConfig() | DistributedPubSub.Get() ClusterSharding.Get() |
| 15 | ClusterClientMessageSerializer | Akka.Cluster.Tools | ClusterClientReceptionist.DefaultConfig() | ClusterClientReceptionist.Get() |
| 16 | MiscMessageSerializer | Akka.Remote | RemoteConfigFactory.Default() | 模块自动注册 |
| 17 | PrimitiveSerializers | Akka.Remote | RemoteConfigFactory.Default() | 模块自动注册 |
| 22 | SystemMessageSerializer | Akka.Remote | RemoteConfigFactory.Default() | 模块自动注册 |
| 30 | StreamRefSerializer | Akka.Streams | ActorMaterializer.DefaultConfig() | ActorSystem.Materializer() |
| 48 | PersistentSnapshotSerializer | Akka.Persistence.Redis | RedisPersistence.DefaultConfig() | RedisPersistence.Get() |
几个值得注意的观察点:
- ID 并非连续分配:表中可见 9、18–21、23–29、31–47 等大量空缺。其中 9 与 18–21 等位置曾用于历史版本或预留,31–47 则保留给后续官方模块。应用自定义序列化器不应占用 1–100 内的任何编号(包括空缺号),否则可能与未来官方序列化器冲突。
- 1 和 4 属于 Akka 核心:由
ActorSystem.Create()通过 akka.conf 中的默认配置直接注入,是所有 ActorSystem 的基础序列化器。 - 48 属于 Akka.Persistence.Redis:这是一个独立的官方模块(当前仓库不包含其实现),通过
RedisPersistence.Get()注入,提醒我们 1–100 区间不只被本仓库的模块占用。
3. 两种注入机制:HOCON 配置注册与模块初始化器注册
码表中 "Direct Access Method" 与 "Injected By" 两列,分别对应序列化器进入 ActorSystem 的两种不同路径。理解它们的区别,是排查序列化错误的关键。
3.1 HOCON 配置注册(Direct Access Method)
传统方式是在akka.actor.serializers、akka.actor.serialization-bindings和akka.actor.serialization-identifiers三处配置节点中显式声明。Akka 核心的两个序列化器即以此方式定义在 akka.conf:
akka.actor { # 序列化器注册表:别名 -> 实现类的完整类型名 serializers { json = "Akka.Serialization.NewtonSoftJsonSerializer, Akka" bytes = "Akka.Serialization.ByteArraySerializer, Akka" } # 类型绑定:类型 -> 序列化器别名(可用接口或抽象基类,取最具体匹配) serialization-bindings { "System.Byte[]" = bytes "System.Object" = json } # ID 注册表:实现类完整类型名 -> 全局唯一 ID serialization-identifiers { "Akka.Serialization.ByteArraySerializer, Akka" = 4 "Akka.Serialization.NewtonSoftJsonSerializer, Akka" = 1 } }"Direct Access Method" 一列给出的ConfigurationFactory.Default()、RemoteConfigFactory.Default()等方法,本质上都是返回一个内置的Config对象,其中就包含上述三组节点的默认定义。开发者可以通过myConfig.WithFallback(RemoteConfigFactory.Default())把这些默认序列化器合并进自己的配置。
3.2 模块初始化器注册(Injected By)
从较新版本开始,官方模块普遍改用代码注册:序列化器不再写进各自的reference.conf,而是由每个模块内部的ModuleSerializers子类在模块加载时自动注册。这一点在 Remote.conf 中有明确注释:
"Remote's serializers, their bindings and their ids register from code (RemoteSerializers), not from this file."
以Akka.Remote为例,RemoteSerializers.cs 通过SerializerDetails.Create(别名, 序列化器实例, 绑定的类型集合)一次性注册了全部远程序列化器:akka-containers(ID 6)、akka-misc(ID 16)、primitive(ID 17)、proto(ID 2)、daemon-create(ID 3)、akka-system-msg(ID 22)以及 Artery 传输专用的artery-control。
这些模块表统一登记在 ModuleSerializers.cs 的ModuleSerializerTable.Default中,每个模块一行,按程序集名懒加载:
["Akka.Remote"] = () => Load("Akka.Remote.Serialization.RemoteSerializers, Akka.Remote"), ["Akka.Streams"] = () => Load("Akka.Streams.Serialization.StreamsSerializers, Akka.Streams"), ["Akka.Cluster"] = () => Load("Akka.Cluster.Serialization.ClusterSerializers, Akka.Cluster"), ["Akka.Cluster.Tools"] = () => Load("Akka.Cluster.Tools.ToolsSerializers, Akka.Cluster.Tools"), ["Akka.Cluster.Sharding"] = () => Load("Akka.Cluster.Sharding.Serialization.ShardingSerializers, Akka.Cluster.Sharding"), ["Akka.DistributedData"] = () => Load("Akka.DistributedData.Serialization.DistributedDataSerializers, Akka.DistributedData"), ["Akka.Cluster.Metrics"] = () => Load("Akka.Cluster.Metrics.Serialization.MetricsSerializers, Akka.Cluster.Metrics"), ["Akka.Persistence"] = () => Load("Akka.Persistence.Serialization.PersistenceSerializers, Akka.Persistence"),该机制的核心特性(见 ModuleSerializers.cs 的注释):
- 无需任何 HOCON 行:只要模块程序集被加载,其序列化器、绑定和 ID 即作为默认值注册,不需要
Type.GetType字符串,天然兼容 AOT/裁剪场景; - HOCON 仍可覆盖:如果开发者在
serializers中对同名别名给出自己的定义,配置优先级更高; - SerializationSetup 优先级最高:显式调用
SerializationSetup的绑定会同时覆盖 HOCON 与模块默认值。
3.3 "Injected By" 列的真实含义
码表中的 "Injected By" 并非指序列化器只在那时才会注册,而是指触发模块初始化器加载的入口。例如:
ActorSystem.Create()加载 Akka 核心默认配置,注入 ID 1 与 4;Persistence.Instance.Apply()应用持久化模块配置,注入 ID 7 与 8;ClusterSharding.Get()、DistributedData.Get()、ClusterMetrics.Get()等扩展方法在首次访问对应扩展时加载其模块表,分别注入 ID 13/11/12、ID 10 等。
当akka.actor.provider配置为remote或cluster时,对应提供者的序列化器会被自动带上:
akka.actor.provider = remote # 或完整类型名 akka.actor.provider = "Akka.Remote.RemoteActorRefProvider, Akka.Remote" akka.actor.provider = cluster # 或完整类型名 akka.actor.provider = "Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"4. 实战:缺失序列化器 ID 时的两种补救手段
码表文档给出了一个典型场景:当你遇到类似 "missing serializer with id 13" 的异常时——即消息帧携带的 ID 在当前 ActorSystem 的序列化器表中找不到对应项——通常是因为承载该序列化器的模块配置没有被合并进系统。解决办法有两种,且经常需要组合使用。
4.1 通过配置 fallback 注入
在构建 ActorSystem 时,把对应模块的DefaultConfig()作为 fallback 合并进你的配置。以下用 Cluster Sharding(ID 13)举例:
var config = myConfig .WithFallback(ClusterSharding.DefaultConfig()) .WithFallback(ClusterSingletonManager.DefaultConfig()); // 视需要 var system = ActorSystem.Create("my-system", config);WithFallback的语义是:当主配置中缺少某个键时,从 fallback 配置取值;主配置已有的键不受影响。这与 Akka 配置体系"层叠 fallback"的设计一致,任何DefaultConfig()返回的配置都可以这样叠加。同理,分布式数据(ID 11/12)既可通过DistributedData.DefaultConfig()注入,也可通过ClusterSharding.DefaultConfig()注入——因为 Sharding 内部依赖 DData。
4.2 通过扩展方法按需加载
更简洁的方式是直接调用模块的扩展入口,让模块表被加载并注册其序列化器:
ClusterSharding.Get(actorSystem); // 注入 ID 13(及依赖的 11/12) DistributedData.Get(actorSystem); // 注入 ID 11/12 ClusterMetrics.Get(actorSystem); // 注入 ID 10 ClusterClientReceptionist.Get(actorSystem);// 注入 ID 15 Persistence.Instance.Apply(system); // 注入 ID 7/8对Akka.Remote与Akka.Cluster的序列化器而言,只要akka.actor.provider配置为remote/cluster(或对应的RemoteActorRefProvider/ClusterActorRefProvider全名),序列化器便会随提供者自动注册,无需额外操作。仓库测试 RemoteSerializersSpec.cs 与 ClusterSerializersSpec.cs 验证了这些注册在标准配置下均生效。
5. 各模块序列化器源码对照
以下逐一对应码表中的 ID 与仓库源码中的实际注册实现,方便读者深入阅读。
5.1 Akka 核心(ID 1、4)
定义于 akka.conf:
- ID 1 NewtonSoftJsonSerializer:别名
json,默认绑定System.Object,即未显式注册类型的兜底序列化器。相关行为开关位于同文件akka.actor.serialization-settings.json节点(如use-pooled-string-builder、pooled-string-builder-minsize),也受akka.actor.serialization-settings.allow-unregistered-types控制。 - ID 4 ByteArraySerializer:别名
bytes,默认绑定System.Byte[],直接透传字节数组,零拷贝开销。
5.2 Akka.Remote(ID 2、3、6、16、17、22)
全部注册于 RemoteSerializers.cs:
- ID 2 ProtobufSerializer(
proto):绑定Google.Protobuf.IMessage,用于所有 protobuf 消息,如集群与持久化协议消息; - ID 3 DaemonMsgCreateSerializer(
daemon-create):序列化DaemonMsgCreate,即远程部署 Actor 时传输的创建指令; - ID 6 MessageContainerSerializer(
akka-containers):绑定ActorSelectionMessage,承载ActorSelection消息; - ID 16 MiscMessageSerializer(
akka-misc):杂项消息序列化器,绑定类型包括Identify、ActorIdentity、IActorRef、PoisonPill、Kill、Status.Failure/Success、各种 Router Pool 配置(RoundRobinPool、BroadcastPool、ConsistentHashingPool等)以及RemoteWatcher心跳消息; - ID 17 PrimitiveSerializers(
primitive):为string、int、long等基元类型提供紧凑编码,是远程消息中最常用的序列化器之一; - ID 22 SystemMessageSerializer(
akka-system-msg):序列化SystemMessage,用于 Actor 内部系统消息(如Watch/Unwatch)的远程传输。
5.3 Akka.Cluster(ID 5)
ClusterSerializers.cs 注册:
- ID 5 ClusterMessageSerializer(
akka-cluster):序列化 Gossip、成员状态等集群协议消息,是所有集群节点互通的基础;同文件还注册了reliable-delivery序列化器用于集群可靠投递。
5.4 Akka.Persistence(ID 7、8)
PersistenceSerializers.cs 注册:
- ID 7 PersistenceMessageSerializer(
akka-persistence-message):序列化持久化事件与 Journal 写入消息; - ID 8 PersistenceSnapshotSerializer(
akka-persistence-snapshot):序列化快照数据。
两者由Persistence.DefaultConfig()提供默认配置、Persistence.Instance.Apply()完成注入。相关验证可参考 PersistenceSerializersSpec.cs。
5.5 Akka.Cluster.Sharding(ID 13)
ShardingSerializers.cs 注册akka-sharding(ID 13,ClusterShardingMessageSerializer),由ClusterSharding.DefaultConfig()提供配置、ClusterSharding.Get()注入。
5.6 Akka.Cluster.Tools(ID 14、15)
ToolsSerializers.cs 注册三个序列化器:
- ID 14 ClusterSingletonMessageSerializer(
akka-singleton):集群单例相关消息,配置来源包括DistributedPubSub.DefaultConfig()、ClusterSingletonProxy.DefaultConfig()、ClusterSingletonManager.DefaultConfig(); - ID 15 ClusterClientMessageSerializer(
akka-cluster-client):集群客户端(ClusterClient)消息,由ClusterClientReceptionist.DefaultConfig()提供配置、ClusterClientReceptionist.Get()注入; - 同文件还注册了
akka-pubsub(DistributedPubSubMessageSerializer),用于分布式发布订阅。
5.7 Akka.DistributedData(ID 11、12)
DistributedDataSerializers.cs 注册:
- ID 11 ReplicatedDataSerializer(
akka-replicated-data):序列化 CRDT 复制数据类型; - ID 12 ReplicatorMessageSerializer(
akka-data-replication):序列化复制器(Replicator)之间的协议消息。
两者均可通过DistributedData.DefaultConfig()或ClusterSharding.DefaultConfig()注入(Sharding 依赖 DData),也可由DistributedData.Get()/ClusterSharding.Get()触发注册。
5.8 Akka.Cluster.Metrics(ID 10)
MetricsSerializers.cs 注册akka-cluster-metrics(ID 10,ClusterMetricsMessageSerializer),由ClusterMetrics.DefaultConfig()提供配置、ClusterMetrics.Get()注入。
5.9 Akka.Streams(ID 30)
StreamsSerializers.cs 注册akka-stream-ref(ID 30,StreamRefSerializer),用于SourceRef/SinkRef流引用的跨节点传输,由ActorMaterializer.DefaultConfig()提供配置、ActorSystem.Materializer()注入。
6. 如何注册自己的序列化器(避开保留区间)
码表的存在意义之一,是让开发者清楚地知道 1–100 是官方保留区间,自定义序列化器必须从 100 之后取 ID。完整的自定义注册分三步,全部在 HOCON 中完成:
akka.actor { serializers { my-protobuf = "MyApp.Serialization.MyProtobufSerializer, MyApp" } serialization-bindings { "MyApp.Messages.OrderMessage, MyApp" = my-protobuf # 也可绑定接口,序列化器查找会取最具体的匹配 } serialization-identifiers { "MyApp.Serialization.MyProtobufSerializer, MyApp" = 101 } }serializers:为序列化器类起一个本地别名(如my-protobuf);serialization-bindings:把消息类型(类、接口或抽象基类)绑定到别名;存在歧义时按"最具体配置类优先"解析,否则给出警告并取第一个;serialization-identifiers:为序列化器实现类分配全局唯一 ID,必须大于 100。
此外,akka.conf 中的akka.actor.serialization-settings.allow-unregistered-types(默认true)决定未显式绑定类型是否回退到System.Object的 JSON 序列化器;设false可开启严格序列化控制,未注册类型将直接抛出SerializationException。如需更精细的类型绑定,可在代码中使用SerializationSetup,其优先级高于 HOCON 与模块默认注册。
7. 最佳实践与注意事项
- ID 必须全局唯一且永不更改:序列化器 ID 是跨节点协议的一部分,一旦发布并被持久化数据使用,改动 ID 会导致旧数据无法解码。官方保留 1–100,自定义从 101 起,切勿复用空缺号(如 9、18–21)。
- 善用
WithFallback分层合并:任何官方模块的DefaultConfig()都可以叠加进你的配置,WithFallback不会覆盖你已显式声明的键,这比手工复制模块配置片段更安全。 - 用扩展方法触发注册:
ClusterSharding.Get()、DistributedData.Get()等调用既是功能入口,也是模块序列化器的注册入口;在依赖某模块前先调用其扩展,可避免 "missing serializer" 类错误。 - 注意
serialization-v2开关:akka.conf 中的akka.actor.serialization-v2默认关闭。开启后,内置序列化器的 V2 版本(别名形如<legacy>-v2)接管旧版绑定,节点以 V2 格式写出消息;旧序列化器仍保留注册以便读取,因此回滚只需关闭开关。切换前必须确认集群所有节点都运行支持 V2 的版本。 - 验证注册结果:仓库提供了大量序列化器验证测试,如 RemoteSerializersSpec.cs、ClusterSerializersSpec.cs、BuiltInSerializerDefaultsSpec.cs,可作为"某个 ID 对应哪个序列化器、默认绑定哪些类型"的行为参照。
掌握 ID 码表与注册机制后,你将能快速定位跨节点消息解码失败、持久化数据反序列化异常等问题的根因——绝大多数情况下,答案就是"某个模块的序列化器没有注入到当前 ActorSystem",而修复手段不过是一次WithFallback或一次扩展方法调用。
- 后端
- 微服务
【免费下载链接】akka.net
Canonical actor model implementation for .NET with local + distributed actors in C# and F#.
相关推荐
Faust 序列化 Codec 体系深度解析:内置编解码器、链式组合与扩展注册机制
Faust 序列化 Codec 体系深度解析:内置编解码器、链式组合与扩展注册机制 Faust(Python Stream Processing)中,所有进出
流处理消息队列后端OpenAI GPT 1 vs GPT 2:一文读懂两代语言模型的核心差异与演进
OpenAI GPT 1 vs GPT 2:一文读懂两代语言模型的核心差异与演进 OpenAI GPT 1和GPT 2作为早期 transformer 语言模型
Faust 序列化注册表(faust.serializers.registry)深度解析:消息键值编解码的中枢机制
Faust 序列化注册表(faust.serializers.registry)深度解析:消息键值编解码的中枢机制 faust.serializers.regi
流处理消息队列后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考