一.说明
消息可持久化是指数据库和activemq的服务器同步数据。当activemq服务器出现问题,但不是影响数据库的数据。
二.消息的高可用
1.事务。
2.签收。
3.持久。【指的是activemq服务器没有问题,消息传递的持久化】
4.可持久化。【指的是activemq服务器出现问题,消息的持久化】
前三条可查看消息可靠性的笔记。
三.持久化的方式
为了避免意外宕机以后丢失信息,需要做到重启后可以恢复消息队列,消息系统一般都会采用持久化机制。
ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的。
就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等再试图将消息发送给接收者,成功则将消息从存储中删除,失败则继续尝试发送。
消息中心启动以后首先要检查指定的存储位置,如果有未发送成功的消息,则需要把消息发送出去。
1.AMQ Message Store
基于文件的存储方式,是以前的默认消息存储,现在不用了。
AMQ是一种文件存储形式,它具有写入速度快和容易恢复的特点。消息存储在一个个文件中,文件的默认大小为32M。当一个存储文件中的消息已经全部被消费,那么这个文件将被标识为可删除,在下一个清除阶段,这个文件被删除。AMQ适用于ActiveMQ5.3之前的版本。
2.KahaDB消息存储(默认)【4G】
基于日志文件,从ActiveMQ5.4开始默认的持久化插件。
(1)activemq的默认配置位置
(2)kahaDB的位置
(3)kahadb的存储原理
KahaDB是目前默认的存储方式,可用于任何场景,提高了性能和恢复能力。
消息存储使用一个事务日志和仅仅用一个索引文件来存储它所有的地址。
KahaDB是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化。
数据被追加到data logs中。当不再需要log文件中的数据的时候,log文件会被丢弃。
(4)kehaDB的目录结构
kahadb在消息保存目录中只有4类文件和一个lock,跟activeMQ的其他几种文件存储引擎相比这就非常简洁了。
1.db-.logKahaDB存储消息到预定义大小的数据记录文件中,文件命名为db-.log。当数据文件已满时,一个新的文件会随之创建,number数值也会随之递增,它随着消息数量的增多,如每32M一个文件,文件名按照数字进行编号,如db-1.log、db-2.log、db-3.log…。当不再有引用到数据文件中的任何消息时,文件会被删除或归档。
2.db.data该文件包含了持久化的BTree索引,索引了消息数据记录中的消息,它是消息的索引文件,本质上是B-Tree(B树),使用B-Tree作为索引指向db-.log里面存储的消息。
3.db.free当前db.data文件里哪些页面是空闲的,文件具体内容是所有空闲页的ID。
5.db.redo用来进行消息恢复,如果kahaDB消息存储在强制退出后启动,用于恢复BTree索引。
6.lock文件锁,表示当前获得kahadb读写权限的broker。
3.JDBC消息存储
4.LevelDB消息存储(了解)【5G】
这种文件系统是从ActiveMQ5.8之后引进的,它和kahaDB非常相似,也是基于文件的本地数据库存储形式,但是它提供比KahaDB更快的持久性。
但它不使用自定义B-Tree实现来索引预写日志,而是使用基于LevelDB的索引。
(1)代码块
默认配置如下
<persistenceAdapter>
<levelDBdirectory="activemq-data"/>
</persistenceAdapter>
(2)范例
5.JDBC Message store with ActiveMQ Journal【mysql持久化】
请看本笔记的第四标题和第五标题
四.JDBC Message store with ActiveMQ Journal【三张表】
1.ACTIVEMQ_MSGS(消息表字段)
ID:自增的数据库主键。
CONTAINER:消息的Destination
MSGID_PROD:消息发送者的主键。
MSG_SEQ:是发送消息的顺序,MSGID_PROD+MSG_SEQ可以组成JMS的MessageID。
EXPIRATION:消息的过期时间,存储的是从1970-01-01到现在的毫秒数。
MSG:消息本体的Java序列化对象的二进制数据。
PRIORITY:优先级,从0-9,数值越大优先级越高。
2.ACTIVEMQ_ACKS(签收表字段)
activemq_acks用于存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存。数据库字段如下:
CONTAINER:消息的Destination。
SUB_DEST:如果是使用Static集群,这个字段会有集群其他系统的信息。
CLIENT_ID:每个订阅者都必须有一个唯一的客户端ID用以区分。
SUB_NAME:订阅者名称。
SELECTOR:选择器,可以选择只消费满足条件的消息。条件可以用自定义属性实现,可支持多属性AND和OR操作。
LAST_ACKED_ID:记录消费过的消息的ID。
3.ACTIVEMQ_LOCK(锁定表字段)
activemq_lock在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker,其他的只能作为备份等待Master Broker不可用,才可能称为下一个Master Broker。这个表用于记录哪个Broker是当前的Master Broker。
五.JDBC的可持久化存储【项目演示】
1.流程
1.添加mysql数据库的驱动包到lib文件夹。
2.jdbcPersistenceAdapter配置。
3.数据库连接池配置。
4.建仓SQL和建表说明。
5.代码运行验证。
6.数据库情况。
2.演示
(1)安装JDBC驱动包
mv ./mysql-connector-java-5.1.49.jar ./apache-activemq-5.16.0/lib
(2)修改activemq的配置文件
在activemq安装目录下的conf文件夹修改activemq.xml文件。
<persistenceAdapter>
<!-- 标签的dataSource是属性配置 -->
<jdbcPersistenceAdapter dataSource="#mysql-ds" createTablesOnStartup="true"/>
</persistenceAdapter>
配置的属性添加说明:
dataSource指定将要引用的持久化数据库的bean名称。
createTablesOnStartup是否在启动的时候创建数据表,默认值是true,这样每次启动都会去创建数据表了,一般是第一次启动的时候设置为true之后改成false。
(3)在activemq的配置文件中配置一个bean对象【连接数据库】
(3-1)在activemq安装目录下的conf文件夹修改activemq.xml文件。
<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://数据库地址:3306/activemq?relaxAutoCommit=true"/>
<property name="username" value="数据库用户名"/>
<property name="password" value="数据库密码"/>
<property name="maxTotal" value="200"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
(3-2)修改位置的存放位置在broker标签和import标签之间。
(3-3)数据库的表信息
activemq会创建三张表,分别是ACTIVEMQ_MSGS(消息)、ACTIVEMQ_ACKS(签收)、ACTIVEMQ_LOCK(锁定)。
(4)远程连接数据库失败【开启远程连接】
# 进入mysql数据库
use mysql
# 在mysql数据库中执行该指令
grant all privileges on *.* to '你的用户名'@'%' identified by '你的密码';
# 刷新数据库
flush privileges;
(5)项目测试【队列】
(5-1)消息生产类
代码块
package com.fengmo.activemq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 测试jdbc的可持久化【生产者】
*/
public class JmsProduce {
//定制activemq的地址,注意tcp协议
public static final String ACTIVEMQ_URL = "tcp://192.168.83.131:61616"; //访问activemq的地址
public static final String QUEUE_NAME = "jdbc01"; //队列名称
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
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.创建目的地(具体是队列还是主题)
Queue queue = session.createQueue(QUEUE_NAME);//参数为队列名称
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
// messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); //非持久化
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); //持久化
//6.通过使用messageProducer生产3条消息发送到MQ的队列里面
for(int i = 0;i < 3;i++){
//7.创建消息
TextMessage textMessage = session.createTextMessage("msg----" + i);//理解一个字符串
textMessage.setStringProperty("c01","这是对textMessage设置自定义属性"); //可选,设置自定义属性
//8.通过messageProducer发送到activemq
messageProducer.send(textMessage);
}
//8.关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("消息发送完成");
}
}
范例
(5-2)查看数据库【消息生产,但是未被消费类消费的时候】
(5-3)消息消费类
代码块
package com.fengmo.activemq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* 测试jdbc可持久化【消费者】
*/
public class JmsConsumer {
//定制activemq的地址,注意tcp协议
public static final String ACTIVEMQ_URL = "tcp://192.168.83.131:61616"; //访问activemq的地址
public static final String QUEUE_NAME = "jdbc01"; //队列名称
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
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.创建目的地(具体是队列还是主题)
Queue queue = session.createQueue(QUEUE_NAME);//参数为队列名称
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
//6.通监听的方式来消费消息
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
//判断message是否为空,并且是否属于textmessage的子类,TextMessage是可变
if(null != message && message instanceof TextMessage)
{
//注意:消息提供者的类型则强转什么类型:TextMessage
TextMessage textMessage = (TextMessage) message;
try {
//消费消息,则需要解决异常
System.out.println("接收的消息是" + textMessage.getText());
//接受自定义的属性
System.out.println("接收的消息是" + textMessage.getStringProperty("c01"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
//7.使控制台保存工作
System.in.read(); //细节:如果不加一直保持读取状态,则监听器无法消费消息
//8.关闭资源
messageConsumer.close();
session.close();
connection.close();
}
}
范例
(5-4)点对点的总结【队列总结】
在点对点类型中,
当DeliveryMode设置为NON_PERSISTENCE时,消息被保存在内存中。
当DeliveryMode设置为PERSISTENCE时,消息保存在broker的相应的文件或者数据库中。
而且点对点类型中消息一旦被Consumer消费就从broker中删除。
(6)项目测试【主题】
(6-1)消息主题类【生产者】
代码块
package com.fengmo.activemq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* 该类是主题消息提供者类【持久化测试】
*/
public class JmsTopic_Produce {
//定制activemq的地址,注意tcp协议
public static final String ACTIVEMQ_URL = "tcp://192.168.83.131:61616"; //访问activemq的地址
public static final String TOPIC_NAME = "topic01"; //队列名称
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得连接connection
Connection connection = activeMQConnectionFactory.createConnection();
//3.创建session,第一个参数事务,第二个参数签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题)
Topic topic = session.createTopic(TOPIC_NAME);
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); //持久化操作
connection.start(); //启动连接
//6.通过使用messageProducer生产3条消息发送到MQ的队列里面
for(int i = 0;i < 6;i++){
//7.创建消息
TextMessage textMessage = session.createTextMessage("topic----" + i);//理解一个字符串
//8.通过messageProducer发送到activemq
messageProducer.send(textMessage);
}
//8.关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("消息发送完成");
}
}
范例
(6-2)消息订阅类【消费者】
代码块
package com.fengmo.activemq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* 该类是主题消息消费者类 【持久化测试】
*/
public class JmsTopic_Consumer {
//定制activemq的地址,注意tcp协议
public static final String ACTIVEMQ_URL = "tcp://192.168.83.131:61616"; //访问activemq的地址
public static final String TOPIC_NAME = "topic01"; //队列名称
public static void main(String[] args) throws JMSException, IOException {
System.out.println("z3启动了。。。。。。 ");
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得连接connection
Connection connection = activeMQConnectionFactory.createConnection();
connection.setClientID("z3"); //持久化第一步设置客户端id
//3.创建session,第一个参数事务,第二个参数签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题)
Topic topic = session.createTopic(TOPIC_NAME);
//持久化第二步设置订阅者,参数一是目的地,参数二是备注
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark...");
//5.启动连接
connection.start();
//6.获取消息
Message message = topicSubscriber.receive();
while(null != message){
TextMessage textMessage = (TextMessage) message;
System.out.println("收到的持久化消息是" + textMessage);
message = topicSubscriber.receive(5000L);
}
session.close();
connection.close();
}
}
范例
(6-3)数据库展示
3.JDBC的小总结
如果是queue,在没有消费者的情况下会将消息保存到activemq_msgs表中,只要有任意一个消费者已经消费过了,消费之后这些消息将会立即被删除。
如果是topic,一般是先启动消费订阅然后再生产的情况下会将消息保存到activemq_acks。
4.配置JDBC可持久化存储方案时的注意事项
(1)数据库jar包
记得需要使用到的相关jar文件放置到ActiveMQ安装路径下的lib目录。mysql-jdbc驱动的jar包和相对应的数据库连接池jar包。
(2)createTablesOnstartup属性
在jdbcPersistenceAdapter标签中设置了createTablesOnStartup属性为true时在第一次启动ActiveMQ时,ActiveMQ服务节点会自动创建所需要的数据表。启动完成后可以去掉这个属性,或者更改createTablesOnStartup属性为false。
(3)下划线坑爹
“java.lang.IllegalStateException.BeanFactory not initialized or already closed"这是因为你的操作系统的机器名中有”_"符号,请更改机器名并且重启后即可解决问题。
六.JDBC Message store with ActiveMQ Journal
1.说明
这种方式克服了JDBC Store的不足,JDBC每次消息过来,都需要去写库和读库。
2.ActiveMQ Journal
使用高速缓存写入技术,大大提高了性能。
当消费者的消费速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要写入到DB中的消息。
如果消费者的消费速度很慢,这个时候journal文件可以使消息以批量方式写到DB。
3.配置文件修改的内容
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="4"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="activemq-data"/>
</persistenceFactory>
4.修改配置文件的范例
修改activemq安装目录下的conf文件夹的activemq.xml文件。
5.举例说明
生产者生产了1000条消息,这1000条消息会保存到journal文件,如果消费者的消费速度很快的情况下,在journal文件还没有同步到DB之前,消费者已经消费了90%以上的消息,那么这个时候只需要同步剩余的10%的消息到DB。
七.总结
1.持久化消息
MQ所在的服务器down了消息不会丢失的机制。
2.持久化机制演化过程
从最初的AMQ Message Store方案到ActiveMQ V4版本中退出的Hight performance journal(高性能事务支持)附件,并且同步退出了关于关系型数据库的存储方案。ActiveMQ5.3版本中又推出了对KahaDB的支持(V5.4版本后称为ActiveMQ默认的持久化方案),后来ActiveMQ V5.8版本开始支持LevelDB,到现在,V5.9+版本提供了标准的Zookeeper+LevelDB集群化方案。我们重点介绍了KahaDB、LevelDB和mysql数据库者三种持久化存储方案。
3.ActiveMQ的消息持久化机制
名称 | 说明 |
---|---|
AMQ | 基于日志文件 |
KahaDB | 基于日志文件,从ActiveMQ5.4开始默认的持久化插件 |
JDBC | 基于第3方数据库 |
LevelDB | 基于文件的本地数据库存储,从ActiveMQ5.8版本之后又推出了LevelDB的持久化引擎性能高于KahaDB。 |
Replicated LevelDB Store | 从ActiveMQ5.9提供了基于LevelDB和Zookeeper的数据复制方式,用于Master-slave方式的首选数据复制方案。 |
无论使用哪种持久化方式,消息的存储逻辑都是一致的。
就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等,然后试图将消息发送给接收者,发送成功则将消息从存储中删除,失败则继续尝试。消息中心启动以后首先要检查指定的存储位置,如果有未发送成功的消息,则需要把消息发送出去。