这个头大的问题纠缠了我一天。不明原因,Akka不能在remote端创建actor。
开始以为是我的模型太复杂,折腾了一下来个最简化(参见这个commit),结果是可以正常工作的。于是使用替换大法,一个类一个类地换,最后定位到某个具体的类,发现是一个第三方库的序列化没做好,导致Akka的Props里的creator不能传到remote端。这个好办,修改了一下这个库的代码,但是问题并没有解决。这回十分诡异了,因为我很确定creator里面的所有东西都是可序列化的了。
无计可施之时,scalap了一下这个creator的生成类:
1 2 3 4 5 6 7 8 9 | final class SupervisorActor$$anonfun$...$actor$flow$SupervisorActor$$buildActors0$1$2 extends scala.runtime.AbstractFunction0 with scala.Serializable { final val config$1: scala.collection.immutable.Map; final val clazz$1: java.lang.String; final val curr$1: ....ActorflowVertex; final val $outer: ....SupervisorActor; def this(....SupervisorActor, ....ActorflowVertex, java.lang.String, scala.collection.immutable.Map): scala.Unit; def apply(): scala.Any; def apply(): ....ActionActor; } |
注意看这个$outer,它就是这个生成类的外部类,但是这个外部类本身并不能序列化。这就是问题的根源。但是为什么会这样呢?先看看这个生成类的原始状态:
1 2 3 | context.actorOf(Props(new StartActor(ActorflowGraph(graph.id, graph.g, curr), params)).withRouter(FromConfig), curr.routerName(self.path.name.style, graph.id)) |
这里的graph是外部类的一个成员变量。因为creator里面的closure引用了该成员变量,所以scalac把整个外部类都给打包了进去,也就是上面的$outer。
问题已经很清楚了,但是怎么改?很简单,用局部变量:
再来看看生成类:
1 2 3 4 5 6 7 8 9 | final class SupervisorActor$$anonfun$...$actor$flow$SupervisorActor$$buildActors0$1$2 extends scala.runtime.AbstractFunction0 with scala.Serializable { final val g$1: edu.uci.ics.jung.graph.Graph; final val id$1: java.lang.String; final val params$1: scala.collection.Seq; final val curr$1: ....ActorflowVertex; def this(...SupervisorActor, ....ActorflowVertex, scala.collection.Seq, java.lang.String, edu.uci.ics.jung.graph.Graph): scala.Unit; def apply(): scala.Any; def apply(): ....StartActor; } |
$outer没了,取而代之的是两个局部变量的拷贝g$1和id$1,这两个对象都是可序列化的。至此,问题解决。
这个诡异的问题折腾了我一整天!结论就是,closure是个好东西,但是由于scala最终都要编译成java的字节码,对于closure里面自由变量的处理有些时候还是要理解的。