2023-07-29
原文作者:说好不能打脸 原文地址:https://yinwj.blog.csdn.net/article/details/50698695

7、ActiveMQ的持久消息存储方案

前文已经讲过,当ActiveMQ接收到PERSISTENT Message消息后就需要借助持久化方案来完成PERSISTENT Message的存储。这个介质可以是磁盘文件系统、可以是ActiveMQ的内置数据库,还可以是某种外部提供的关系型数据库。本节笔者将向读者讲解三种ActiveMQ推荐的存储方案的配置使用。

202307292153409821.png

  • 如上图2.1的步骤所示, 所有PERSISTENT Message都要执行持久化存储操作,持久化存储操作方案的性能直接影响着整个MQ服务端的PERSISTENT Message吞吐性能 。另外NON_PERSISTENT Message虽然不会进行持久化存储,但是NON_PERSISTENT Message也不是永远都只存在与内存区域。
  • 有的读者会问,Topic模式的工作队列在没有任何活动订阅者的情况下也会对PERSISTENT Message进行持久化存储吗?当然会,因为Topic模式的工作队列还要考虑“Durable Topic Subscribers”形式的订阅者。即使没有“Durable Topic Subscribers”形式的订阅者,先存储再标记的过程也不会改变(只是不一定真正进入物理磁盘)。
  • 有的读者还会问,在客户端启动事务的情况下,如果没有事务的commit操作,PERSISTENT Message也会进行持久化存储吗?当然还是会,前文我们已经讲过没有做事务的commit只是说这些事务中的消息不会进行确认操作,不会分发到某个指定的具体队列中;但是 只要使用了send方法,PERSISTENT Message就会被发送到服务端,就会进行持久化存储操作
  • 如上图中被标示为2.2的操作步骤所示,在ActiveMQ设置的持久化方案完成某条消息的持久化后,会在ActiveMQ服务节点的内部发出一个“完成”信号。这是为了告诉ActiveMQ服务节点自己,是否可以进行下一步操作。但是为了加快ActiveMQ服务节点内部的处理效率,这个过程可以设置为“异步”。
  • 那么进行了持久化存储的PERSISTENT Message什么时候被删除呢?就如同之前我们提到的一样,ActiveMQ服务端只有在收到消费者端某一条消息或某一组消息的ACK标示后,才会认为消息被消费者端正确处理了。就是在这个时候,ActiveMQ会通知持久化方案,进行删除这一条或者这一组消息的操作,并空闲出相应的存储空间。如上图被标示为5.1的操作步骤所示。

7-1、存储方案配置

在介绍ActiveMQ的存储方案之前,首先需要明确的是ActiveMQ中的几种“容量”描述:

  • ActiveMQ的内核是Java编写的 ,也就是说如果服务端没有Java运行环境ActiveMQ是无法运行的。ActiveMQ启动时, 启动脚本使用wrapper包装器来启动JVM 。JVM相关的配置信息在启动目录的“wrapper.conf”配置文件中。各位读者可以通过改变其中的配置项,设置JVM的初始内存大小和最大内存大小(当然还可以进行其他和JVM有关的设置,例如开启debug模式),如下:
    ......
    
    # Initial Java Heap Size (in MB)
    wrapper.java.initmemory=100
    
    # Maximum Java Heap Size (in MB)
    wrapper.java.maxmemory=512
    
    ......

以上配置项设置JVM的初始内存大小为100MB,设置JVM的最大内存大小为512MB。如果您在更改后使用console参数启动ActiveMQ,那么会看到当前ActiveMQ的JVM设置发生了变化:

202307292153416942.png

  • 明确了ActiveMQ的内存区域来源,才好理解后续的设置内容。ActiveMQ每一个服务节点都是一个独立的进程。在ActiveMQ主配置文件中,读者可以找到一个“systemUsage”标记,类似定义如下:
    <systemUsage>
        <systemUsage>
            <memoryUsage>
                <memoryUsage percentOfJvmHeap="70" />
            </memoryUsage>
            <storeUsage>
                <storeUsage limit="100 gb"/>
            </storeUsage>
            <tempUsage>
                <tempUsage limit="50 gb"/>
            </tempUsage>
        </systemUsage>
    </systemUsage>

