AMQ持久化机制
当MQ服务区宕机后,消息不会丢失。为了避免意外宕机以后丢失信息,需要做到重启后可以恢复消息队列,消息系统一般会采用持久化机制。ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的。
就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等再试图将消息发送给接收者,成功则将消息从存储中删除,失败则继续尝试发送。
有哪些
- AMQ Message Store
基于文件的存储方式,是以前的默认消息存储
它具有写入速度快和容易恢复的特点。消息存储在一个文件中,文件默认大小为32M,当一个存储文件中的消息已经被全部消费后,这个文件将被标识为可删除,在下一个清除阶段,这个文件会删除。
- KahaDB消息存储(默认)
基于日志文件,从ActiveMQ5.4开始默认的持久化插件
KahaDB是当前默认的存储方式,可用于任何场景,提高了性能和恢复能力。
消息存储使用一个事务日志和仅仅用一个索引文件来存储它所有的地址
KahaDB是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化。数据被追加到data logs中。当不需要log文件的数据的时候,log文件会被丢弃
kahaDB在消息保存目录中只有四类文件和一个lock
1.db-(number).log KahaDB存储消息到预定义大小的数据记录文件中。当数据文件已满时,一个新的文件会随之创建,number数值也会随之递增。当不再有引用到数据文件中的任何消息时,会被删除或归档。
2.db.data该文件包含了持久化的BTree索引,索引了消息数据记录中的消息,它是消息的索引文件,本质上是B-Tree,使用B-Tree作为索引指向db-(number).log里面存储的消息。
3.db.free 当前db.data文件里哪些页面是空闲的,文件具体内容是所有空闲页的ID。
4.db.redo用来进行消息恢复,和KahaDB消息存储在强制退出后启动,用于恢复BTree索引。
- JDBC消息存储-------消息基于JDBC存储
- LevelDB消息存储
和KahaDB相似,也是基于文件的本地数据库存储新式,但它不使用自定义B-Tree实现来索引预写日志,而是使用基于levelDB的索引
- JDBC Message store with ActiveMQ Journal
持久化机制之JDBC配置mysql
- MQ+MySQL
- 添加mysql数据库的驱动包到lib
- jdbcPersistenceAdapter配置
- 数据库连接池配置
- 建表
ACTIVEMQ_MSGS
ID:自增的数据库主键
CONTAINER:消息的Destination
MSGID_PROD:消息发送者的主键
MSG_SEQ:是发送消息的顺序,MSGID_PROD+MSG_SEQ可以组成JMS的MessageID
EXPIRATION:消息的过期时间
MSG:消息本体的Java序列化对象的二进制数据
PRIORITY:优先级,从0-9,数值越大优先级越高
ACTIVEMQ_ACK
activemq_acks:用于存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存
CONTAINER:消息的Destination
SUB_DEST:如果是使用Static集群,这个字段会有集群其他系统的信息
CLIENT_ID:每个订阅者都必须有一个唯一的客户端ID
SUB_NAME:订阅者名称
SELECTOR:选择器,可以选择只消费满足条件的信息
LAST_ACKED_ID:记录消费过的信息的ID
ACTIVEMQ_LOCK
在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker
如果没有连接成功,看看数据库是否接受远程访问。
- 代码运行验证
队列
public class jmsProduce {
public static final String ACTIVEMQ_URL="nio://192.168.10.100:61608";
public static final String QUEUE_NAME="jdbc01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,采用默认的用户名密码,连接给定的url
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得Connection并启动
Connection connection = connectionFactory.createConnection();
connection.start();
//3创建session
//两个参数,第一个:事务,第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(队列还是主题)
Queue queue = session.createQueue(QUEUE_NAME);
//5创建消息的生产者
MessageProducer producer = session.createProducer(queue);
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
//6.通过使用MessageProducers生产3条消息发送到mq的队列里
for (int i = 1; i <=3; i++) {
TextMessage textMessage= session.createTextMessage("jdbc msg---" + i);//理解为一个字符串
//发送
producer.send(textMessage);
}
//关闭资源
producer.close();
session.close();
connection.close();
System.out.println("******消息发布完成");
}
}
public class jmsConsumer {
public static final String ACTIVEMQ_URL="nio://192.168.10.100:61608";
public static final String QUEUE_NAME="jdbc01";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂,采用默认的用户名密码,连接给定的url
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得Connection并启动
Connection connection = connectionFactory.createConnection();
connection.start();
//3创建session
//两个参数,第一个:事务,第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(队列还是主题)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer consumer = session.createConsumer(queue);
/*while(true){
TextMessage receive = (TextMessage)consumer.receive();
if(receive!=null){
System.out.println("消费者接收到消息"+receive.getText());
}else{
break;
}
}*/
//通过监听方式来消费消息
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message!=null&&message instanceof TextMessage){
TextMessage receive = (TextMessage)message;
try {
System.out.println("消费者接收到消息"+receive.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();//保证控制台不灭
consumer.close();
session.close();
connection.close();
}
}
-
数据库情况
ACTIVEMQ_MSGS
消费之后
说明点对点类型中的消息一旦被Consumer消费就从broker中删除 -
总结
如果是队列
在没有消费者消费的情况下会将消息保存到activitymq_msgs表中,只要有任意一个消费者消费过,消息就会立马被删除
如果是topic
一般是先启动消费订阅然后再生产的情况下会将消息保存到activemq_acks中
- 开发问题
数据库jar包
需要使用到的相关connector的jar包要放到activemq安装路径下的lib目录下
createTablesOnStartup
在jdbcPersistenceAdapter标签中设置了createTablesOnStartup属性为true时在第一次启动ActiveMQ时,会自动创建需要的数据表,启动完成后可以去掉这个属性,或者改成false
下划线
操作系统的机器名中有"_"符号,要更改机器名并且重启后即解决问题。
JDBC WITH Journal
这种方式克服了JDBC Store的不足,JDBC每次消息过来,都需要去写库和读库
ActiveMQ Journal,使用高速缓存写入技术,大大提升了性能
当消费者的消费速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要写入到DB的消息。
举个例子:
生产者消费了1000条消息,这1000条消息会保存到journal文件,如果消费者的消费速度很快的情况下,在journal文件还没有同步到DB之前,消费者已经消费了90%的消息,那么这个时候只需要同步剩余的10%的消息到DB,如果消费者的消费速度很慢,这个时候journal文件可以使消息以批量方式写到DB。
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="4"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="activemq-data"/>
</persistenceFactory>
总结
持久化消息主要是指:
MQ所在的服务器宕机了消息不会丢失的机制
持久化机制演化过程:
从最初的AMQ Message Store方案到ActiveMQ V4版本中推出的High performance journal(高性能事务支持)附件,并且同步推出了关于关系型数据库的存储方案。ActiveMQ 5.3版本中又推出了对KahaDB的支持,后来ActiveMQ V5.8版本开始支持Level DB,到现在,V5.9+版本提供了标准的Zookeeper+LevelDB集群化方案。
ActiveMQ的消息持久化机制有:
AMQ------基于日志文件
KahaDB-------基于日志文件,从ActiveMQ 5.4开始默认的持久化插件
JDBC------基于第三方数据库
LevelDB-------基于文件的本地数据库存储,5.8之后推出了LevelDB的持久化引擎性能高于KahaDB
Replicated LevelDB Store-----从5.9提供了基于LevelDB和Zookeeper的数据复制方式,用于Master-slave方式的首选数据复制方案
无论使用哪种持久化方式,消息的存储逻辑都是一致的。