ActiveMQ的消息存储和持久化

目录

一、官网

二、简介

三、是什么

四、详解

(一)AMQ Message Store

(二)kahaDB(当前默认存储机制)

(三)LevelDB

(四)JDBC消息存储

(五)JDBC Message Store with ActiveMQ Journal

五、总结


一、官网

ActiveMQ (apache.org)

可以看到最新版本中LevelDB已经被弃用,推荐使用的是kahaDB

二、简介

此处持久化和之前的持久化的区别

MQ高可用:事务、可持久、签收,是属于MQ自身特性,自带的。这里的持久化是外力,是外部插件。之前讲的持久化是MQ的外在表现,现在讲的的持久是是底层实现。

三、是什么

持久化是什么?一句话就是:ActiveMQ宕机了,消息不会丢失的机制。

说明:为了避免意外宕机以后丢失信息,需要做到重启后可以恢复消息队列,消息系统一半都会采用持久化机制。ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的。就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等。再试图将消息发给接收者,成功则将消息从存储中删除,失败则继续尝试发送。消息中心启动以后,要先检查指定的存储位置是否有未成功发送的消息,如果有,则会先把存储位置中的消息发出去。

四、详解

(一)AMQ Message Store

基于文件的存储机制,是以前的默认机制,现在不再使用。

AMQ是一种文件存储形式,它具有写入速度快和容易恢复的特点。消息存储再一个个文件中文件的默认大小为32M,当一个文件中的消息已经全部被消费,那么这个文件将被标识为可删除,在下一个清除阶段,这个文件被删除。AMQ适用于ActiveMQ5.3之前的版本

可以认为曾经牛逼过,现在大势已去。了解即可!

(二)kahaDB(当前默认存储机制)

1.conf/activemq.xml配置文件

kahadb的路径在activemq目录下的data目录中

kahadb目录结构

  

 2.官网

ActiveMQ (apache.org)

3.kahaDB存储原理

KahaDB是目前默认的存储方式,可用于任何场景,提高了性能和恢复能力。消息存储使用一个事务日志和仅仅用一个索引文件来存储它所有的地址。KahaDB是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化。数据被追加到data logs中。当不再需要log文件中的数据的时候,og文件会被丢弃。

4.目录结构

kahadb在消息保存目录中只有4类文件和一个ock,跟ActiveMQ的其他几种文件存储引擎相比这就非常简洁了。

① db-<Number>.log 

KahaDB存储消息到预定义大小的数据记录文件中,文件命名为db-<Number>.log。当数据文件已满时,一个新的文件会随之创建,number数值也会随之递增,它随着消息数量的增多,如每32M一个文件,文件名按照数字进行编号,如db-1.log、db-2.log、db-3.log·。当不再有引用到数据文件中的任何消息时,文件会被删除或归档。

② db.data

该文件包含了持久化的BTree索引,索引了消息数据记录中的消息,它是消息的索引文件,本质上是B-Tree (B树),使用B-Tree作为索引指向db-<Number>.log里面存储的消息。
③ db.free

当前db.data文件里哪些页面是空闲的,文件具体内容是所有空闲页的ID

④ db.redo

用来进行消息恢复,如果KahaDB消息存储在强制退出后启动,用于恢复BTree索引。

⑤ lock

文件锁,表示当前获得kahadb读写权限的broker。

(三)LevelDB

这种文件系统是从ActiveMQ5.8之后引进的,它和KahaDB非常相似,也是基于文件的本地数据库储在形式,但是它提供比KahaDB更快的持久性。
但它不使用自定义B-Tree实现来索引预写日志,而是使用基于LevelDB的索引

默认配置如下:
< persistenceAdapter >
        < levelDBdirectory = "activemq-data" />
</ persistenceAdapter >

(四)JDBC消息存储

1.MQ+Mysql

2.添加mysql数据库的驱动包到lib文件夹

默认是的dbcp数据库连接池,如果要换成其他数据库连接池,需要将该连接池jar包,也放到lib目录下。

3.jdbcPersistenceAdapter配置

<persistenceAdapter>

        <jdbcPersistenceAdapter dataSource=“#mysql-ds" createTableStartup="true"/>

</persistenceAdapter>

dataSource指定将要引用的持久化数据库的bean名称;

createTablesOnStartup是否在启动的时候创建数据表,默认值是true。这样每次启动都会去创建数据表了,一般是第一次启动的时候设置为true之后改成false。 

 

 

4.数据库连接池配置

需要我们准备一个mysql数据库,并创建一个名为activemq的数据库并创建一个用户zjy,给zjy授予权限。

 

①参照官网ActiveMQ (apache.org)

 

 <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】:3306/activemq?activemq?characterEncoding=utf8&amp;useSSL=false&amp;allowPublicKeyRetrieval=true&amp;serverTimezone=UTC&amp;relaxAutoCommit=true""/>
        <property name="username" value="【数据库用户名】"/>
        <property name="password" value="【数据库密码】"/>
        <property name="poolPreparedStatements" value="true"/>
</bean>

 

在</broker>标签和<import>标签之间插入数据库连接池配置 


