ActiveMQ
ActiveMQ的消息储存和持久化
1.1 介绍
(1) 此处持久化和之前的持久化的区别
MQ高可用:事务、可持久、签收,是属于MQ自身特性,自带的。这里的持久化是外力
,是外部插件
。
之前讲的持久化是MQ的外在表现,现在讲的的持久是是底层实现。
通过独立的外部插件,如:文件储存、数据库等来做持久化备份,而不是单单储存在MQ数据库本身,加强了信息的高可用
MQ如百度云盘,外部插件如本地硬盘
(2)是什么:
官网文档:http://activemq.apache.org/persistence
持久化是什么?一句话就是:ActiveMQ宕机了,消息不会丢失的机制。
说明:为了避免意外宕机以后丢失信息,需要做到重启后可以恢复消息队列,消息系统一半都会采用持久化机制。ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的。就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等。再试图将消息发给接收者,成功则将消息从存储中删除,失败则继续尝试尝试发送。消息中心启动以后,要先检查指定的存储位置是否有未成功发送的消息,如果有,则会先把存储位置中的消息发出去。
1.2 ActiveMQ有哪些持久化机制
(1)AMQ Message Store

基于文件的存储机制,是以前的默认机制,现在不再使用。
AMQ是一种文件存储形式,它具有写入速度快和容易恢复的特点。消息存储再一个个文件中文件的默认大小为32M,当一个文件中的消息已经全部被消费,那么这个文件将被标识为可删除,在下一个清除阶段,这个文件被删除。AMQ适用于ActiveMQ5.3之前的版本
(2)kahaDB
现在默认的
。下面我们再详细介绍。
(3)JDBC消息存储
下面我们再详细介绍。
(4)LevelDB消息存储
过于新兴的技术,现在有些不确定。
(5)JDBC Message Store with ActiveMQ Journal
下面我们再详细介绍。
1.3 kahaDB消息存储
(1)介绍
基于日志文件,从ActiveMQ5.4(含)开始默认的
持久化插件。
官网文档:http://activemq.aache.org/kahadb
官网上还有一些其他配置参数。
配置文件activemq.xml中,如下:↓
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
日志文件的存储目录在:%activemq安装目录%/data/kahadb
(2)说明
(3)KahaDB的存储原理
.log 存 文件
.data 存 索引
1.4 JDBC消息存储
1.4.1 设置步骤
(1)原理图
(2)添加mysql数据库的驱动包到lib文件夹
(3)jdbcPersistenceAdapter配置
修改配置文件activemq.xml。将之前的替换为jdbc的配置。如下:
<!--
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
-->
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" createTableOnStartup="true"/>
</persistenceAdapter>
(4)数据库连接池配置
需要我们准备一个mysql数据库,并创建一个名为activemq的数据库。
在< /broker>
标签和< import>
标签之间插入数据库连接池配置:↓↓↓
具体操作如下:
</broker>
<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://mysql数据库URL/activemq?relaxAutoCommit=true"/>
<property name="username" value="mysql数据库用户名"/>
<property name="password" value="mysql数据库密码"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
<import resource="jetty.xml"/>
之后需要建一个数据库,名为activemq。新建的数据库要采用latin1
或者ASCII编码
。
https://blog.csdn.net/JeremyJiaming/article/details/88734762
默认是的dbcp数据库连接池,如果要换成其他数据库连接池,需要将该连接池jar包,也放到lib目录下。
然后将class引用修改成你导入的连接池jar包的DataSource
(5)建库SQL和创表说明
重启activemq。会自动生成如下3张表。如果没有自动生成,需要我们手动执行SQL。
我个人建议要自动生成,我在操作过程中查看日志文件,发现了不少问题,最终解决了这些问题后,是能够自动生成的。如果不能自动生成说明你的操作有问题。如果实在不行,下面是手动建表的SQL:
手动建表的代码sql代码:
-- auto-generated definition
create table ACTIVEMQ_ACKS
(
CONTAINER varchar(250) not null comment '消息的Destination',
SUB_DEST varchar(250) null comment '如果使用的是Static集群,这个字段会有集群其他系统的信息',
CLIENT_ID varchar(250) not null comment '每个订阅者都必须有一个唯一的客户端ID用以区分',
SUB_NAME varchar(250) not null comment '订阅者名称',
SELECTOR varchar(250) null comment '选择器,可以选择只消费满足条件的消息,条件可以用自定义属性实现,可支持多属性AND和OR操作',
LAST_ACKED_ID bigint null comment '记录消费过消息的ID',
PRIORITY bigint default 5 not null comment '优先级,默认5',
XID varchar(250) null,
primary key (CONTAINER, CLIENT_ID, SUB_NAME, PRIORITY)
)
comment '用于存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存';
create index ACTIVEMQ_ACKS_XIDX
on ACTIVEMQ_ACKS (XID);
-- auto-generated definition
create table ACTIVEMQ_LOCK
(
ID bigint not null
primary key,
TIME bigint null,
BROKER_NAME varchar(250) null
);
-- auto-generated definition
create table ACTIVEMQ_MSGS
(
ID bigint not null
primary key,
CONTAINER varchar(250) not null,
MSGID_PROD varchar(250) null,
MSGID_SEQ bigint null,
EXPIRATION bigint null,
MSG blob null,
PRIORITY bigint null,
XID varchar(250) null
);
create index ACTIVEMQ_MSGS_CIDX
on ACTIVEMQ_MSGS (CONTAINER);
create index ACTIVEMQ_MSGS_EIDX
on ACTIVEMQ_MSGS (EXPIRATION);
create index ACTIVEMQ_MSGS_MIDX
on ACTIVEMQ_MSGS (MSGID_PROD, MSGID_SEQ);
create index ACTIVEMQ_MSGS_PIDX
on ACTIVEMQ_MSGS (PRIORITY);
create index ACTIVEMQ_MSGS_XIDX
on ACTIVEMQ_MSGS (XID);
ACTIVEMQ_MSGS数据表:
ACTIVEMQ_ACKS数据表:
ACTIVEMQ_LOCK数据表:
阿昌遇到的问题:
根据提示说createTableOnStartup不能出现在↓中
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" createTableOnStartup="true"/>
</persistenceAdapter>
↓↓↓除去后
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>
再一次出现问题
网上查找一了波,发现需要在persistenceAdapter中添加useDatabaseLock="false"
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" useDatabaseLock="false" />
</persistenceAdapter>
↓↓↓过了一会再去启动activemq
发现启动成功
↓↓↓登录前台控制网页http://192.168.109.101:8161/
发现登录不了
查看问题还是如上
↓↓↓过了一会再去重启activemq
可以正常访问
查看mysql数据库是否自动生成表
自动生成,成功
1.4.2 queue验证和数据表变化
//设置持久化
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
===================================================================
//设置不持久化
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
queue模式,非持久化不会将消息持久化到数据库。↑
queue模式,持久化会将消息持久化数据库。↑
启动消费者,消费了所有的消息后,发现数据表的数据消失了。
queue模式非持久化,不会持久化消息到数据表。
阿昌遇到的问题:↓
全部工作完成之后,在启动生产者代码后报错,查看日志↓
这是因为,mysql默认的binlog_format是STATEMENT,而在READ COMMITTED或READ UNCOMMITTED隔离级别下,innodb只能使用的binlog_format是ROW。
而在ActiveMQ的store JDBC实现中(TransactionContext),为了提高并发性能,使用的是READ UNCOMMITTED:
解决方法:
①在mysql里设置binlog_format=row
,此时binlog会增大,但是一般来说对数据复制支持的更好,建议单机高性能环境下使用。
②在activemq.xml的jdbcPersistenceAdapter里配置transactionIsolation="4"
1.4.3 topic验证和说明
先启动一下持久化topic的消费者。看到ACTIVEMQ_ACKS数据表多了一条消息。
生产者代码:
public class JmsConsumer_topic_Persist_jdbc {
public static final String ACTIVEMQ_URL = "tcp://192.168.109.101:61616";
public static final String TOPIC_NAME = "topic-jdbc01";
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection conn = mqConnectionFactory.createConnection();
conn.setClientID("marrry");
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber subscriber = session.createDurableSubscriber(topic, "remark。。。");
conn.start();
Message receive = subscriber.receive();
while (null != receive) {
TextMessage textMessage = (TextMessage) receive;
String messageText = textMessage.getText();
System.out.println("收到持久化 topic: " + messageText);
receive = subscriber.receive();
}
conn.close();
session.close();
}
}
消费者代码:
public class JmsProducer_topic_Persist_jdbc {
public static final String ACTIVEMQ_URL = "tcp://192.168.109.101:61616";
public static final String TOPIC_NAME = "topic-jdbc01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection conn = mqConnectionFactory.createConnection();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageProducer messageProducer = session.createProducer(topic);
=======================================================================
//会话设置持久化
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
=======================================================================
// 设置持久化topic之后再,启动连接
conn.start();
for (int i = 1; i <= 3; i++) {
TextMessage textMessage = session.createTextMessage("TOPIC_NAME-jdbc01--" + i);
messageProducer.send(textMessage);
}
messageProducer.close();
session.close();
conn.close();
System.out.println("===TOPIC_NAME=====消息发布到MQ完成===========");
}
}
运行以上消费者代码ACTIVEMQ_ACKS数据表,多了一个消费者的身份信息。一条记录代表:一个持久化topic消费者
我们启动持久化生产者发布3个数据,ACTIVEMQ_MSGS数据表新增3条数据,消费者消费所有的数据后,ACTIVEMQ_MSGS数据表的数据并没有消失。持久化topic的消息不管是否被消费,是否有消费者,产生的数据永远都存在,且只存储一条。
这个是要注意的,持久化的topic大量数据后可能导致性能下降。这里就像公众号一样,消费者消费完后,消息还会保留。
14.4 总结
开发中的坑:
1.5 JDBC Message Store with ActiveMQ Journal
(1)说明
这种方式克服了JDBC Store的不足,JDBC每次消息过来,都需要去写库读库。ActiveMQ Journal,使用高速缓存写入技术,大大提高了性能。当消费者的速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要写入到DB中的消息。
举个例子:生产者生产了1000条消息,这1000条消息会保存到journal文件,如果消费者的消费速度很快的情况下,在journal文件还没有同步到DB之前,消费者已经消费了90%的以上消息,那么这个时候只需要同步剩余的10%的消息到DB。如果消费者的速度很慢,这个时候journal文件可以使消息以批量方式写到DB。
为了高性能,这种方式使用日志文件存储+数据库存储。先将消息持久到日志文件,等待一段时间再将未消费的消息持久到数据库。该方式要比JDBC性能要高。
在mysql和activemq中加了一层高速缓存
(2)配置
下面是基于上面JDBC配置,再做一点修改:
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="4"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="activemq-data" />
</persistenceFactory>
阿昌遇到的问题:
添加上了上面的配置内容后,访问8161前台,被拒绝
解决方法:跟前面的一样在,
useDatabaseLock="false"
1.6 总结
① jdbc效率低,kahaDB效率高,jdbc+Journal效率较高。
② 持久化消息主要指的是:MQ所在服务器宕机了消息不会丢试的机制。
③ 持久化机制演变的过程:
从最初的AMQ Message Store方案到ActiveMQ V4版本退出的High Performance Journal(高性能事务支持)附件,并且同步推出了关于关系型数据库的存储方案。ActiveMQ5.3版本又推出了对KahaDB的支持(5.4版本后被作为默认的持久化方案),后来ActiveMQ 5.8版本开始支持LevelDB,到现在5.9提供了标准的Zookeeper+LevelDB集群化方案。
④ ActiveMQ消息持久化机制有:
AMQ | 基于日志文件 |
---|---|
KahaDB | 基于日志文件,从ActiveMQ5.4开始默认使用 |
JDBC | 基于第三方数据库 |
Replicated LevelDB Store | 从5.9开始提供了LevelDB和Zookeeper的数据复制方法,用于Master-slave方式的首选数据复制方案。 |
感谢尚硅谷