最近在整理一个开发框架:
主体:spring4+mybatis3+mysql
前端:easyui 或者 Ace Admin,后者是一个基于bootstrap 的强大后台管理系统界面。
集群:Tomcat+kryo+Redis
其它:c3p0链接池、P6SPY作SQL跟踪、logback作日志。
其中在整合kryo的时候,遇到了一些挑战,记录如下:
问题一:
在系列化有Hashtable、Hashmap之类的对象属性的时候,会遇到异常:
com.esotericsoftware.kryo.KryoException: Class cannot be created (missing no-arg constructor): java.util.Collections$SynchronizedRandomAccessList
Serialization trace:
childs (xc.core.tree.Tree)
tree (xc.core.RegionTree)
at com.esotericsoftware.kryo.Kryo$DefaultInstantiatorStrategy.newInstantiatorOf(Kryo.java:1319)
at com.esotericsoftware.kryo.Kryo.newInstantiator(Kryo.java:1127)
at com.esotericsoftware.kryo.Kryo.newInstance(Kryo.java:1136)
at com.esotericsoftware.kryo.serializers.CollectionSerializer.create(CollectionSerializer.java:107)
at com.esotericsoftware.kryo.serializers.CollectionSerializer.read(CollectionSerializer.java:111)
at com.esotericsoftware.kryo.serializers.CollectionSerializer.read(CollectionSerializer.java:40)
at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731)
这个异常比较容易解决,简单来说就是默认的系列化器不支持java.util.Collections$SynchronizedRandomAccessList这种没有无参构造函数的类,解决办法就是下载
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.42</version>
得到kryo-serializers-0.42.jar,这里面包含了很多特殊类的自定义系列化器,加入到工程中然后代码中执行:
SynchronizedCollectionsSerializer.registerSerializers(kryo);
问题即可以得到解决。
问题二:
在反系列化时,会得到异常:
com.esotericsoftware.kryo.KryoException: Encountered unregistered class ID: XXXX
at com.esotericsoftware.kryo.util.DefaultClassResolver.readClass(DefaultClassResolver.java:137)
at com.esotericsoftware.kryo.Kryo.readClass(Kryo.java:693)
at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:804)
其中XXXX的值不定,这类异常,有几种可能性存在,首先要了解一些kryo的基本原理:
当Kryo写出一个对象的实例时,首先可能需要写出一些标识对象类的东西。默认情况下,写入完整类名,然后写入该对象的字节。后续出现的同一类对象图的对象用变长的int来写(using a variable length int)。写类的名字有点低效,所以类可以事先注册:kryo.register(SomeClass.class);这样的话,SomeClass 注册到了 Kryo,它将该类与一个 int 型的 ID 相关联。当 Kryo 写出 SomeClass 的一个实例时,它会写出这个 int ID。这比写出类名更有效。在反序列化期间,注册的类必须具有序列化期间相同的 ID 。
还有一种情况是kryo每次写入类的完整信息,而不是通过int类型的ID号去代替。
两种情况如何取舍呢?写id的情况,效率会高一些,但是缺陷很明显:所有系列化涉及到的类都需要代码里手工kryo.register(),否则报告unregistered class ID;另外,kryo不能保证"每次jvm重启之后,或者在多台jvm机器之间,同一个类注册到kryo的class ID会相同",所以这就导致该模式应用于集群会存在严重问题,所以用id代替类信息的模式不建议使用,而且,默认该功能也是关闭的,除非你在代码中显性的调用了以下代码:
kryo.register(SomeClass.class);
或者:
Kryo.setRegistrationRequired(true);
所以遇到Encountered unregistered class ID之类的问题,首先要检查该功能是否被不经意的开启了。如果确保未开启该功能,再考虑以下情况:就是对象太大,导致系列化结果不完整,因为output对象默认的缓存字节数并不大,实际对象超出大小的时候,系列化的时候并不会报告错误,但是系列化结果已经不完整,从而导致反系列化的时候会失败,报告的错误一般也是Encountered unregistered class ID。该问题的解决:
一般系列化的时候,代码是这么写的:
ByteArrayOutputStream baos=new ByteArrayOutputStream();
Output output = new Output(baos);
这种情况下,缓存字节数大于4096的话,就会发生问题。必须在建立Output对象的时候,指定更大的bufferSize,例如:
ByteArrayOutputStream baos=new ByteArrayOutputStream();
Output output = new Output(baos,100000);