产生背景:
在线上的生产环境中,登录时有一个将用户的访问权限写入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
*/
从类介绍看出的是,读取数据出现问题,有两个可能的原因,一个是读操作读取未读的原始数据,另一个是在流中的最后一个数据是序列化的对象。这两句话也看不出什么问题。网上查了一下有说要数据处理要同步。
无奈下,将存入的逻辑仔细debug好几十遍,出现这个错误的时候,存入的对象的序列化ID都是显示表明的,所以排除序列化ID不同造成的。最后,看存入的数据时,发现数据的数目总是变动,外部条件完全一致,获取的数据量总是不同,跟进去看到。使用了parallelStream,然后foreach,将元素加入到一个非线程安全的集合HashSet中,所以数据量一直变动。
为此我写了一段测试代码。
public static void main(String[] args) throws InterruptedException {
Collection<String> nameCollection = testParallelStream();
System.out.println(nameCollection.size());
nameCollection.forEach(name -> {
System.out.println(name);
System.out.println("sign");
});
}
private static Collection<String> testParallelStream() {
Set<String> nameSet = new HashSet<>();
// Collection<String> collection = new HashSet<>();
Collection<String> collection = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 1000; i++) {
nameSet.add("cj" + i);
}
nameSet.parallelStream().forEach(name -> {
collection.add(name);
});
System.out.println(collection.size());
return collection;
}
这个里面如果不使用Collections.synchronizedSet(new HashSet<>()),则会造成线程安全问题。本质上parallelStream是多线程处理,而HashSet是个非线程安全的集合,往里面放数据的时候,会出现数据丢失。