使用Protostuff来进行深拷贝操作

在上篇中,说到了 如何解决@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%的提升,开心开心,以上就是本人代码优化的一些过程,碰到的问题以及一些解决方法

首先,需要在前端项目中引入 `protostuff` 库。可以通过 npm 安装或者使用 `<script>` 标签引入。 接下来,需要定义用于序列化和反序列化的数据结构。可以使用 protobuf 定义数据结构,然后通过 `protostuff` 的 `Message` 类来创建 JavaScript 类。 例如,假设我们有一个 protobuf 文件定义了一个 `Person` 消息类型: ```protobuf syntax = "proto3"; message Person { string name = 1; int32 age = 2; } ``` 我们可以使用 `protoc` 编译器来生成对应的 JavaScript 代码: ``` protoc --js_out=import_style=commonjs,binary:. person.proto ``` 然后在前端项目中引入生成的代码: ```javascript const ProtoBuf = require('protobufjs'); const proto = require('./person_pb'); // create a Person message const person = new proto.Person(); person.setName('Alice'); person.setAge(30); // serialize the message to a Uint8Array const buffer = proto.Person.encode(person).finish(); // deserialize the message from a Uint8Array const decodedPerson = proto.Person.decode(buffer); console.log(decodedPerson.getName()); // output: "Alice" console.log(decodedPerson.getAge()); // output: 30 ``` 在这个例子中,我们使用 `Person` 类来创建一个 `person` 对象,并设置其属性。然后将其序列化为一个 `Uint8Array`,并在反序列化时使用 `Person` 类来解码该数组并还原为 `decodedPerson` 对象。 需要注意的是,`protostuff` 库本身并不支持在浏览器中使用,因为它依赖于 Node.js 的 `Buffer` 类。但是可以使用一些类似 `buffer` 的库,如 `typedarray-to-buffer` 或 `base64-arraybuffer`,将 `Uint8Array` 转换为 `buffer`,然后再进行序列化和反序列化操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值