ActiveMQ系列—ActiveMQ性能优化(下2)(消息存储方案 LevelDB)

10、KahaDB存储方案

LevelDb是能够处理十亿级别规模Key-Value型数据持久性存储的C++ 程序库,由Google发起并开源。LevelDB只能由本操作系统的其他进程调用,所以它不具有网络性。如果您需要网络上的远程进程操作LevelDB,那么就要自行封装服务层。

10.1、LevelDB基本结构

LevelDB中的核心设计算法是跳跃表(Skip List),核心操作策略是对磁盘上的数据日志结构进行归并(LSM)。跳跃表实际上是二叉平衡树的一种变形结构,它通过将一个有序链表进行“升维”操作,从而减少每一层上需要遍历的数据数量,达到快速查找的目的。下图示意了一个跳跃表结构(在实际工作中,跳跃表的层级和“升维”策略的不同,跳跃表的结构也不一样):

这里写图片描述

您可以将上图中的每个元素节点,想象成每一条消息的key值。为了讲解方便,上图中我将拥有全部数据的元素的跳跃层称为Level 2(最高层),但实际上规范的跳跃表结构中,拥有全部元素的层次称为Level 0(最底层)。跳跃表的结构并非一成不变,当有一条新的记录需要插入到结构时,可能会引起表中的多个Level都发生变化。

那么LevelDB是如何应用跳跃表结构的?又是如何进行归并操的?我们首先来看看LevelDB的简要结构:

这里写图片描述

Log文件

当LevelDB收到新的消息是会同步写两个地方:内存中的MemTable区域和磁盘上的Log文件。直接写Log文件是为了在系统异常退出并重启时,能够将LevelDB恢复到退出前的结构;那么有的读者会问,由于是直接写磁盘会不会成为性能瓶颈呢?答案是,LevelDB的log文件操作采用预占磁盘空间(默认为100MB),进行顺序写的方式。并且这个过程可以设置为异步的(当然如果设置成异步的,可能需要接受异常情况下数据丢失的风险)。

MemTable和Immutable

LevelDB还写将消息写入内存的MemTable区域,MemTable区域的的数据组织结构就是跳跃表(Skip List),这样的数据组织结构可以在读取内存中信息的时候,快速完成信息定位。当MemTable区域的数据量达到indexWriteBufferSize属性设置的大小时(默认为6MB),LevelDB就会把这个MemTable区域标记为Immutable,并开启一个新的MemTable区域。一定注意,是标记为Immutable,而不是把MemTable区域的数据拷贝到某一个Immutable区域。

新标记的Immutable区域中的数据会被执行Compact操作,从而写入到磁盘上的.sst文件中。所谓Compact操作是指:LevelDB会剔除Immutable区域中那些已经被标示为“删除”的数据(成功消费的数据就会被标记为“删除”),排除那些格式错误的数据,并可能进行数据压缩。

SSTable文件

SSTable文件是指存在于硬盘上,后缀名为.sst的文件。这些文件是LevelDB磁盘上最重要的数据记录文件,每一个SSTable文件的默认大小为2MB,也就是说LevelDB的文件夹下会有很多的.sst文件。SSTable文件并不是顺序写的,而是按照数据的key排序进行随机写,所以SSTable文件无需预占存储磁盘存储空间。

借鉴于跳跃表的设计思想,SSTable文件也是分层次的。每一层可存储的数据量是上一层的的10倍。举个例子,第Level 2层可存储的数据量80MB,那么第Level 3层可存储的数据量就是800MB。当某一层可存储的数据量达到最大值,LevelDB就会从当层选取一个.sst文件,向下层做Compact操作,由于来自于上层的新数据,所以下层的.sst文件内容将产生变化(上文说过,.sst文件中的内容是按照数据的key排序的)。

每一个SSTable文件,由多个Block块构成(默认大小为4KB),block块是LevelDB读写磁盘上SSTable文件的最小单元。每一个SSTable文件最后一个Block块称为Index Block,它指明了SSTable文件中每一个Data block的起始位置。