systemUsage:该标记用于设置整个ActiveMQ节点在进程级别的各种“容量”的设置情况。其中可设置的属性包括:sendFailIfNoSpaceAfterTimeout,当ActiveMQ收到一条消息时,如果ActiveMQ这时已经没有多余“容量”了,那么就会等待一段时间(这里设置的毫秒数),如果超过这个等待时间ActiveMQ仍然没有可用的容量,那么就拒绝接收这条消息并在消息的发送端抛出javax.jms.ResourceAllocationException异常;sendFailIfNoSpace,当ActiveMQ收到一条消息时,如果ActiveMQ这时已经没有多余“容量”了,就直接拒绝这条消息(不用等待一段时间),并在消息的发送端抛出javax.jms.ResourceAllocationException异常。

memoryUsage:该子标记设置整个ActiveMQ节点的“可用内存限制”。这个值不能超过上文中您设置的JVM maxmemory的值。其中的percentOfJvmHeap属性表示使用“百分数值”进行设置,除了这个属性以外,您还可以使用limit属性进行固定容量授权,例如:limit=”1000 mb”。这些内存容量将供所有队列使用。

storeUsage:该标记设置整个ActiveMQ节点,用于存储“持久化消息”的“可用磁盘空间”。该子标记的limit属性必须要进行设置。在使用后续介绍的KahaDB方案或者LevelDB方案进行PERSISTENT Message持久化存储时,这个storeUsage属性都会起作用;但是如果使用数据库存储方案,这个属性就不会起作用了。

tempUsage:在ActiveMQ 5.X+ 版本中,一旦ActiveMQ服务节点存储的消息达到了memoryUsage的限制,NON_PERSISTENT Message就会被转储到 temp store区域。虽然我们说过NON_PERSISTENT Message不进行持久化存储,但是 ActiveMQ为了防止“数据洪峰”出现时NON_PERSISTENT Message大量堆积致使内存耗尽的情况出现,还是会将NON_PERSISTENT Message写入到磁盘的临时区域——temp store 。这个子标记就是为了设置这个temp store区域的“可用磁盘空间限制”。最后提醒各位读者storeUsage和tempUsage并不是“最大可用空间”,而是一个阀值。

7-2、ActiveMQ中持久化存储方案的演化

说到ActiveMQ中持久化存储方案的演化问题,如果您仔细阅读ActiveMQ官方文档中关于持久化部分的描述,您就不难发现ActiveMQ的开发团队在针对持久化性能问题的优化上可谓与时俱进。这也符合一款健壮软件的生命周期特征:任何功能特性都在进行不断累计完善:

从最初的AMQ Message Store方案,到ActiveMQ V4版本中推出的High performance journal(高性能事务支持)附件 ,并且同步推出了关于关系型数据库的存储方案。ActiveMQ 5.3版本中又推出了对KahaDB的支持(V5.4版本后称为ActiveMQ默认的持久化方案),后来ActiveMQ V5.8版本开始支持LevelDB,到现在,V5.9+版本提供了标准的Zookeeper+LevelDB集群化方案。下面我们重点介绍一下ActiveMQ中KahaDB、LevelDB和关系型数据库这三种持久化存储方案。并且会和读者一起,使用Zookeeper搭建LevelDB集群存储方案。

对于最初的AMQ Message Store方案,ActiveMQ官方已不再推荐使用(实际上在笔者的实际工作中,也不会使用AMQ Message Store)。如果各位读者想进行了解可以自行搜索相关资料,这里不再进行介绍。

7-3、KahaDB存储方案

7-3-1、KahaDB基本结构

KahaDB is a file based persistence database that is local to the message broker that is using it. It has been optimised for fast persistence and is the the default storage mechanism from ActiveMQ 5.4 onwards. KahaDB uses less file descriptors and provides faster recovery than its predecessor, the AMQ Message Store.

以上引用自Apache ActiveMQ 官方对KahaDB的定义。首先KahaDB基于文件系统,其次KahaDB支持事务。在ActiveMQ V5.4版本及后续版本KahaDB都是ActiveMQ的默认持久化存储方案。最后Apache ActiveMQ官方表示它用来替换之前的AMQ Message Store存储方案。

KahaDB主要元素包括:一个内存Metadata Cache用来在内存中检索消息的存储位置、若干用于记录消息内容的Data log文件、一个在磁盘上检索消息存储位置的Metadata Store、还有一个用于在系统异常关闭后恢复Btree结构的redo文件。如下图所示(官网引用):

202307292153423213.png