5.建仓SQL和建表说明

重启activemq。会自动生成如下3张表。

如果没有自动生成,需要我们手动执行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数据表:

6.代码运行验证

 生产者都要开启持久化

MessageProducer producer = session.createProducer(queue);
producer.setDeliveryMode(DeliveryMode.PERSISTENT);

(1)队列生产者

public class JMSProduce {

    public static final String ACTIVEMQ_URL = "tcp://193.179.123.10:61616";
    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
        //3.1 两个参数 ①事务 ②签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(queue or topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建生产者
        MessageProducer producer = session.createProducer(queue);
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);
        //6.通过MessageProducer生成3条消息到MQ队列中
        for(int i = 1 ; i <= 3; i ++ ) {
            TextMessage textMessage = session.createTextMessage("jdbcMessage--" + i);
            producer.send(textMessage);
        }
        producer.close();
        session.close();
        connection.close();

        System.out.println("消息发布到完成……");
    }
}

运行代码后,到数据库activemq_msgs中查看,有三条记录

 

 

(2)队列消费者

启动消费者,消费了所有的消息后,发现数据表的数据消失了。

public class JmsConsumer {
        public static final String ACTIVEMQ_URL = "tcp://193.179.123.10:61616";
    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
        //3.1 两个参数 ①事务 ②签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(queue or topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建消费者
        MessageConsumer consumer = session.createConsumer(queue);
       

        //通过监听方式读取消息
        /*
         异步非阻塞方式(监听器onMessage())
        订阅者或接收者通过MessageConsumer的setMessageListener(MessageListener listener)注册一个消息监听器
        当消息到达之后,系统自动调用监听器MessageListener的onMessage(Message message)方法。*/
        consumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                if(null != message && message instanceof TextMessage) {
                    TextMessage textMessage = (TextMessage) message;
                    try {
                        System.out.println("监听到队列消息:"+textMessage.getText());
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //连接AvtiveMQ需要等待时间
        System.in.read();
        consumer.close();
        session.close();
        connection.close();
    }
}

 

总结

queue模式,非持久化不会将消息持久化到数据库。

queue模式,持久化会将消息持久化数据库。

我们使用queue模式持久化,发布3条消息后,发现ACTIVEMQ_MSGS数据表多了3条数据。

启动消费者,消费了所有的消息后,发现数据表的数据消失了。

(3)主题消费者

public class JmsConsumer_persist {
    public static final String ACTIVEMQ_URL = "tcp://193.179.123.10:61616";
    public static final String TOPIC_NAME = "topic-jdbc-persist";

    public static void main(String[] args) throws JMSException, IOException {

        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.setClientID("z3");
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Topic topic = session.createTopic(TOPIC_NAME);
        TopicSubscriber subscriber = session.createDurableSubscriber(topic, "remark");
        connection.start();

        Message message = subscriber.receive();
        while(null != message) {
            TextMessage textMessage = (TextMessage)message;
            System.out.println("******收到的持久化topic:"+textMessage.getText());
            message = subscriber.receive(1000);
        }

        session.close();
        connection.close();
    }
}

启动消费者后到activemq_acks数据中查看,发现有一条记录 

 

(4)主题生产者

public class JmsProduce_persist {
    public static final String ACTIVEMQ_URL = "tcp://192.168.23.100:61616";
    public static final String TOPIC_NAME = "topic-jdbc-persist";

    public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        Connection connection = activeMQConnectionFactory.createConnection();

        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Topic topic = session.createTopic(TOPIC_NAME);
        MessageProducer producer = session.createProducer(topic);
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);
        connection.start();
        for(int i = 1 ; i <= 3; i ++ ) {
            TextMessage textMessage = session.createTextMessage("TextMessage--" + i);
            producer.send(textMessage);
        }
        producer.close();
        session.close();
        connection.close();

        System.out.println("topicMessage消息发布到MQ完成……");
    }
}

 生产者启动后可以看到activemq_msg数据库多出了三条记录,且消费者下线后仍然存在

 所以activemq_msg表实际上存放着queue和topic,但是queue消费后就被删除了,而topic记录会一直存在


 

 

如果在前台将用户z3删除,那么acks表中的z3的记录也会消失 

 7.小总结


8.开发有坑

 

 (五)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性能要高。

2.配置并重启服务

 启动队列生产者发现消息并没有立即存储到activemq_msg数据库中,因为首先是存储在jdbc高速缓存日志中,过一段时间如果还未消费才会存储进数据库。

五、总结

  • jdbc效率低,kahaDB效率高,jdbc+Journal效率较高。
  • 持久化消息主要指的是:MQ所在服务器宕机了消息不会丢试的机制。
  • 持久化机制演变的过程:

从最初的AMQ Message Store方案到ActiveMQ V4版本退出的High Performance Journal(高性能事务支持)附件,并且同步推出了关于关系型数据库的存储方案。ActiveMQ5.3版本又推出了对KahaDB的支持(5.4版本后被作为默认的持久化方案),后来ActiveMQ 5.8版本开始支持LevelDB,到现在V5.14.2 / V5.17.0还是推荐KahaDB。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zoeil

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值