如何排查Akka序列化失败根因并处理非序列化消息成员
问题描述
当Akka出现如下序列化错误时,如何处理Akka消息中违规的非序列化成员/字段?异常信息确实指出了无法序列化的成员值(如下的com.our.member.object),但该对象本身包含许多嵌套成员,错误消息似乎无法提供导致序列化失败的嵌套对象的线索:
2018-05-24T06:30:51.897Z ERROR VCA-akka.actor.default-dispatcher-4 akka.remote.EndpointWriter - Failed to serialize remote message [class akka.actor.ActorSelectionMessage] using serializer [class akka.remote.serialization.MessageContainerSerializer]. Transient association error (association remains live)akka.remote.MessageSerializer$SerializationException: Failed to serialize remote message [class akka.actor.ActorSelectionMessage] using serializer [class akka.remote.serialization.MessageContainerSerializer]. at akka.remote.MessageSerializer$.serialize(MessageSerializer.scala:62) at akka.remote.EndpointWriter.$anonfun$serializeMessage$1(Endpoint.scala:895) at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58) at akka.remote.EndpointWriter.serializeMessage(Endpoint.scala:895) at akka.remote.EndpointWriter.writeSend(Endpoint.scala:786) at akka.remote.EndpointWriter$$anonfun$4.applyOrElse(Endpoint.scala:761) at akka.actor.Actor.aroundReceive(Actor.scala:513) at akka.actor.Actor.aroundReceive$(Actor.scala:511) at akka.remote.EndpointActor.aroundReceive(Endpoint.scala:452) at akka.actor.ActorCell.receiveMessage(ActorCell.scala:519) at akka.actor.ActorCell.invoke(ActorCell.scala:488) at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257) at akka.dispatch.Mailbox.run(Mailbox.scala:224) at akka.dispatch.Mailbox.exec(Mailbox.scala:234) at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260) at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339) at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979) at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)Caused by: java.io.NotSerializableException: com.our.member.object at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at akka.serialization.JavaSerializer.$anonfun$toBinary$1(Serializer.scala:313) at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12) at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58) at akka.serialization.JavaSerializer.toBinary(Serializer.scala:313) at akka.remote.serialization.MessageContainerSerializer.serializeSelection(MessageContainerSerializer.scala:37) at akka.remote.serialization.MessageContainerSerializer.toBinary(MessageContainerSerializer.scala:26) at akka.remote.MessageSerializer$.serialize(MessageSerializer.scala:47) ... 17 common frames omitted
解决方案
遇到这种只提示顶层对象序列化失败但没给出嵌套细节的情况,我通常会用下面这几个方法来定位和解决问题:
1. 手动序列化测试,精准定位嵌套问题
Akka的Java序列化报错有时候会隐藏底层的嵌套类信息,最直接的方式是自己写个简单的测试类,用Java原生的ObjectOutputStream来序列化出问题的对象,这样能得到完整的栈追踪,直接指向最底层没有实现Serializable的类:
import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; public class SerializationDebugger { public static void main(String[] args) { try { // 替换成你实际要测试的对象实例 com.our.member.Object problematicObject = getProblematicObject(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(problematicObject); oos.close(); System.out.println("对象序列化成功!"); } catch (Exception e) { // 这里会打印出精确到嵌套层级的错误信息 e.printStackTrace(); } } private static com.our.member.Object getProblematicObject() { // 初始化你的对象,模拟Akka消息中的实例 return new com.our.member.Object(); } }
运行这个类后,你就能在控制台看到具体是哪个嵌套类没实现Serializable了。
2. 针对不同场景修复序列化问题
根据定位到的问题,选择对应的修复方案:
- 自定义类未实现Serializable:直接给该类添加
implements Serializable接口,如果有不需要序列化的字段,用transient修饰即可。 - 第三方类无法修改:
- 用
transient标记引用该第三方类的字段,然后通过writeObject和readObject方法手动处理序列化逻辑,比如只序列化必要的属性,反序列化时再重建对象:private transient ThirdPartyNonSerializableClass externalObj; private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // 手动写出第三方对象的关键数据 out.writeString(externalObj.getIdentifier()); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 从读取的数据重建第三方对象 externalObj = ThirdPartyService.fetchById(in.readString()); } - 为第三方类自定义Akka序列化器:实现Akka的
Serializer接口,然后在Akka配置中注册这个序列化器,指定该第三方类使用它来序列化。
- 用
- 简化传输对象:如果不需要传递整个复杂对象,只传递必要的业务数据(比如ID、名称等),可以定义一个简单的DTO(数据传输对象),只包含可序列化的字段,用它来替代原对象进行传输。
3. 开启Akka序列化调试日志
在Akka的配置文件中开启序列化调试日志,能帮你在运行时观察更多序列化过程的细节:
akka { loglevel = DEBUG serialization { debug = on } }
开启后,Akka会输出序列化时处理的每个对象信息,辅助你排查问题。
内容的提问来源于stack exchange,提问作者matanox