以下是KahaDB在磁盘文件上的现实展示。注意,可能您查看自己测试实例中所运行的KahaDB,看到的效果和本文中给出的效果不完全一致。例如您的data log文件可能叫db-1.log,也有可能会多出一个db.free的文件,但是这些都不影响我们对文件结构的分析:

    [root@localhost KahaDB]# ll -h
    总用量 29M
    -rw-r--r--. 1 root root  32M 4月   7 04:53 db-3.log
    -rw-r--r--. 1 root root 7.6M 4月   7 04:53 db.data
    -rw-r--r--. 1 root root 2.8M 4月   7 04:53 db.redo
    -rw-r--r--. 1 root root    8 4月   7 04:50 lock
  • db-3.log:这个文件就是我们上文提到的Data log文件。一个KahaDB中,可能同时存在多个Data log文件,他们存储了每一条持久化消息的真正内容。这些Data log文件统一采用db-*.log的格式进行命名,并且每个Data log文件默认的大小都是32M(当然是可以进行设置的) 。当一个Data log文件中的所有消息全部被成功消息后,这个Data log文件会在Metadata Cache中被标记为删除,并在下个checkpoint周期进行删除操作。
  • 各位读者可能已经注意到一个现象:为什么db-3.log的默认占用大小就是32M,但是目录显示的“总用量”却只有29M呢?在这个文件夹中,除了db-3.log文件本身,加上其他几个文件所占用的大小,已经远远超过了32M!这是因为,为了加快写文件的性能, Data log文件采用顺序写的方式进行操作,为了保证文件使用的扇区在物理上是连续的,所以Data log文件需要预占这些扇区 (这个和Hadoop中每一个block大小都是固定的原因相似)。虽然您看到Data log文件占用的32M的磁盘空间,但是这些磁盘空间并没有全部使用。另外,关于随机读写和连续读写的巨大性能差异,我会在今年下半年新的“数据存储专栏”中,进行详细介绍。
  • 为了更快的找到某个具体消息在Data log文件中的具体位置。消息的位置索引采用BTree的结构被存储在内存中,这个内存区域就是上文提到的Metadata Cache(大小也是可以设置的)。要知道Mysql的Innodb 存储引擎也是采用BTree结构构造索引结构(用了都说快哦~~)。所以一般情况下,只要某个队列有活动的消费者存在,消息的定位、读取操作是可以很快完成的。
  • 内存中没有被处理的消息索引会以一定的周期(或者一定的数量规模)为依据,同步(checkpoint)到Metadata Store中 ,这就是我们在上文中看到的“db.data”文件。当然db.redo文件也会被更新,以便在ActiveMQ服务节点在重启后对Metadata Cache进行恢复。最后,消息同步(checkpoint)依据,可以在ActiveMQ的主配置文件中进行设置。

7-3-2、在ActiveMQ中配置KahaDB

由于在ActiveMQ V5.4+的版本中,KahaDB是默认的持久化存储方案。所以即使您不配置任何的KahaDB参数信息,ActiveMQ也会启动KahaDB。这种情况下,KahaDB文件所在位置是您的ActiveMQ安装路径下的/data/${broker.Name}/KahaDB子目录。其中${broker.Name}代表这个ActiveMQ服务节点的名称。

正式的生产环境还是建议您在主配置文件中明确设置KahaDB的工作参数。如下所示:

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

以上配置项设置使用kahaDB为持久化存储方法,并且设置kahaDB的工作目录为ActiveMQ安装路劲下/data/kahadb目录。如果您需要Data log文件默认的32M的大小,可以使用journalMaxFileLength属性进行设置,如下所示:

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

您还可以设置为:当Metadata Cache中和Metadata Store中不同的索引条数达到500条时,就进行checkpoint同步。如下所示:

    ......
    <broker xmlns="http://activemq.apache.org/schema/core"  brokerName="localhost" dataDirectory="${activemq.data}">
        ......
        <persistenceAdapter>
            <kahaDB directory="${activemq.data}/kahadb" journalMaxFileLength="64mb" indexWriteBatchSize="500"/>
        </persistenceAdapter>
        ......
    </broker>
    ......

以下表格为读者示例了KahaDB中所有的配置选项和其含义(引用自网络,加“*”部分是笔者认为重要的配置选项):