但是每次读取某个Block块时,如果都在磁盘上先去找Index Block,然后再根据其中记录的index,找到Block在文件的起始位置的话,查找效率显然不高。所以LevelDB的内存区域中,有一个称为Block Cache的区域。这个区域存储着众多的Index Block,这样就不需要到磁盘上查找Index Block了。

Manifest

那么众多的.sst文件是如何被管理的呢?要知道如果在众多.sst文件中进行某条消息的查找时,如果将某一层的.sst文件全部进行遍历,那么性能肯定是不能接受的。在LevelDB中有一类文件被称为Manifest,这些Manifest文件记录了sst文件的关键信息,包括(但不限于):某个.sst文件属于哪一个Level、这个.sst文件中最小的key值、这个.sst文件中最大的key值。

10.2、在ActiveMQ中配置LevelDB

在ActiveMQ中配置使用LevelDB作为持久化存储方案实际上很简单,使用主配置文件中的persistenceAdapter标记就可以完成。最简配置如下所示:

......
<broker xmlns="http://activemq.apache.org/schema/core"  brokerName="localhost" dataDirectory="${activemq.data}">
    ......
    <persistenceAdapter>
        <levelDB directory="${activemq.data}/levelDB"/>
    </persistenceAdapter>
    ......
</broker>
......

以上示例配置中,directory属性表示LevelDB的结构文件所放置的目录位置。请注意,由于log文件是顺序写的机制,所以log文件也会预占磁盘空间,并且log文件默认的大小就是100MB。那么只要生成一个log文件,就至少会占据100MB的存储空间(但这不代表总的已使用量)。也就是说,如果您将主配置文件中storeUsage标记的limit属性设置为200mb,那么透过ActiveMQ管理界面看到的现象就是:只要有任何一条PERSISTENT Message被接受,Store percent used立刻就会变成50%。如果您将storeUsage标记的limit属性为100mb,那么只要有任何一条PERSISTENT Message被接受,ActiveMQ服务端的Producer Flow Control策略就会立刻开始工作。

所以一定不要吝啬分配memoryUsage、storeUsage。依据您的团队在生产环境下的存储方案,也可以通过logSize属性改变LevelDB中单个log文件的大小。如下示例:

......
<!-- 限制成50mb -->
<persistenceAdapter>
    <levelDB directory="${activemq.data}/levelDB" logSize="52428800"/>
</persistenceAdapter>
......

上一小节我们介绍到,默认的LevelDB存储策略中,当ActiveMQ接收到一条消息后,就会同步将这条消息写入到log文件中,并且同时在内存区域向Memtable写入位置索引。通过配置您也可以将这个过程改为“异步”:

......
<!-- 改为异步写log文件 -->
<persistenceAdapter>
    <levelDB directory="${activemq.data}/levelDB" logSize="52428800" sync="false"/>
</persistenceAdapter>
......

以下列表展示了您可以使用的LevelDB的配置属性,使用“*”标识出来的属性是笔者认为重要的配置项:

property namedefault valueComments
*directoryLevelDB数据文件的存储目录
synctrue是否进行磁盘的同步写操作
*logSize104857600 (100 MB)log日志文件的最大值
verifyChecksumsfalse是否对从文件系统中读取的数据进行强制校验
paranoidChecksfalse如果LevelDB检测到数据错误,则尽快将错误在存储位置进行标记
indexFactoryorg.fusesource.leveldbjni.JniDBFactory, org.iq80.leveldb.impl.Iq80DBFactory创建LevelDB时使用的工厂类,由于LevelDB的本质是C++程序库,所以Java是通过Jni进行底层调用的
*indexMaxOpenFiles1000可供索引使用的打开文件的数量,这是因为Level内部使用了多线程进行文件读写操作
indexWriteBufferSize6291456 (6 MB)内存MemTable的最大值,如果MemTable达到这个值,就会被标记为Immutable
indexBlockSize4096 (4 K)每个读取到内存的SSTable——Index Block数据的大小
*indexCacheSize268435456 (256 MB)使用一个内存区域记录多个Level中,SSTable——Index Block数据,以便读操作时,不经过遍历就可直接定位数据在某个level中的位置,建议增大该区域
indexCompressionsnappy适用于索引块的压缩类型,影响Compression策略
logCompressionnone适用于日志记录的压缩类型,影响Compression策略
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值