java.io.OptionalDataException
Java的对象传输是在一个keepalive连接上传输的,就是说建立一个网络连接后可以传输很多很多的对象实例。
譬如:A跟B通讯,A端write对象a,write对象b,write对象c;B端就要read对象a,read对象b,read对象c。
但是B端面对的是源源不断的数据,怎么才能区分出a和b的分界点呢,这个就是应用协议要解决的问题了。通用的解决方法是在write a前用一个商定的协议头,在协议头里描述数据的长度,通过这个长度来进行数据的定界。如果在write协议头的时候,序列化的a对象被修改了,那协议头的标识的长度和正式长度就不一致了,这样导致了a对象的部分数据会被read b的时候当作协议头,这样这个连接管道原来有序的数据分界就乱了,只有重置这个连接才能恢复。怎样才能重置这个连接呢,重启是最简单的办法了,重启连接的任何一端都可以。
什么情况下会出现“如果在write协议头的时候,序列化的a对象被修改”的情况呢,在并发环境下,一个线程在进行write对象a,另外一个线程在修改对象a就会导致这种情况。这种情况一般是因为应用使用非线程安全的类,譬如:HashMap,并且应用自身并发控制不严引起的。
定位问题的办法:
修改JDK 的library里的ObjectInputStream类,当出现StreamCorruptedException时把Stream里的前4K字节写到console ouput中。可以通过这个方法找到第一次破坏协议的序列化的内容,以这个序列化的内容作为线索找出应用问题发生的地方。在出现问题的系统上加上这个补丁。这个补丁需要使用-Xbootclasspath来设置,影响JVM boot ClassLoader.
Bug之java.io.OptionalDataException
产生背景:
在线上的生产环境中,登录时有一个将用户的访问权限写入redis缓存的逻辑,以便后面访问接口时候,快速验证用户是否具有权限。写入的时候,没有报错提示。但是取出来的时候,偶发性的会报Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.OptionalDataException。
分析:
写入是没有问题的,可以从redis里面看到确实写入的数据,但是数据是加密的,看不出来具体是什么。首先,还是看看java.io.OptionalDataException这个类的介绍。
/**
* Exception indicating the failure of an object read operation due to
* unread primitive data, or the end of data belonging to a serialized
* object in the stream. This exception may be thrown in two cases:
*
* <ul>
* <li>An attempt was made to read an object when the next element in the
* stream is primitive data. In this case, the OptionalDataException's
* length field is set to the number of bytes of primitive data
* immediately readable from the stream, and the eof field is set to
* false.
*
* <li>An attempt was made to read past the end of data consumable by a
* class-defined readObject or readExternal method. In this case, the
* OptionalDataException's eof field is set to true, and the length field
* is set to 0.
* </ul>
*
* @author unascribed
* @since JDK1.1
*/
123456789101112131415161718192021
从类介绍看出的是,读取数据出现问题,有两个可能的原因,一个是读操作读取未读的原始数据,另一个是在流中的最后一个数据是序列化的对象。这两句话也看不出什么问题。网上查了一下有说要数据处理要同步。 无奈下,将存入的逻辑仔细debug好几十遍,出现这个错误的时候,存入的对象的序列化ID都是显示表明的,所以排除序列化ID不同造成的。最后,看存入的数据时,发现数据的数目总是变动,外部条件完全一致,获取的数据量总是不同,跟进去看到。使用了parallelStream,然后foreach,将元素加入到一个非线程安全的集合HashSet中,所以数据量一直变动。