java序列化之-性能优化

  在最近的计划中,打算看看在不使用google protobuf的情况下,在原有的采用jackson作为json序列化工具的基础上,是否可以实现进一步的性能优化。主要是针对list的情况,在一些包含比较大的对象比如有上百个对象的列表序列化、反序列化的逻辑中,有一个序列化+反序列化操作,他们加起来时间占据了接近1/3,由此可见为了达到高TPS,序列化的性能和大小也是不可忽视的

  测试的时候选择了一个50个字段的对象,采用50条记录的list作为例子。因为大部分还都是可控的系统rpc交互,所以测试的时候选择了将字段用逗号分隔的方式。

  在反射机制中,Reflection和BeanInfo两种均作了测试,method/field都做了缓存的前提,结果中与原生jackson序列化、反序列化性能相差在20%以内,并无明显的提升。

  同时,在测试中将class缓存后性能提升约10%,将setAccessible设置为true后性能提升也在10%左右。字符串的解析并没有太大的性能消耗,反而是invoke消耗了绝大部分的性能。

  所以,至少就原生的Java反射机制而言,性能并没有明显的提升(当然,性能跟字段数还是有一定的关系,如果字段数较少、行数没有那么多,那么class每次加载的比例可能会增加)。

  除了json外,还可以考虑kryo序列化方式,其性能比json高出不少,同时没有pb/flatbuffer一样的额外结构维护要求。如下:

        // 序列化、反序列化性能测试
        UserRole userRole = new UserRole();
        userRole.setRoleId(1L);
        Role role = new Role();
        role.setRoleCode("roleCode");
        role.setCurrentUserId(1L);
        role.setSystemRoleFlag(true);
        role.setTenantCode("ta");
        userRole.setRole(role);
        User user =new User();
        user.setPassPercent(10);
        user.setAddress("wwwfw");
        user.setPassModifytime(new Date());
        userRole.setUser(user);
        UserRole newUserRole = null;
        // kryo序列化
        long beg = System.currentTimeMillis();
        for (int i=0;i<100000;i++) {
            newUserRole = deserialize(serialize(userRole));
        }
        System.out.println(System.currentTimeMillis()-beg); // 1443毫秒
        // jdk序列化
        beg = System.currentTimeMillis();
        for (int i=0;i<100000;i++) {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream os = new ObjectOutputStream(bos);
            os.writeObject(userRole);
            os.flush();
            os.close();
            byte[] b = bos.toByteArray();
            bos.close();
            ByteArrayInputStream bis = new ByteArrayInputStream(b);
            ObjectInputStream is = new ObjectInputStream(bis);
            newUserRole = (UserRole) is.readObject();
        }
        System.out.println(System.currentTimeMillis()-beg); // 8000毫秒

        // TODO 自定义序列化,非性能极端场景不要使用,太不够灵活

        // json序列化
        beg = System.currentTimeMillis();
        for (int i=0;i<100000;i++) {
            newUserRole = JsonUtils.json2Object(JsonUtils.toJson(userRole),UserRole.class);
        }
        System.out.println(System.currentTimeMillis()-beg); // 3934毫秒
        // 性能测试结束

  对于序列化来说,要考虑的是兼容性。在开发中,增减字段是常见的事,由于我们会使用大量的缓存,因此如何在结构变化后保持兼容性非常重要的。对于JDK序列化来说,只要serialVersionUID相同,就可以无缝处理。如下:

@Getter@Setter
public class ShareDetail implements Serializable {
    private static final long serialVersionUID = 1000L;

    private String tableName;
    private String status;
    // 动态改变
    private Date date;
}
        // 结构兼容性测试
        ////        ShareDetail sh = new ShareDetail();
//        sh.setTableName("t");
//        sh.setStatus("s");
//        FileOutputStream bos = new FileOutputStream("d:\javaserial.dat");
//        ObjectOutputStream os = new ObjectOutputStream(bos);
//        os.writeObject(sh);
//        os.flush();
//        os.close();
//        bos.close();
        // 读,新增Date字段
        FileInputStream bis = new FileInputStream("d:\javaserial.dat");
        ObjectInputStream is = new ObjectInputStream(bis);
        // 没有serialVersionUID的话,报Exception in thread "main" java.io.InvalidClassException: com.hundsun.ta.base.service.ShareDetail; local class incompatible: stream classdesc serialVersionUID = 161953476132204792, local class serialVersionUID = 3990225612413777011
        ShareDetail sh = (ShareDetail) is.readObject();
        System.out.println(sh.getStatus());
/**
     * Kryo本身非线程安全,需要使用线程本地变量,也可以线程池,参见https://www.jianshu.com/p/f56c9360936d
     */
    private static ThreadLocal<Kryo> kryo = new ThreadLocal<Kryo>() {
        @Override
        protected Kryo initialValue() {
            Kryo kryo = new Kryo();
            kryo.setDefaultSerializer(CompatibleFieldSerializer.class);
            return kryo;
        }
    };

    /**
     * 注意越界 https://m.2cto.com/kf/201612/573016.htmlhttps://www.jianshu.com/p/43008038866c
     * @param o
     * @return
     */
    public static byte[] serialize(Object o) {
        Output output = new Output(4096,65536);
        kryo.get().writeClassAndObject(output, o);
        byte[] bytes = output.toBytes();
        output.flush();
        output.close();
        return bytes;
    }

    /**
     * T 定义修改后,默认会报Exception in thread "main" com.esotericsoftware.kryo.KryoException: Encountered unregistered class ID: 13994
     * 需使用CompatibleFieldSerializer序列化器。在目标类上增加@DefaultSerializer(CompatibleFieldSerializer.class)也行
     * 字段顺序的变化不会导致反序列化失败,因为写入的时候会排序
     * 相关其他限制可以参考https://www.cnblogs.com/hntyzgn/p/7122709.html
     * @param bytes
     * @param <T>
     * @return
     */
    public static  <T> T deserialize(byte[] bytes) {
        Input input = new Input(bytes);
        T o = (T)kryo.get().readClassAndObject(input);
        return o;
    }

// 使用CompatibleFieldSerializer序列化支持向前或向后兼容。

        // kryo
//        FileOutputStream bos = new FileOutputStream("d:\kryo.dat");
//        ShareDetail sh = new ShareDetail();
//        sh.setTableName("t");
//        sh.setStatus("s");
//        bos.write(serialize(sh));
//        bos.flush();
//        bos.close();

        FileInputStream bis = new FileInputStream("d:\kryo.dat");
        byte[] buffer = new byte[74];
        int len = bis.read(buffer);
        ShareDetail sh = deserialize(buffer);
        System.out.println(sh.getStatus());
原文地址:https://www.cnblogs.com/zhjh256/p/6306103.html