propertyname defaultvalue Comments
*directory activemq-data 消息文件和日志的存储目录
*indexWriteBatchSize 1000 当Metadatacache区域和Metadatastore区域不同的索引数量达到这个值后,Metadatacache将会发起checkpoint同步
*indexCacheSize 10000 内存中,索引的页大小。超过这个大小Metadatacache将会发起checkpoint同步
*enableIndexWriteAsync false 索引是否异步写到消息文件中,将以不要设置为true
*journalMaxFileLength 32mb 一个消息文件的大小
*enableJournalDiskSyncs true 如果为true,保证使用同步写入的方式持久化消息到journal文件中
*cleanupInterval 30000 清除(清除或归档)不再使用的db-*.log文件的时间周期(毫秒)。
*checkpointInterval 5000 写入索引信息到metadatastore中的时间周期(毫秒)
ignoreMissingJournalfiles false 是否忽略丢失的journal文件。如果为false,当丢失了journal文件时,broker启动时会抛异常并关闭
checkForCorruptJournalFiles false 检查消息文件是否损坏,true,检查发现损坏会尝试修复
checksumJournalFiles false 产生一个checksum,以便能够检测journal文件是否损坏。
  • 5.4版本之后有效的属性:
propertyname defaultvalue Comments
*archiveDataLogs false 当为true时,归档的消息文件被移到directoryArchive,而不是直接删除
*directoryArchive null 存储被归档的消息文件目录
databaseLockedWaitDelay 10000 在使用负载时,等待获得文件锁的延迟时间,单位ms
maxAsyncJobs 10000 等待写入journal文件的任务队列的最大数量。应该大于或等于最大并发producer的数量。配合并行存储转发属性使用。
concurrentStoreAndDispatchTopics false 如果为true,转发消息的时候同时提交事务
concurrentStoreAndDispatchQueues true 如果为true,转发Topic消息的时候同时存储消息的messagestore中
  • 5.6版本之后有效的属性:
propertyname defaultvalue Comments
archiveCorruptedIndex false 是否归档错误的索引到Archive文件夹下
  • 5.10版本之后有效的属性:
propertyname defaultvalue Comments
IndexDirectory 单独设置KahaDB中,db.data文件的存储位置。如果不进行设置,db.data文件的存储位置还是将以directory属性设置的值为准

7-4、LevelDB存储方案

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

7-4-1、LevelDB基本结构

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

202307292153430034.png

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

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

202307292153439145.png

  • 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值

7-4-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的配置属性,使用“*”标识出来的属性是笔者认为重要的配置项:

propertyname defaultvalue Comments
*directory “LevelDB” 数据文件的存储目录
sync true 是否进行磁盘的同步写操作
*logSize 104857600(100MB) log日志文件的最大值
verifyChecksums false 是否对从文件系统中读取的数据进行强制校验校验
paranoidChecks false 如果LevelDB检测到数据错误,则尽快将错误在存储位置进行标记
indexFactory org.fusesource.leveldbjni.JniDBFactory,org.iq80.leveldb.impl.Iq80DBFactory 创建LevelDB时使用的工厂类,由于LevelDB的本质是C++程序库,所以Java是通过Jni进行底层调用的
*indexMaxOpenFiles 1000 可供索引使用的打开文件的数量,这是因为Level内部使用了多线程进行文件读写操作
indexWriteBufferSize 6291456(6MB) 内存MemTable的最大值,如果MemTable达到这个值,就会被标记为Immutable
indexBlockSize 4096(4K) 每个读取到内存的SSTable——IndexBlock数据的大小
*indexCacheSize 268435456(256MB) 使用一个内存区域记录多个Level中,SSTable——IndexBlock数据,以便读操作时,不经过遍历就可直接定位数据在某个level中的位置,建议增大该区域
indexCompression snappy 适用于索引块的压缩类型,影响Compression策略
logCompression none 适用于日志记录的压缩类型,影响Compression策略

7-5、关系型数据库存储方案

从ActiveMQ V4版本开始,ActiveMQ就支持使用关系型数据库进行持久化存储——通过JDBC实现的数据库连接。可以支持的关系型数据库包括(但不限于):Apache Derby、DB2、HSQL、Informix、MySQL、Oracle、Postgresql、SQLServer、Sybase。

下面向各位读者演示如何为ActiveMQ配置Mysql数据库服务。前提是您已经某个网络位置准备好了Mysql服务,并可以成功进行远程登录。

    ......
    <broker>
        ......
        <!-- 配置ActiveMQ连接到Mysql服务 -->
        <!-- 记得去掉原来的KahaDB或者LevelDB的配置 -->
        <persistenceAdapter>
            <jdbcPersistenceAdapter dataSource="#mysql_datasource" createTablesOnStartup="true"/>
        </persistenceAdapter>
        ......
    </broker>
    
    <!-- 演示使用的是C3p0连接池,当然您也可以使用DBCP连接池 -->
    <!-- 就是spring的配置文件结构 -->
    <bean id="mysql_datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <property name="driverClass" value="com.mysql.jdbc.Driver"/>
      <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activemqdb?relaxAutoCommit=true&useUnicode=true&characterEncoding=utf-8"/>
      <property name="user" value="root"/>
      <property name="password" value="123456"/>
      <property name="minPoolSize" value="10"/>
      <property name="maxPoolSize" value="30"/>
      <property name="initialPoolSize" value="10"/>
    </bean>
    ......

