深克隆插件 -- FKC

年前没机会分享我写的插件了,这里也做个总结吧:

1.深克隆有几种实现方法:


(1)最low的,大家都会的,get set get set get set巴拉巴拉。(缺点:显而易见,不通用,而且代码非常的丑陋!)

(2)对象流读入,対向流读出,这其实就是一个序列化反序列化。(缺点:流是非常占用资源的,不停地new close会消耗CPU,一个请求过来要进行几次new操作,会导致GC非常频繁,还有就是原生速度比较慢)

2.针对以上第二种实现方法,设计了一个深克隆的插件,特点和实现如下:


(1)针对原生序列化反序列化慢,使用kryo做序列化反序列化处理,kryo底层其实还是用了原生的流,只不过进行了一些特殊处理,首先,它存储空间更小,字段的存储是变长的,跟mysql的varchar和char一样,还有就是针对string做了特殊的结束标识符(最后一位byte+x70),kryo在初始化的时候提前加载了一些类的解析器,比如ArrayList甚至enum类型的解析器。对于序列化过的类,kryo会保存类的信息,下次再进行序列化的时候,会很快。

(2)针对流的生命周期,很容易也很方便的一种方式就是对象池,由对象池来管理stream的生命周期。选用的是apache的commons-pool里的genericObjectPool。这个池会自动的管理对象,比于线程池来说这个玩意儿更符合一个池的定义,用的时候从里面borrow一个,用过之后return返还一个。

下面简单的说一下genericObjectPool的底层实现:底层是一个queue,期中维护的是IDLE OBJ(就是空闲的对象),还有一个map,来维护所有对象,毕竟一个对象借走,还是要归还的,那么所有对象都要被池管理,自然需要一个管理所有对象的集合。

那么这个池的工作原理是怎样的呢。创建这个池的时候需要制定一个对象工厂,这个工厂不止维护对象的创建,还负责进行销毁、激活、失效等操作。类似工厂的流水线,一个产品生产出来,需要检查质量,一个产品销毁掉,需要检查是否能销毁等。

borrow的时候调用工厂的相应方法来创建并且验证和激活一个对象。

return的时候会调用工厂的相应方法来验证对象是否可用,如果池中对象过多,会由工厂来销毁对象。

个性化定制的参数,在具体的config中

(3)kryo生命周期的管理:这里没有用池,而是实现了一个更轻量级的,也是kryo官方推荐的缓存模式,我是使用了一个队列来维护kryo。这个队列维护的是软引用(这里有兴趣的同学可以想一下为什么我不用弱引用而使用软引用)。同样,用的时候borrowOne,用完重置之后returnOne。

(4)其他相关设计:这里由于我考虑到自身的low逼水平以及实现方法多样,还是用SPI做了一个可扩展。大家可以自己实现Incubator的接口来自定义孵化器(为什么用孵化器这个名字,就是因为传入一个对象,生成一个对象,传入的对象好比生成对象的双亲)默认使用的是defaultIncubator,如果需要改,要在META-INF/services中写入你自己的incubator实现

3.用法:


1
2
3
A a = new A();
Incubator<A> incubator = IncubatorFactory.INSTANCE.getDefaultIncubator();
A theNewOne = incubator.born(a);

4.现阶段的开发情况


年前不上线,还没有完全完善,年后会出一个1.0.0版本,最好经过压测(主要是调整pool的相关参数),就可以在项目中使用了~


年后的第一版本正式完成并通过测试,期间修复和重新设计一些问题:

(1)对象流废弃。

在之前的设计中是使用对象流 + kryo混合方式完成,在后面实现过程中发现并不可取,这里换成完全使用kryo的方式实现

(2)缓存kryo的kryoPool实现了两种方式的实现。

第一种:仍然是queue的实现方式,这种方式比较轻量,但是不如第二种方式更加完善(默认采用这种方式).

第二种:二次封装了apache-commons的genericObjectPool,并加入了防泄漏策略,调用方可以自己定义一些防泄漏策略。

(3)注释完善

(4)关于使用apache-commons实现kryoPool时的一些自定义扩展:

以cart-soa的使用场景为例,该插件主要是用来深克隆Item。在插件的poolobjfactory包中实现了一个Adapter,那么建议自己实现一个类集成自Adapter,然后重写掉makeObject方法。

1
2
3
4
5
6
@Override
public PooledObject<Kryo> makeObject() throws Exception {
Kryo kryo = new Kryo();
kryo.register(Item.class);
return new DefaultPooledObject<>(kryo);
}

这就在kryo生产出来的时候直接在kryo中注册了Item的class信息,通过阅读源码,这样可以在第一次使用这个生产出来的kryo时,减少操作。

如果有需要可以自定义Serializer(继承自kryo的包中的Serializer),大部分情况下(包括默认情况下)使用的都是kryo中提供的FieldSerializer,所以可以进一步把代码给处理成这样:

1
2
3
4
5
6
@Override
public PooledObject<Kryo> makeObject() throws Exception {
Kryo kryo = new Kryo();
kryo.register(Item.class, new FieldSerializer(kryo, Item.class));
return new DefaultPooledObject<>(kryo);
}