在上篇中,说到了 如何解决@cacheable 使用的问题,即 对于后续有set操作的 @cacheable接口做一次深拷贝,但是上篇中我们最后使用的深拷贝的方法是利用java的自带的ByteArrayOutputStream和ByteArrayInputStream来进行深拷贝,但是java自带的这种深拷贝操作性能操作不是特别的好,压测结果如下:
多次稳定后的压测结果如下(本机虚拟机中):
./wrk -t8 -c20 -d15 http://192.168.139.1:8011/map
Running 15s test @ http://192.168.139.1:8011/map
8 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 47.51ms 21.26ms 205.76ms 70.02%
Req/Sec 42.04 12.15 120.00 70.03%
5063 requests in 15.08s, 402.53MB read
Requests/sec: 335.76
Transfer/sec: 26.69MB
于是决定利用Protostuf来进行深度拷贝的操作。
利用protostuff进行深度拷贝时,有几个问题需要进行说明下
1.利用protostuff进行深度拷贝的原理就是,先序列化再反序列化
2.再利用protostuff进行反序列化时,集合类型是无法进行反序列化的
于是查了一篇文章如何利用protostuff进行集合对象的反序列化操作
地址:https://blog.youkuaiyun.com/caodongfang126/article/details/78751027
再该博文给出的代码基础上我利用protostuff做了深拷贝的操作,代码如下;
public class CopyUtils {
private static Logger logger = LoggerFactory.getLogger(CopyUtils.class);
/**
* 对象深度复制(对象必须是实现了Serializable接口)
*/
@SuppressWarnings("unchecked")
public static <T> T clone(T obj) {
T clonedObj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
clonedObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return clonedObj;
}
/**
* 基于protostuff的深拷贝
* @param obj
* @param <T>
* @return
*/
public static <T> T cloneByProtoStuff(T obj){
T cloneObj = null;
byte[] clonebytes = ProtoStuffUtils.serialize(obj);
logger.debug("cloneByProtoStuff class {}",obj.getClass().getName());
cloneObj = (T)ProtoStuffUtils.deserialize(clonebytes,obj.getClass());
return cloneObj;
}
}
但是替换掉原生的clone操作后发现,问题来了。。。
java.lang.InstantiationException: com.google.common.collect.RegularImmutableMap
at java.lang.Class.newInstance(Class.java:427)
at com.sohu.media.mobile.util.ProtoStuffUtils.deserialize(ProtoStuffUtils.java:127)
at com.sohu.media.mobile.util.CopyUtils.cloneByProtoStuff(CopyUtils.java:47)
at com.sohu.media.mobile.service.home.HomeModuleDataService.getHomeModuleData(HomeModuleDataService.java:33)
无法实例化RegularImmutableMap对象,是的忘记我们上篇说过加上@cacheable的接口返回的数据是会被包装成RegularImmutableMap对象,所以需要在序列化时对其进行一次包装成pojo类,修改原博文的ProtoStuffUtils类:
public class ProtoStuffUtils {
private static Logger logger = LoggerFactory.getLogger(ProtoStuffUtils.class);
private static final String GOOGLE_CACHE_MAP_CLASS_NAME = "com.google.common.collect.RegularImmutableMap";
/**
* 需要使用包装类进行序列化/反序列化的class集合
*/
private static final Set<Class<?>> WRAPPER_SET = new HashSet<>();
/**
* 序列化/反序列化包装类 Schema 对象
*/
private static final Schema<SerializeDeserializeWrapper> WRAPPER_SCHEMA = RuntimeSchema.createFrom(SerializeDeserializeWrapper.class);
/**
* 缓存对象及对象schema信息集合
*/
private static final Map<Class<?>, Schema<?>> CACHE_SCHEMA = Maps.newConcurrentMap();
/**
* 预定义一些Protostuff无法直接序列化/反序列化的对象
*/
static {
WRAPPER_SET.add(List.class);
WRAPPER_SET.add(ArrayList.class);
WRAPPER_SET.add(CopyOnWriteArrayList.class);
WRAPPER_SET.add(LinkedList.class);
WRAPPER_SET.add(Stack.class);
WRAPPER_SET.add(Vector.class);
WRAPPER_SET.add(Map.class);
WRAPPER_SET.add(HashMap.class);
WRAPPER_SET.add(TreeMap.class);
WRAPPER_SET.add(Hashtable.class);
WRAPPER_SET.add(SortedMap.class);
WRAPPER_SET.add(Map.class);
WRAPPER_SET.add(Object.class);
WRAPPER_SET.add(com.google.common.collect.ImmutableMap.class);
}
/**
* 注册需要使用包装类进行序列化/反序列化的 Class 对象
*
* @param clazz 需要包装的类型 Class 对象
*/
public static void registerWrapperClass(Class clazz) {
WRAPPER_SET.add(clazz);
}
/**
* 获取序列化对象类型的schema
*
* @param cls 序列化对象的class
* @param <T> 序列化对象的类型
* @return 序列化对象类型的schema
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private static <T> Schema<T> getSchema(Class<T> cls) {
Schema<T> schema = (Schema<T>) CACHE_SCHEMA.get(cls);
if (schema == null) {
schema = RuntimeSchema.createFrom(cls);
CACHE_SCHEMA.put(cls, schema);
}
return schema;
}
/**
* 序列化对象
*
* @param obj 需要序列化的对象
* @param <T> 序列化对象的类型
* @return 序列化后的二进制数组
*/
@SuppressWarnings("unchecked")
public static <T> byte[] serialize(T obj) {
Class<T> clazz = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Object serializeObject = obj;
Schema schema = WRAPPER_SCHEMA;
if (!WRAPPER_SET.contains(clazz)) {
schema = getSchema(clazz);
} else {
serializeObject = SerializeDeserializeWrapper.builder(obj);
}
return ProtostuffIOUtil.toByteArray(serializeObject, schema, buffer);
} catch (Exception e) {
logger.error("序列化对象异常 [" + obj + "]", e);
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
/**
* 反序列化对象
*
* @param data 需要反序列化的二进制数组
* @param clazz 反序列化后的对象class
* @param <T> 反序列化后的对象类型
* @return 反序列化后的对象集合
*/
public static <T> T deserialize(byte[] data, Class<T> clazz) {
try {
if (!WRAPPER_SET.contains(clazz) &&
!GOOGLE_CACHE_MAP_CLASS_NAME.equals(clazz.getName())) {
T message = clazz.newInstance();
Schema<T> schema = getSchema(clazz);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
} else {
SerializeDeserializeWrapper<T> wrapper = new SerializeDeserializeWrapper<>();
ProtostuffIOUtil.mergeFrom(data, wrapper, WRAPPER_SCHEMA);
return wrapper.getData();
}
} catch (Exception e) {
e.printStackTrace();
logger.error("prostuff deserialize error [" + clazz.getName() + "]", e);
throw new IllegalStateException(e.getMessage(), e);
}
}
}
但是接着编译运行,新的问题来了
java.lang.NullPointerException
是的,空异常,为什么这样,Debug模式。
没错进去的时候还是有数据,反序列化后数据没了,为什么会这样呢?突然想起来,之前的文章从cacheable接口返回的数据是经过包装过后的RegularImmutableMap对象,该类的修饰符没有public,是一个包内的引用,那么结果当然就是反序列化时为空咯,因为无法使用包内对象嘛。。。
到了这里终于是对 GuavaCache 有点小抱怨了。。。,但是问题还在,我要如何解决这个问题呢?
突然灵光一闪,我为什么不对这个返回的 RegularImmutableMap 再进行一次包装呢,将其转换成java自带的HashMap不就行了吗?代码:
Map<String, TestVo> cachedMaps = new HashMap<>();
cachedMaps.putAll(cachedService.getTestData());
就是这么简单。运行通过,压测毛问题。
上最后压测数据:
./wrk -t8 -c20 -d15 http://192.168.139.1:8011/map
Running 15s test @ http://192.168.139.1:8011/map
8 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 31.29ms 13.02ms 161.88ms 77.26%
Req/Sec 64.60 13.16 110.00 60.99%
7760 requests in 15.09s, 618.94MB read
Requests/sec: 514.11
Transfer/sec: 41.01MB
QPS提升了一百多。。。,差不多有50%的提升,开心开心,以上就是本人代码优化的一些过程,碰到的问题以及一些解决方法