在配置关系型数据库作为ActiveMQ的持久化存储方案时,要注意几个事项:

  • 配置信息建议放置在您的jetty.xml配置文件中,也可以放置在activemq.xml配置文件中。除此之外,还要记得需要使用到的相关jar文件放置到ActiveMQ安装路径下的./lib目录。例如使用mysql + c3p0的配置中,需要的jar包至少包括:mysql-jdbc驱动的jar包和c3p0的jar包。
  • 在jdbcPersistenceAdapter标签中,我们设置了createTablesOnStartup属性为true,这是为了在第一次启动ActiveMQ时,ActiveMQ服务节点会自动创建所需要的数据表。启动完成后,可以去掉这个属性,或者更改createTablesOnStartup属性为false。
  • 在配置和测试的过程中,您可以会遇到这样的问题:“java.lang.IllegalStateException: BeanFactory not initialized or already closed”这是因为您的操作系统的机器名中有“_”符号。更改机器名并且重启后,即可解决问题。

在同样的硬件资源条件下,相比KahaDB和LevelDB这样的“内存+存储介质”这样的持久化方案而言,使用关系型数据库作为ActiveMQ的持久化方案绝对不能说“性能”最好,但是在大多数情况下这个持久化方案也不会成为整个顶层架构的设计瓶颈(因为关系型数据库一般都有自己的热备和负载方案)。所以很多团队还是会使用这样的持久化方案,很大一部分原因就是这些团队对关系型数据库有更丰富的使用经验,且有专门的数据库管理人员。

7-6、如何选用持久化存储方案

以上我们介绍了三种持久化存储方案:KahaDB、LevelDB、关系型数据库。其中KahaDB和LevelDB的工作原理基本类似,都采用内存+磁盘介质的方案:内存用于存放信息的位置索引,磁盘介质上存放消息内容。而关系型数据库的方案,ActiveMQ将完全通过JDBC对数据库进行操作完成消息的存储和修改。

但是不是如网络上一些资料所说的那样,一定要对三种持久化存储方案的速度做比较后,选择最快的那种存储方案呢?这里面至少有两个误区:

  1. 某种存储方案的速度一定比另一种存储方案的速度快。
  2. 一定要选择速度快的那种存储方案。

下面我们进行详细的讨论:

  • 根据不同的硬件层配置,同一种持久化存储方案的性能是完全不一样的。例如在单节点计算的情况下,选用DDR 2133双通道内存组和DDR3 1333单通道内存条从理论上至少就可以多获得4Gbps的带宽;选用同样支持SATA3规范的机械硬盘和SSD固态硬盘,虽然两者理论上的对外速度都标称6Gbps,但是由于机械硬盘上单磁头的读写速度存在瓶颈,所以就算进行连续读操作,速度也只能达到200MB/s左右;但是固态硬盘的连续读速度却可以达到500MB/s左右(基本已经接近6Gbps)。
  • 如果是企业级硬件存储方案,那么速度差异还会继续扩大。例如电信行业经常采用的IBM 各个系列磁盘阵列,一般都会配置诸如RAID5这样的软存储方案。这样一来,同一份文件有多个副本,并且有多个磁头负责读写。磁盘阵列的对外输出一般会采用光纤通道(FC),而光纤通道行业协会(Fibre Channel Industry Association)最新推出的(2015年实施)Gen 6第6代光纤通道标准中,设计的对外传输理论速度是128Gbps。
  • 当然,除非您的公司/团队能够接受这些企业级存储方案高昂的费用。否则还是建议在生产环境搭建性价比较高的折中方案。例如采用20台左右PC Server搭建Ceph/MFS分布式存储系统。这个方案,我们将在下一篇文章中进行详细说明。

所以某种存储方案的性能,除了这种存储方案的工作原理以外对其有直接影响外,还要考虑它的工作环境。 只有根据软件团队预估的系统压力、综合建设方案、考虑后续扩容方式,来确定采用哪一种存储方案,才是科学的

阅读全文