一、ActiveMQ消息持久化理论简介
前面我们保留了备份,现在先将文件恢复回来,并重启ActiveMQ。
MQ高可用的4个条件:事务,持久,签收,可持久化。
其中持久是指的DeliveryMode.PERSISTENT,可持久化是指将消息存储在磁盘中。事务,持久,签收,这三个都是MQ自带实现的功能。
对于可持久化,需要借助外力,比如数据库等。
ActiveMQ官网持久化介绍:http://activemq.apache.org/persistence。
在ActiveMQ 5.9中,引入了复制LevelDB存储。 它处理使用Apache ZooKeeper从一组配置为复制单个LevelDB存储的代理节点中选择一个主服务器。 然后将所有从属LevelDB存储与主数据库同步,通过将所有更新复制到主数据库,从而使它们保持最新状态。 这可能会成为首选的主从配置。
从5.3开始-我们建议您使用KahaDB-它提供了比AMQ消息存储更高的可伸缩性和可恢复性。
AMQ消息存储库虽然比KahaDB快,但扩展性却不如KahaDB,恢复时间也更长。
通过这两段话,我们知道了两种DB,一种是KahaDB,一种是LevelDB。
为了避免意外宕机后消息丢失,需要做到重启服务后,可以恢复消息队列,消息系统一般会采用持久化机制。
ActiveMQ的消息持久化机制有JDBC、AMQ、KahaDB和LevelDB,无论哪种方式进行持久化,消息的存储逻辑都是一样的。
在发送者将消息发送出去之后,消息中心首先将消息存储在本地的数据文件、内存数据库或远程数据库等再将消息发送给解说着,如果成功,则将消息从存储中删除,否则继续尝试重发。
消息中心启动后,需要先检查存储位置,如果有未发送成功的消息,需要先把消息发送出去。
二、ActiveMQ消息持久化机制之AMQ和KahaDB
消息持久化的方式有以下5种:
1.AMQ Message Store(了解)
2.KahaDB消息存储(默认)
3.JDBC消息存储(常用)
4.LevelDB消息存储(了解)
5.JDBC Message store with ActiveMQ Journal
AMQ Message Store:基于文件的存储方式,是旧版本的默认消息存储,现在不使用了。它具有写入速度快和容易恢复的特点。消息存储在一个个文件中,文件默认大小是32M,当一个存储文件中的消息已经被消费,这个文件被标记为可删除,在下一清除阶段,这个文件就被删除了。AMQ在ActiveMQ 5.3版本之前适用。
KahaDB:基于日志文件存储,从ActiveMQ 5.4版本开始默认的持久化存储方式。再回到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>
这个地方配置的directory的意思是:持久化的文件都存储在了data/kahadb文件夹下,我们可以在这个文件夹下看到4个文件。
三、ActiveMQ消息持久化机制之KahaDB的存储原理
ActiveMQ给出关于KahaDB配置的介绍:http://activemq.apache.org/kahadb
消息存储使用一个事务日志和仅仅用一个索引文件来存储它所有的地址。
KahaDB是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化。
数据被追加到data和log中,当不需要log文件中的数据时,log文件被丢弃。
kahadb在消息保存目录中有4类文件(我刚刚重启了服务,所以只有3个,少了一个db.free文件)和一个lock。
db-<Number>.log:KahaDB存储消息到预定义大小的数据记录文件中,文件名为db-<Number>.log。当前文件已满时,一个新的文件会随之创建,number的数值随之递增。如每32M一个文件,文件名按照数字进行编号。当不再有引用到的数据文件中的任何消息时,文件会被删除或归档,由后续的清理机制来清除文件。
db.data:包含了持久化的BTree索引,索引了消息数据记录中的消息,它是消息的索引文件,本质上是B-Tree(B树),使用B-Tree作为索引指向db-<Number>.log里存储的消息。
db.free:当前db.data文件里,哪些页面是空闲的,文件具体内容是所有空闲页的ID,方便后续建索引的时候,先从空闲页开始建立,保证索引的连续性,没有碎片。
db.redo:用来进行消息恢复,如果KahaDB消息存储在强制退出后启动,用于恢复B-Tree索引。
lock:文件锁,表示当前获得KahaDB读写权限的Broker。
这里的log和data类似于MySQL数据库里的数据和索引,可以类比来理解。
四、ActiveMQ消息持久化机制之LevelDB简介
LevelDB是从ActiveMQ 5.8之后引进的,它和KahaDB非常类似,也是基于文件的本地数据库存储形式,但是它提供比KahaDB更高效的持久性能。它不适用B-Tree来实现索引,而是使用基于LevelDB的索引。
如果想在ActiveMQ中使用LevelDB,需要修改activemq.xml配置文件,将persistenceAdapter结点修改成如下即可。
<persistenceAdapter>
<levelDB directory="activemq-data"/>
</persistenceAdapter>
可能是LevelDB还不稳定,或者还有bug,所以官方推荐使用的还是KahaDB的存储方式。
五、ActiveMQ消息持久化机制之JDBC配置MySQL
将mysql-connector-java的jar包扔到ActiveMQ的lib目录下。
配置PersistenceAdapter结点为JDBC。修改conf目录下的activmeq.xml中的persistenceAdapter结点
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" createTablesOnStartup="true"/>
</persistenceAdapter>
上面的#是引用符,dataSource指定要引用的持久化数据库的bean名称,createTablesOnStartup是否在启动的时候创建数据表,默认为true,这样每次启动都会新建数据表,一般是第一次启动设置成true,后面改成false。
添加mysql-ds,这样上面的配置文件才能拿到DataSource。将下面这段内容放在broker结点和import结点之间。
<bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/activemq?relaxAutoCommit=true"/>
<property name="username" value="root"/>
<property name="password" value=""/>
<property name="poolPreparedStatements" value="true"/>
</bean>
根据上面的配置,我们需要在自己的windows 的ip这台机器上,创建一个数据库名称为activemq的数据库。由于本人的windows 的mysql报连接的问题,所以用的是linux系统的linux
这里需要注意一个问题,如果是高版本的MySQL数据库,需要指定时区,那就要在MySQL连接后面加上&serverTimezone=UTC的参数,这里的&不能直接写,要写&,也就是&的转义。这是后面我碰到的一个错误,更新一下。
新建了activemq的数据库后,如果配置都正确,那么重启ActiveMQ服务的时候,会自动新建3张表:activemq_msgs,activemq_acks,activemq_lock。
启动ActiveMQ服务,启动过程中,如果报错,就去data目录下,查看activemq.log启动日志,里面会告诉你启动报错的原因。
启动不成功可能的原因,依次检查一下:
1. 虚拟机和实体机是否互相ping通,不能ping通可能是防火墙问题。
2. 实体机上的mysql是不是支持远程访问。
3.mysql的地址是否正确,是否有activemq这个数据库(高版本需要加一个serverTimezone=UTC参数,用&连接)。
4.如果还是不行,就去data目录下,看activemq.log启动日志。、
启动成功后,在activemq数据库下,可以看到多出来3张表。
activemq_msgs(消息表):
activemq_acks(订阅关系表,针对于持久化的Topic,订阅者和服务器的订阅关系会保存在这个表中):
activemq_lock:
在集群环境中才有用,只有一个Broker可以获取消息,称为Master Broker,其他的只能作为备份等待Master Broker不可用,才可能成为下一个Master Broker。这个表用来记录哪个Broker是当前的Master Broker。
要测试持久化,就要设置持久化,messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);,
① 当DeliverMode设置为NON_PERSISTENCE时,消息被保存在内存中
② 当DeliverMode设置为PERSISTENCE时,消息保存在broker的响应的文件或数据库中
而且点对点类型中信息一旦被Consumer消费就从broker中删除。
public class JmsProduce {
// public static final String ACTIVEMQ_URL = "tcp://10.5.96.48:61616";
public static final String ACTIVEMQ_URL = "tcp://10.5.96.48:61616";
public static final String QUEUE_NAME = "jdbc01";
public static void main(String[]args) throws JMSException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话 session
//两个桉树 ,第一个叫事务 第二个叫签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 .创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5. 创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
/*** 消息的持久化设置 start***/
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
/*** 消息的持久化设置 end***/
//6.通过使用messageProducer 生产3条消息发送到队列里面
for (int i = 0; i <3 ; i++) {
// 7.创建消息
TextMessage textMessage = session.createTextMessage("Msg---" + i);
//8.消息生产者发送给mq
messageProducer.send(textMessage);
}
//9.关闭资源 顺着开启,倒着关闭
messageProducer.close();
session.close();
connection.close();
System.out.println("**** 消息发布到MQ完毕 ****");
}
}
消费者的代码可以复用之前的JmsConsumer.java 这个类对应的端口号修改下,数据库的截图就不贴出来了。
运行成功后,在ActiveMQ的管理界面的queue标签,可以看到有3条消息,再去看数据库的activemq_msgs表,会发现里面有几条记录,这就是持久化的结果,说明配置的持久化生效了。
再次运行消费者,将消息消费掉,再去数据库里查看,activemq_msgs表里的数据就没了,说明消息的消费操作也同步更新了数据库。
下面来验证Topic,先启动消费者监听,查看数据库的activemq_acks表,可以发现已经出现数据了,再启动生产者,消费者执行消费,查看数据库的activemq_acks表和activemq_msgs表,里面的记录依旧存在。这个地方结合微信公众号来理解,微信公众号发布过哪些文章,微信公众号的关注者,不能因为服务的关闭,就把发布的文章和关注者清空,这是不合理的,从这个角度来理解就能明白了。
六、ActiveMQ消息持久化机制之JDBC配置MySQL小总结
消息持久化,如果使用的Destination是Queue,那么使用的是activemq_msgs表,消费后立刻删除,如果使用的Destination是Topic,那么使用的是activemq_msgs和activemq_acks表,消息消费后数据不清除。
可能碰到坑:
1、将mysql-connector-java.jar放到ActiveMQ的lib目录下,如果使用了其他连接池,也要把连接池用到的jar包放到这里面来,ActiveMQ默认使用的是dbcp数据库连接池。
2、还记得jdbcPersistenceAdapter结点中的createTablesOnStartup属性吧,第一次启动的时候会新建表,后面就不需要了,可以把这个属性去掉,或者将值改为false,我没有去掉,在启动过程中也没有报错,可能也没有什么影响吧。
3、如果在启动过程中碰到了"java.lang.IllegalStateException: BeanFactory not initialized or already closed"异常,是因为操作系统的机器名中含有下划线符号,修改机器名并重启即可解决问题。
七、ActiveMQ消息持久化机制之JDBC With Journal
先说JDBC With Journal是什么,这个东西可以简单理解为JDBC的增强吧,它比JDBC的速度要快,是利用高速缓存实现的。
因为JDBC的读写性能还是有瓶颈的,所以出现了这么个东西,当消息到来后,先存储到这个缓存中,而不是直接落库,缓存的速度可是非常快的 ,当消息消费者的速度能够及时跟上消息生产者的速度时,就能避免全部消息入库,从而达到提高速度的目的。如果说消费者消费的速度比较慢,那么缓存可以批量将消息写入db,而不是每次一条一条的处理,批处理的速度是快于一次一条的。
修改conf目录下的activemq.xml配置文件的persistenceAdapter结点,将其替换成下面的内容。
<persistenceFactory>
<journalPersistenceAdapterFactory journalLogFiles="4" journalLogFileSize="32768" useJournal="true" useQuickJournal="true" dataDirectory="activemq-data" dataSource="#mysql-ds"/>
</persistenceFactory>
修改好配置后,重启ActiveMQ服务,做测试。这个地方因为用了Journal做缓存,所以持久化的消息并不会立刻在DB中看到,可以多等一会儿,可以尝试10分钟吧。
八、ActiveMQ持久化机制小总结
持久化消息主要是指:MQ所在的服务器宕机了,消息不会丢失的机制。
持久化机制演化过程:
从最初的AMQ Message Store方案到ActiveMQ V4版本中推出的High performance journal(高性能事务支持)附件,并且同步推出了关于关系型数据库的存储方案。ActiveMQ 5.3版本中又推出了对KahaDB的支持(V 5.4版本后称为ActiveMQ默认的持久化方案),后来ActiveMQ V 5.8版本开始支持LevelDB,到现在V 5.9+提供了标准的Zookeeper+LevelDB集群化方案,重点了解KahaDB、LevelDB和MySQL数据库这三种持久化存储方案。
ActiveMQ的消息持久化机制有:
无论使用哪种持久化方式,消息存储的逻辑是一致的。在消息发送后,消息中心先将消息存储到本地数据文件、内存数据库或远程数据库等,然后试图将消息发送给接收者,发送成功则将消息从存储中删除,失败则继续重试。消息中心启动后先检查指定的存储位置,如果有未发送成功的消息,需要先将消息发送出去。
参考
https://blog.csdn.net/qq_36059561/article/details/103866365
https://blog.csdn.net/qq_36059561/article/details/103882033
https://blog.csdn.net/qq_36059561/article/details/103882558
https://blog.csdn.net/qq_36059561/article/details/103882869
https://blog.csdn.net/qq_36059561/article/details/103884742
https://blog.csdn.net/qq_36059561/article/details/103884757
https://blog.csdn.net/qq_36059561/article/details/103898119