ActiveMQ消息存储持久化
ActiveMQ不仅支持persistent和non-persistent两种方式,还支持消息的recovery(恢复)方式。
1.PTP
Queue的存储是很简单的,就是一个FIFO的Queue
2.PUB/SUB
对于持久化订阅主题,每一个消费者将获得一个消息的复制。
3.有效的消息存储
ActiveMQ提供了一个插件式的消息存储,类似于消息的多点传播,主要实现了如下几种:
1.AMQ消息存储-基于文件的存储,以前默认的存储方式
2.KahaDB消息存储-提供了容量的提升和恢复能力,现在的默认方式
3.JDBC消息存储-消息基于JDBC存储
4.Memory消息存储-基于内存的消息存储
3.1 kahaDB消息存储--目前默认的存储方式
(1)kahaDB Message Store概述
KahaDB是目前默认的存储方式,可用于任何场景,提高了性能和恢复能力。消息存储使用一个事务日志和仅仅用一个索引文件来存储它所有的地址。
KahaDB是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化。在kaha中,数据被追加到 data logs中。 当不再需要log文件中的数据的时候,log文件会被丢弃。
(2)基本配置方式: 在activemq的安装目录下的:conf/activemq.xml中有如下配置:(默认配置)
<!-- Configure message persistence for the broker. The default persistence mechanism is the KahaDB store (identified by the kahaDB tag). For more information, see: http://activemq.apache.org/persistence.html --> <persistenceAdapter> <kahaDB directory="${activemq.data}/kahadb"/> </persistenceAdapter>
如上面配置了文件的位置,现在我们查看kahadb的文件位置:
(3)可用的属性有:
1. director: KahaDB存放的路径,默认值activemq-data
2. indexWriteBatchSize: 批量写入磁盘的索引page数量,默认值为1000
3. indexCacheSize: 内存中缓存索引page的数量,默认值10000
4. enableIndexWriteAsync: 是否异步写出索引,默认false
5. journalMaxFileLength: 设置每个消息data log的大小,默认是32MB
6. enableJournalDiskSyncs: 设置是否保证每个没有事务的内容,被同步写入磁盘,JMS持久化的时候需要,默认为true
7. cleanupInterval: 在检查到不再使用的消息后,在具体删除消息前的时间,默认30000
8. checkpointInterval: checkpoint的间隔时间,默认是5000
9. ignoreMissingJournalfiles: 是否忽略丢失的消息日志文件,默认false
10. checkForCorruptJournalFiles: 在启动的时候,将会验证消息文件是否损坏,默认false
11. checksumJournalFiles: 是否为每个消息日志文件提供checksum,默认false
12. archiveDataLogs: 是否移动文件到特定的路径,而不是删除它们,默认false
13. directoryArchive: 定义消息已经被消费过后,移动data log到的路径,默认null
14. databaseLockedWaitDelay: 获得数据库锁的等待时间(used by shared master/slave),默认10000。用于之后主从复制的时候配置
15. maxAsyncJobs: 设置最大的可以存储的异步消息队列,默认值10000,可以和concurrent MessageProducers设置成一样的值。
16. concurrentStoreAndDispatchTransactions:是否分发消息到客户端,同时事务存储消息,默认true
17. concurrentStoreAndDispatchTopics: 是否分发Topic消息到客户端,同时进行存储,默认true
18. concurrentStoreAndDispatchQueues: 是否分发queue消息到客户端,同时进行存储,默认true
(4)Java内嵌Broker使用kahadb的例子:
package cn.qlq.activemq.broker; import java.io.File; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.store.kahadb.KahaDBStore; public class BrokerUeingKahadb { public static void main(String[] args) throws Exception { BrokerService brokerService = new BrokerService(); File dataKahadbFile = new File("data/kahadb"); KahaDBStore kahaDBStore = new KahaDBStore(); kahaDBStore.setDirectory(dataKahadbFile); kahaDBStore.setJournalDiskSyncInterval(1024 * 1000); kahaDBStore.setIndexWriteBatchSize(100); kahaDBStore.setEnableIndexWriteAsync(true); brokerService.setPersistenceAdapter(kahaDBStore); brokerService.addConnector("tcp://localhost:61616"); brokerService.start(); } }
3.2 AMQ Message Store消息存储
(1)概述:
AMQ Message Store是ActiveMQ5.0缺省的持久化存储,它是一个基于文件、事务存储设计为快速消息存储的一个结构,该结构是以流的形式来进行消息交互的。
这种方式中,Messages被保存到data logs中,同时被reference store进行索引以提高存取速度。Data logs由一些简单的data log文件组成,缺省的文件大小是32M,如果某个消息的大小超过了data log文件的大小,那么可以修改配置以增加data log文件的大小。如果某个data log文件中所有的消息都被成功消费了,那么这个data log文件将会被标记,以便在下一轮的清理中被删除或者归档。
(2)配置示例如下:(用下面的配置方式替换掉activemq.xml的 persistenceAdapter 即可,也就是该配置需要作为broker的属性)
<persistenceAdapter> <amqPersistenceAdapter directory="${activemq.data}/data" maxFileLength="32mb"/> </persistenceAdapter>
3.3 JDBC持久化存储
ActiveMQ支持使用JDBC来持久化消息,我们只需要配置JDBC驱动即可,至于表结构activemq会自动帮我们建好表结构。
我使用的activemq的版本是:5.15.8,我的配置方式如下:
(1)拷贝mysql的驱动包 mysql-connector-java-5.1.37-bin.jar 到 apache-activemq-5.15.8\lib\optional\ 目录下
(2)修改apache-activemq-5.15.8\conf\activemq.xml
首先定义dataSource(该dataSource位于apache-activemq-5.15.8\lib\optional\ commons-dbcp2-2.1.1.jar包内,当然可以换成我们自己的c3p0,durid等连接池)
<bean id="mysql_ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:mysql://localhost:3306/activemq</value> </property> <property name="username"> <value>root</value> </property> <property name="password"> <value>123456</value> </property> </bean>
修改broker的persistenceAdapter持久化方式:
<persistenceAdapter> <jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#mysql_ds"/> </persistenceAdapter>
(3)启动项目发现activemq会自动帮我们创建好表结构,如下三张表:
mysql> show tables; +--------------------+ | Tables_in_activemq | +--------------------+ | activemq_acks | | activemq_lock | | activemq_msgs | +--------------------+ 3 rows in set (0.00 sec) mysql> desc activemq_msgs; +------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+-------+ | ID | bigint(20) | NO | PRI | NULL | | | CONTAINER | varchar(250) | NO | MUL | NULL | | | MSGID_PROD | varchar(250) | YES | MUL | NULL | | | MSGID_SEQ | bigint(20) | YES | | NULL | | | EXPIRATION | bigint(20) | YES | MUL | NULL | | | MSG | longblob | YES | | NULL | | | PRIORITY | bigint(20) | YES | MUL | NULL | | | XID | varchar(250) | YES | MUL | NULL | | +------------+--------------+------+-----+---------+-------+ 8 rows in set (0.00 sec) mysql> desc activemq_acks; +---------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------------+--------------+------+-----+---------+-------+ | CONTAINER | varchar(250) | NO | PRI | NULL | | | SUB_DEST | varchar(250) | YES | | NULL | | | CLIENT_ID | varchar(250) | NO | PRI | NULL | | | SUB_NAME | varchar(250) | NO | PRI | NULL | | | SELECTOR | varchar(250) | YES | | NULL | | | LAST_ACKED_ID | bigint(20) | YES | | NULL | | | PRIORITY | bigint(20) | NO | PRI | 5 | | | XID | varchar(250) | YES | MUL | NULL | | +---------------+--------------+------+-----+---------+-------+ 8 rows in set (0.00 sec) mysql> desc activemq_lock; +-------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------+-------+ | ID | bigint(20) | NO | PRI | NULL | | | TIME | bigint(20) | YES | | NULL | | | BROKER_NAME | varchar(250) | YES | | NULL | | +-------------+--------------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
其建表语句如下:(可以看出acks表是多列做联合主键)
mysql> show create table activemq_acks\G *************************** 1. row *************************** Table: activemq_acks Create Table: CREATE TABLE `activemq_acks` ( `CONTAINER` varchar(250) NOT NULL, `SUB_DEST` varchar(250) DEFAULT NULL, `CLIENT_ID` varchar(250) NOT NULL, `SUB_NAME` varchar(250) NOT NULL, `SELECTOR` varchar(250) DEFAULT NULL, `LAST_ACKED_ID` bigint(20) DEFAULT NULL, `PRIORITY` bigint(20) NOT NULL DEFAULT '5', `XID` varchar(250) DEFAULT NULL, PRIMARY KEY (`CONTAINER`,`CLIENT_ID`,`SUB_NAME`,`PRIORITY`), KEY `ACTIVEMQ_ACKS_XIDX` (`XID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec) mysql> show create table activemq_msgs\G *************************** 1. row *************************** Table: activemq_msgs Create Table: CREATE TABLE `activemq_msgs` ( `ID` bigint(20) NOT NULL, `CONTAINER` varchar(250) NOT NULL, `MSGID_PROD` varchar(250) DEFAULT NULL, `MSGID_SEQ` bigint(20) DEFAULT NULL, `EXPIRATION` bigint(20) DEFAULT NULL, `MSG` longblob, `PRIORITY` bigint(20) DEFAULT NULL, `XID` varchar(250) DEFAULT NULL, PRIMARY KEY (`ID`), KEY `ACTIVEMQ_MSGS_MIDX` (`MSGID_PROD`,`MSGID_SEQ`), KEY `ACTIVEMQ_MSGS_CIDX` (`CONTAINER`), KEY `ACTIVEMQ_MSGS_EIDX` (`EXPIRATION`), KEY `ACTIVEMQ_MSGS_PIDX` (`PRIORITY`), KEY `ACTIVEMQ_MSGS_XIDX` (`XID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec) mysql> show create table activemq_lock\G *************************** 1. row *************************** Table: activemq_lock Create Table: CREATE TABLE `activemq_lock` ( `ID` bigint(20) NOT NULL, `TIME` bigint(20) DEFAULT NULL, `BROKER_NAME` varchar(250) DEFAULT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec)
解释上面三张表:
1.消息表,缺省表明为ACTIVEMQ_MSGS, queue和topic都存储在里面
ID:自增主键; CONTAINER:所属类型以及所在队列或者主题; MSGID_PROD:生产者ID;MSGID_SEQ:在同一批次消息中的序号; EXPIRATION:过期时间(表示永不过期); MSG:序列化之后的消息; PRIORITY优先级; XID:暂时不知道这个有什么用。
2.ACTIVEMQ_ACKS表存储持久订阅的信息和最后一个持久订阅接收的消息ID
CONTAINER:与上面消息表的CONTAINER一样; SUB_DEST:子目的地,与CONTAINER一样;
CLIENT_ID: 链接的客户端ID,也就是我们程序:connection.setClientID("cc1"); 产生的ID
SUB_NAME:持久订阅者的名称.也就是我们程序: session.createDurableSubscriber(destination, "C11"); 产生的名称
SELECTOR:消息选择器,consumer可以选择自己想要的
LAST_ACKED_ID:最后一次确认ID,这个字段存的该该订阅者最后一次收到的消息的ID
XID:暂时不知道这个有什么用。
3.锁定表,缺省表名为ACTIVEMQ_LOCK,用来确保在某一时刻,只能有一个ActiveMQ broker实例来访问数据库
XID:自增的主键
TIME:日期
BROKER_NAME:占用数据库的brokerName
(4)测试持久化存储和订阅
lock表一直是只有一条数据:
mysql> select * from activemq_lock; +----+------+-------------+ | ID | TIME | BROKER_NAME | +----+------+-------------+ | 1 | NULL | NULL | +----+------+-------------+ 1 row in set (0.00 sec)
我们按照如下顺序进行操作:
- 发送2条 消息到queue,查看数据库结构:
- 启动持久订阅主题消费者
- 发布5条消息都主题中:
消息表如下:
acks表如下:
- 启动队列消费者消费队列信息(发现队列的消息被删除)
- 再次启动主题生产者生产消息
补充:Java内嵌Broker使用JDBC持久化存储
(1)依赖的jar包
(2)broker启动的代码
package cn.qlq.activemq; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.store.jdbc.JDBCPersistenceAdapter; import org.apache.commons.dbcp2.BasicDataSource; public class BrokerUsingJDBC { public static void main(String[] args) throws Exception { BrokerService brokerService = new BrokerService(); // 1.创建数据源 BasicDataSource dataSource = new BasicDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/activemq"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); // 2.创建JDBCPersistenceAdapter JDBCPersistenceAdapter Adapter = new JDBCPersistenceAdapter(); Adapter.setDataSource(dataSource); brokerService.setBrokerName("brokerName"); brokerService.setUseJmx(true); brokerService.setPersistenceAdapter(Adapter); brokerService.addConnector("tcp://localhost:61616"); brokerService.start(); } }
测试结果以及代码同上面。关于整合spring的broker启动方式也是类似注入正确的bean即可。
补充:JDBC Message Store with ActiveMQ Journal====优化版的JDBC存储
这种方式克服了JDBC Store的不足,使用快速的缓存写入技术,大大提高了性能。
JDBC 配合其自带的 high performance journal;根据官方说法,它内置的高性能journal的工作类似于在缓存层工作,消息会优先写入到journal,后台的定时任务会每隔一段时间间隔去。
JDBC Store和JDBC Message Store with ActiveMQ Journal的区别:
1. JDBC with journal的性能优于jdbc
2. JDBC用于master/slave模式的数据库分享
3. JDBC with journal不能用于master/slave模式
4. 一般情况下,推荐使用jdbc with journal
其配置方式如下:注释掉原来的持久化适配器,并注入持久化工厂
<!-- <persistenceAdapter> <jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#mysql_ds"/> </persistenceAdapter> --> <persistenceFactory> <journalPersistenceAdapterFactory journalLogFiles="4" journalLogFileSize="32768" useJournal="true" useQuickJournal="true" dataSource="#mysql_ds" dataDirectory="activemq-data"/> </persistenceFactory>
其java启动broker的使用方式如下:
(1)需要的jar包:
(2)Broker启动类:
package cn.qlq.activemq; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.store.journal.JournalPersistenceAdapterFactory; import org.apache.commons.dbcp2.BasicDataSource; public class BrokerUsingJDBC { public static void main(String[] args) throws Exception { BrokerService brokerService = new BrokerService(); // 1.创建数据源 BasicDataSource dataSource = new BasicDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/activemq"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); // 2.创建PersistenceAdapterFactory JournalPersistenceAdapterFactory persistenceFactory = new JournalPersistenceAdapterFactory(); persistenceFactory.setDataSource(dataSource); persistenceFactory.setJournalLogFiles(4); persistenceFactory.setJournalLogFileSize(32768); persistenceFactory.setUseJournal(true); persistenceFactory.setUseQuickJournal(true); // 3.设置持久化工厂并启动broker brokerService.setBrokerName("brokerName"); brokerService.setUseJmx(true); brokerService.setPersistenceFactory(persistenceFactory); brokerService.addConnector("tcp://localhost:61616"); brokerService.start(); } }
3.4 Memory Message Store
内存消息存储主要是存储所有的持久化的消息在内存中。这里没有动态的缓存存在,所以你必须注意设置你的broker所在的JVM和内存限制。
这种方式的持久化消息只在当前JVM内有效,当重启JVM之后会丢失持久化的消息。
配置方式如下:只需要将 persistent 属性设为false即可。
<broker xmlns="http://activemq.apache.org/schema/core" persistent="false" brokerName="brokerName" dataDirectory="${activemq.data}">
其内嵌Java的broker方式如下:
BrokerService brokerService = new BrokerService(); brokerService.setPersistent(false); brokerService.setBrokerName("brokerName"); brokerService.setUseJmx(true); brokerService.addConnector("tcp://localhost:61616"); brokerService.start();