文章首发于:clawhub.club


这几天一直研究怎么将LMDB作为持久化队列,今儿终于出来了一个简单的版本,以前用到的本地持久化队列是BDB封装的,虽然已经很稳定了,但是技术就应该有好的点子就去尝试一下,万一真的很不错呢。

经过简单的测试,效果真的还是可以的:

入队文件:110,592 字节;时间单位为纳秒。
//不手动执行 bdbQueue.sync()方法,测试结果
bdb offer 耗时:303738
lmdb offer 耗时:323650
bdb poll 耗时:446820
lmdb poll 耗时:42868
//手动执行 bdbQueue.sync()方法,测试结果
bdb offer 耗时:6290361
lmdb offer 耗时:386631
bdb poll 耗时:432605
lmdb poll 耗时:67527

从结果中可以看出,lmdb的读性能确实很好,写性能与BDB基本持平(当然是不手动执行DBD的sync命令,同步写入磁盘)。

封装的队列源码已经上传至github:https://github.com/ClawHub/queue-db,感兴趣的话可以看看,欢迎评论交流,互相学习。

简单介绍一下LMDB持久化队列封装的思路:

  • 采用Kryo序列化框架
  • 通过lmdb维护一个首指针(当前我只建了一个lmdb库,其实可以建另一个库专门存储一些和用户数据无关的元数据,比如首指针)
  • lmdb时key-val数据库,值肯定是序列化之后的队列元素,键为Long类型的整数
  • 系统启动后,首先查看lmdb中所有元素的数量,再将首指针的值与首指针所占用的一个位置考虑进来,维护一个内存变量:尾指针,这个用来作为插入数据的key。
  • offer使用了AtomicLong所封装的尾指针来标记key的位置,所以不需要上锁。
  • poll与peek使用了synchronized修饰。
  • 操作lmdb时要应用事务,需要手动提交(封装好了)。
  • 项目中也简单的写了几个单元测试,可以用来参考。

贴一段代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Offer boolean.
*
* @param item the item
* @return the boolean
*/
@Override
public boolean offer(E item) {
//获取一个指针位
long last = lastIndex.getAndIncrement();
//序列化
try (Output out = new Output(0, 20971520)) {
//获取序列化器
Kryo kryo = KryoPoolFactory.INSTANCE.getPool().borrow();
//序列化
kryo.writeClassAndObject(out, item);
//释放序列化器
KryoPoolFactory.INSTANCE.getPool().release(kryo);
//入库
dbi.put(ByteBufferUtil.longToByteBuffer(last), ByteBufferUtil.bytesToByteBuffer(out.getBuffer()));
//数量+1
entries.incrementAndGet();
return true;
} catch (Exception e) {
logger.error("lmdb offer fail." + e);
return false;
}
}