一.ActiveMQ的Broker
1.Broker是什么
- 相当于一个ActiveMQ服务器实例
- 说白了,Broker其实就是实现了用代码的形式启动ActiveMQ将MQ嵌入到Java代码中,以便随时用随时启动,在用的时候再去启动这样能节省资源,保证可靠性
2.按照配置文件启动
- 不同的配置文件模拟不同的实例
cp activemq.xml activemq02.xml
//读取activemq02.xml
./activemq start xbean:file:/xxxx/conf/activemq02.xml
3.嵌入式Broker
- 用ActiveMQ Broker作为独立的消息服务器来构建JAVA应用
- ActiveMQ也支持在vm中通信基于嵌入式的broker,能够无缝的集成其它java应用
A.pom引入
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
B.启动类
public class EmbedBroker {
public static void main(String[] args) throws Exception{
//ActiveMQ也支持在vm中通信基于嵌入式的broker
BrokerService brokerService = new BrokerService();
brokerService.setUseJmx(true);
brokerService.addConnector("tcp://localhost:61616");
brokerService.start();
}
}
C.测试
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://localhost:61616";
public static final String QUEUE_NAME = "queue01";
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.CLIENT_ACKNOWLEDGE);
//4.创建目的地(主题和队列) 接口类 队列和主题是它的子类
Queue queue = session.createQueue(QUEUE_NAME);//Collection collection = new ArrayList();
//5.创建消息的生产者
MessageProducer producer = session.createProducer(queue);
//6.通过使用消息生产者生产3条消息发送到MQ的队列里面
for (int i = 0; i <=3 ; i++) {
//7.创建消息(好比学生按照要求写好的面试题消息)
TextMessage textMessage = session.createTextMessage("broker message---" + i);//理解为一个字符串
//8.通过Producer发送给mq
producer.send(textMessage);
}
//9.关闭资源
producer.close();
//session.commit();
session.close();
connection.close();
System.out.println("******消息发布到MQ完成");
}
}
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://localhost:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("我是2号消费者");
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得连接connection 并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话session 第一个参数叫事务 签收
Session session = connection.createSession(false,Session.DUPS_OK_ACKNOWLEDGE);
//4.创建目的地(主题和队列) 接口类 队列和主题是它的子类
Queue queue = session.createQueue(QUEUE_NAME);//Collection collection = new ArrayList();
//5.创建消费者
MessageConsumer consumer = session.createConsumer(queue);
//同步阻塞方法receive
//订阅者或接受者调用MessageConsumer的receive方法来接收消息,receive方法在能够接收到消息之前(或超时之前)将一直阻塞
while(true){
TextMessage textMessage = (TextMessage) consumer.receive(4000L);
if(textMessage!=null){
System.out.println("****消费者接受到的broker消息:"+textMessage.getText());
textMessage.acknowledge();
}
else{
break;
}
}
consumer.close();
//session.commit();
session.close();
connection.close();
}
}
二.Spring整合ActiveMQ
1.Maven修改,需要添加Spring支持JMS的包
<!--activemq对JMS的支持,整合Spring和Activemq-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<!--activemq所需要的pool包配置-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.9</version>
</dependency>
<!--Spring-AOP等相关jar-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
2.Spring配置文件
- applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启包的自动扫描-->
<context:component-scan base-package="com.playmaker.activemq"/>
<!--配置生产者-->
<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<!--真正可以产生Connection的ConnectionFactory,由对应的JMS服务厂商提供-->
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://106.12.213.16:61616"></property>
</bean>
</property>
<property name="maxConnections" value="100"></property>
</bean>
<!-- 队列目的地 点对点的 -->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="spring-active-queue"></constructor-arg>
</bean>
<!-- Spring提供的JMS的工具类,可以进行消息发送,接收等-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="defaultDestination" ref="destinationQueue"/>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
</beans>
3.队列
- 生产者
@Service
public class SpringMQ_Produce {
@Resource
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SpringMQ_Produce produce = (SpringMQ_Produce) ctx.getBean("springMQ_Produce");
produce.jmsTemplate.send((session)->{
TextMessage textMessage = session.createTextMessage("*****spring和ActiveMQ的整合case111......");
return textMessage;
});
System.out.println("***********send task over");
}
}
- 消费者
@Service
public class SpringMQ_Consumer {
@Resource
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SpringMQ_Consumer consumer = (SpringMQ_Consumer) ctx.getBean("springMQ_Consumer");
String retValue = (String) consumer.jmsTemplate.receiveAndConvert();
System.out.println("************消费者收到的消息:"+retValue);
}
}
4.主题
- Spring配置文件,新增主题topic
- 代码不变
<!-- 主题目的地 -->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-topic"></constructor-arg>
</bean>
<!-- Spring提供的JMS的工具类,可以进行消息发送,接收等-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="defaultDestination" ref="destinationTopic"/>
<!-- <property name="defaultDestination" ref="destinationQueue"/>-->
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
- 先启动消费者监听,再启动生产者
5.在Spring里面实现消费者不启动,直接通过配置监听完成
- 前面这样类似于手写生产消费实现receive方法(同步阻塞),还有一种设置监听器的实现方法
- 简单点说就是把监听器的实现丢给spring
- 需要些一个类实现消息监听
- 只需要启动生产者,消费者不用启动,自动会监听记录
<!--配置监听程序-->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="destination" ref="destinationTopic"/>
<property name="messageListener" ref="myMessageListener"/>
</bean>
<!-- <bean id="myMessageListener" class="com.playmaker.activemq.spring.MyMessageListener"></bean>-->
@Component
public class MyMessageListener implements MessageListener {
@Override
public void onMessage(Message message) {
if(message!=null&&message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println(textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
三.SpringBoot整合ActiveMQ
1.队列
A.队列生产者
a.新建maven工程
- boot_mq_produce
b.pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
</dependencies>
c.application.yml
server:
port: 7777
spring:
activemq:
broker-url: tcp://106.12.213.16:61616 # 自己的MQ服务器地址
user: admin
password: admin
jms:
pub-sub-domain: false # false=Queue true=Topic 默认false
#自己定义队列名称
myqueue: boot-activemq-queue
d.配置Bean-类似Spring框架的applicationContext.xml文件
@Component
@EnableJms //开启jms适配注解
public class ConfigBean {
@Value("${myqueue}")
private String myQueue;
@Bean
public Queue queue(){
return new ActiveMQQueue(myQueue);
}
}
e.Queue_Produce
@Component
public class Queue_Produce {
@Resource
private JmsMessagingTemplate jmsMessagingTemplate;
@Resource
private Queue queue;
public void produceMsg(){
jmsMessagingTemplate.convertAndSend(queue,"******:"+ UUID.randomUUID().toString().substring(0,6));
}
}
f.主启动类MainApp_Produce
@SpringBootApplication
public class MainApp_Produce {
public static void main(String[] args) {
SpringApplication.run(MainApp_Produce.class,args);
}
}
g.测试单元
- 启动
@SpringBootTest(classes = MainApp_Produce.class) //针对类
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestActiveMQ {
@Resource
private Queue_Produce queue_produce;
@Test
public void testSend() throws Exception{
queue_produce.produceMsg();
}
}
h.新需求
- 要求每隔3秒往MQ推送消息以下定时发送Case,案例修改
- 修改Queue_Produce新增定时投递方法
- 修改主启动类MainApp_Produce
- 直接开启主启动类,间隔发送消息
//间隔时间3秒钟定投
@Scheduled(fixedDelay=3000)
public void produceMsgScheduled(){
jmsMessagingTemplate.convertAndSend(queue,"******Scheduled:"+ UUID.randomUUID().toString().substring(0,6));
System.out.println("************produceMsgScheduled send ok");
}
@SpringBootApplication
@EnableScheduling //开启
public class MainApp_Produce {
public static void main(String[] args) {
SpringApplication.run(MainApp_Produce.class,args);
}
}
B.队列消费者
a.新建maven工程并设置包名类名-boot_mq_consumer
b.pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
c.application.yml
server:
port: 8888
spring:
activemq:
broker-url: tcp://106.12.213.16:61616 # 自己的MQ服务器地址
user: admin
password: admin
jms:
pub-sub-domain: false # false=Queue true=Topic 默认false
#自己定义队列名称
myqueue: boot-activemq-queue
d.Queue_Consumer
@Component
public class Queue_Consumer {
@JmsListener(destination = "${myqueue}")
public void receive(TextMessage textMessage) throws Exception{
System.out.println("******消费者收到消息:"+textMessage.getText());
}
}
f.主启动类
@SpringBootApplication
public class MainApp_Consumer {
public static void main(String[] args) {
SpringApplication.run(MainApp_Consumer.class,args);
}
}
- 即使接收完了,消费者还是在监听中…
2.发布订阅
A.Topic生产者
a.新建maven工程并设置包名类名-boot_mq_topic_produce
b.pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
c.application.yml
server:
port: 6666
spring:
activemq:
broker-url: tcp://106.12.213.16:61616 # 自己的MQ服务器地址
user: admin
password: admin
jms:
pub-sub-domain: true # false=Queue true=Topic 默认false
#自己定义队列名称
myTopic: boot-activemq-topic
d.配置Bean
@Component
public class ConfigBean {
@Value("${myTopic}")
private String topicName;
@Bean
public Topic topic(){
return new ActiveMQTopic(topicName);
}
}
e.Topic_Produce
@Component
public class Topic_Produce {
@Resource
private JmsMessagingTemplate jmsMessagingTemplate;
@Resource
private Topic topic;
@Scheduled(fixedDelay = 3000)
public void produceTopic(){
jmsMessagingTemplate.convertAndSend(topic,"主题消息:"+ UUID.randomUUID().toString().substring(0,6));
}
}
f.主启动类MainApp_TopicProduce
@SpringBootApplication
@EnableScheduling
public class MainApp_TopicProduce {
public static void main(String[] args) {
SpringApplication.run(MainApp_TopicProduce.class,args);
}
}
B.Topic消费者
a.新建maven工程并设置包名类名-boot_mq_topic_consumer
b.pom.xml
- 同上
c.application.yml
server:
port: 5555 # 启动两个,等会改成5566
spring:
activemq:
broker-url: tcp://106.12.213.16:61616 # 自己的MQ服务器地址
user: admin
password: admin
jms:
pub-sub-domain: true # false=Queue true=Topic 默认false
#自己定义队列名称
myTopic: boot-activemq-mytopic
d.Topic_Consumer
@Component
public class Topic_Consumer {
@JmsListener(destination = "${myTopic}")
public void receive(TextMessage textMessage) throws JmsException {
try {
System.out.println("消费者收到订阅的主题:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
e.模拟两个订阅者主启动类,先启动订阅者再启动发布者
@SpringBootApplication
public class MainApp5555 {
public static void main(String[] args) {
SpringApplication.run(MainApp5555.class,args);
}
}
四.ActiveMQ的传输协议
- 日常使用前面几章就可以了,如果你想深入就需要了解这些东西
1.面试题
- 默认的61616端口如何修改
- 你生产上的链接协议如何配置的?
2.官网
- 生产上面的传输协议是NIO
3.是什么
- ActiveMQ支持的client-broker通讯协议有:TCP,NIO,UDP,SSL,Http(s)、VM
- 其中配置Transport Connector的文件在activeMQ安装目录的conf/activemq.xml中的
<transportConnectors>
标签之内
- 在上文给出的配置信息中,URI描述信息的头部都是采用协议名称
- 例如,描述amqp协议的监听端口时,采用的URI描述格式为"amqp://…"
- 描述Stomp协议的监听端口时,采用的URI描述格式为"stomp://…"
- 唯独在进行openwire协议描述时,URI头却采用的"tcp://…",这是因为ActiveMQ中默认的消息协议就是openwire
4.有哪些
A.TCP
- 这是默认的Broker配置,TCP的Client监听端口61616
- 在网络传输数据前,必须要序列化数据,消息是通过一个叫wire protocol的来序列化成字节流
- 默认情况下ActiveMQ把wire protocol叫做OpenWire,它的目的使促使网络上的效率和数据快速交互
- TCP连接的URI形式如:tcp://hostname:port?key=value&key=value,后面的参数是可选
- TCP传输的优点:
- TCP协议传输可靠性高,稳定性强
- 高效性:字节流方式传递,效率很高
- 有效性,可用性:应用广泛,支持任何平台
- 关于Transport协议的可配置参数可以参考官网:官网
B.NIO
- NIO协议和TCP协议类似但NIO更侧重于底层的访问操作。它允许开发人员对同一资源可有更多的client调用和服务端有更多的负载
- 适合使用NIO协议的场景
- 可能有大量的Client去链接到Broker上,一般情况下,大量的Client去连接Broker是被操作系统的线程所限制的。因此,NIO的实现比TCP需要更少的线程去运行,所以建议使用NIO协议
- 可能对于Broker有一个很迟钝的网络传输,NIO比TCP提供更好的性能
- NIO的连接URI形式:nio//hostname:port?key=vale
- 关于Transport Connector协议的配置示例:官网
C.AMQP协议
- 即Advanced Message Queuing Protocol,一个=提供统一消息服务的应用层标准高级消息队列协议,是应用层一些的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制
- 官网
D.STOMP协议
- STOMP,Streaming Text Orientated Message Protocol,是流文本定向消息协议,是一种为MOM(面向消息的中间件)设计的简单文本协议
E.Secure Sockets Layer Protocal(SSL)
- SSL是一种安全加固协议
- 连接的URL形式:ssl://hostname:port?key=value
- 官网
F.Mqtt协议
-
消息队列遥测传输,是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品喝外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议
-
https://github.com/fusesource/mqtt-client
G.WS协议
- 偏向前端html5 WebSockets
H.小总结
- 工作常用TCP和NIO
6.NIO案例演示
A.官网
B.修改配置文件activemq.xml
<transportConnectors>
<transportConnector name="nio" uri="nio://0.0.0.0:61618?trace=true"/>
</<transportConnectors>
- 如果不特定指定ActiveMQ的网络监听端口,那么这些端口都将使用BIO网络IO模型|(OpenWire,STOMP,AMQP…)
- 所以为了首先提高单节点的网络吞吐性能,我们需要明确指定Active的网络IO模型
- 如下所示:URI格式头以"nio"开头,表示这个端口使用以TCP协议为基础的NIO网络IO模型
C.生产和消费两端协议代码修改
- 新添加的配置没有影响之前的tcp协议连接的情况
D.运行验证
- 注意,如果你的CentOS7没有关闭防火墙,就需要开放61618端口,不然会连接不上…
firewall-cmd --add-port=61618/tcp --permanent
7.NIO案例演示增强
A.上述NIO性能不错了,如何进一步优化?
B.问题
- URI格式头以"nio"开头,表示这个端口使用以TCP协议为基础的NIO网络IO模型,但是这样的设置方式,只能让这个端口支持openwire协议
- 出厂默认是BIO+TCP,现在调成了NIO+TCP,想调成NIO+TCP/MQTT/STOMP(多协议)
C.那么我们怎么既让这个端口支持NIO网络IO模型,又让它支持多个协议呢?
D.解决
a.使用auto关键字
b.使用"+"符号来为端口设置多种特性
c.如果我们即需要某一个端口支持NIO网络IO模型,又需要它支持多个协议
<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61608?maximumConnections=1000&wireFormat.maxFrameSize=104857600&org.apache.activemq.transport.nio.SelectorManager.corePoolSize=20&org.apache.activemq.transport.nio.Se1ectorManager.maximumPoo1Size=50"/>
- 正常启动,能访问就没问题
- 改成了tcp呢
- 这样就是端口统一,协议随便用,当然换成一些其他协议,代码可能会发生一些变化
- 如果你在配置文件只写nio+auto,把其他所有都注释掉这也是可以的…但需要在生产环境上做好足够的测试哦
五.ActiveMQ的消息存储和持久化
1.引言
- MQ的高可用
- 事务
- 持久
- 签收(前三个MQ自带)
- 可持久化(同样一份东西保存在不同的两台物理机上,增强了高可用)
2.官网
3.是什么
A.面试题
- AMQ持久化机制聊聊
B.回忆redis持久化机制有几种
- RDB:以文件形式
- AOF:记录所有写操作,重新执行一遍
C.说明
-
为了避免意外宕机以后丢失信息,需要做到重启后可以恢复消息队列,消息系统一般都会采用持久化机制
-
ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的
-
就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等再试图将消息发送给接收者,成功则将消息从存储中删除,失败则继续尝试发送
-
消息中心启动以后首先要检查指定的存储位置,如果有未发送成功的消息,则需要把消息发送出去
-
一句话,MQ服务器宕机了,消息不会丢失的机制
4.有哪些
A.AMQ Message Store(了解)
- 基于文件的存储方式,是以前的默认消息存储,现在不用了
- 具有写入速度快和容易恢复的特点。消息存储在一个个文件中,文件默认大小为32M,当一个存储文件中的消息已经全部被消费,那么这个文件将被标识为可删除,在下一个清除阶段,这个文件被删除。AMQ适用于ActiveMQ5.3之前的版本
B.KahaDB消息存储(默认)
- 基于日志文件,从ActiveMQ5.4开始默认的持久化插件,类似于redis持久化机制AOF,记录下你做了哪些操作
- 5.4之后默认
- 验证
- 配置文件里默认的配置
- 配置文件里默认的配置
- 说明
KahaDB是目前默认的存储方式,可用于任何场景,提高了性能和恢复能力
消息存储使用一个事务日志和仅仅用一个索引文件来存储它所有的地址
KahaDB是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化
数据被追加到data logs中,当不再需要log文件中的数据的时候,log文件会被丢弃
- KahaDB的存储原理
kahaDB在消息保存目录中只有4类文件和一个lock,更ActiveMQ的其他几种文件存储引擎相比这就飞翔简洁了
db-<Number>.log
KahaDB存储消息到预定义大小的数据记录文件中,文件命名为db-<Number>.log
。当数据文件已满时,一个新的文件会随之创建,number数值也会随之递增,它随着消息数量的增多,如每32M一个文件,文件名按照数字进行编号,如db-1.log,db-2.log…当不再有引用到数据文件中的任何消息时,文件会被删除或归档-主要存储数据
db.data 该文件包含了持久化的BTree索引,索引了消息数据记录中的消息,它是消息的索引文件,本质是B-Tree(B树),使用B-Tree作为索引指向db-<Number>.log
里面存储的消息-主要存储索引文件
db.free 当前db.data文件里哪些页面是空闲的,文件具体内容是所有空闲页的ID
db.redo 用来进行消息恢复,如果KahaDB消息存储在强制退出后启动,用于恢复BTree索引
lock文件锁,表示当前获得KahaDB读写权限的broker,类似于mysql的悲观锁(写独占,查询共享)
- 对于mysql来说,MYISYM引擎,创建一个表会生成三个文件,FRM(数据库表结构),MYD(表的数据),MYI(表的索引)
C.JDBC消息存储-消息基于JDBC存储的
- 消息有一部分会真实的进入mysql和oracle数据库
D.LevelDB消息存储(了解)
- 这种文件系统是从ActiveMQ5.8之后引进的,它和KahaDB非常相似,也是基于文件的本地数据库存储形式,但是它提供比KahaDB更快的持久性
- 但它不使用自定义B-Tree实现来索引预写日志,而是使用基于LevelDB的索引
//默认配置如下:
<persistenceAdapter>
<levelDBdirectory="activemq-data"/>
</persistenceAdapter>
E.JDBC Message store with ActiveMQ Journal
5.JDBC消息存储
A.MQ+MySQL
- 持久化操作就需要mysql
B.添加mysql数据库的驱动包到lib文件夹
C.jdbcPersistenceAdapter配置
- 修改conf路径下activemq.xml配置文件
- ‘#’类似于spring配置文件中的ref属性,引用
D.数据库连接池配置
- 注意写在broker标签后,import标签之前
- 这里默认连接数据库方式是dbcp2,如果你想换成c3p0或者阿里的德鲁伊,就需要你自己加入相应的jar包到lib文件夹中
<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://ip地址/activemq?relaxAutoCommit=true"/>
<property name="username" value="用户名"/>
<property name="password" value="密码"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
E.建仓SQL和建表说明
a.建一个名为activemq的数据库
CREATE DATABASE activemq
b.三张表的说明
-
ACTIVEMQ_MSGS
-
ACTIVEMQ_ACKS
-
ACTIVEMQ_LOCK
c.如果新建数据库ok+上述配置ok+代码运行ok,3表会自动生成
d.万一情况,手动建表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);
F.代码运行验证
- 一定要开启持久化
a.队列
-
生产
-
消费
b.topic
-
生产
-
消费
G.数据库情况
-
队列生产
-
队列消费
-
点到点
- 当DeliveryMode设置为NON_PERSISTENCE时,消息被保存在内存中
- 当DeliveryMode设置为PERSISTENCE时,消息保存在broker的相应的文件或者数据库中
- 而且点对点类型中消息一旦被Consumer消费就从broker中删除
-
换成非持久化
- 生产消息,msgs表并不会有数据
- 生产消息,msgs表并不会有数据
-
主题消费
- 先启动消费者订阅再运行生产,看看activemq_acks
- 先启动消费者订阅再运行生产,看看activemq_acks
-
主题生产
I.小总结
- 如果是queue
- 在没有消费者消费的情况下会将消息保存到activemq_msgs表中,只要有任意一个消费者已经消费过,消费之后这些消息将会立即被删除
- 如果是topic
- 一般是先启动消费订阅然后再生产的情况下会将消息保存到activemq_acks
J.开发有坑
- 在配置关系型数据库作为ActiveMQ的持久化存储方案时,有坑
- 数据库jar包
- 记得需要使用到的jar包防止到lib目录下,mysql-jdbc驱动的jar和对应数据库连接池jar包
- createTablesOnStartup属性
- 在jdbcPersistenceAdapter标签中设置了createTablesOnStartup属性为true时在第一次启动ActiveMQ时,ActiveMQ服务节点会自动创建所需要的数据表。启动完成后可以去掉这个属性,或者更改createTablesOnStartup属性为false
- 下划线坑爹
- “java.lang.lllegalStateException:BeanFactory not initialized or already closed”这是因为您的操作系统的机器名中有"_"符号。请更改机器名并且重启后即可解决问题
- 数据库jar包
6.JDBC Message store with ActiveMQ Journal(官网强烈用这个)
A.是什么
- 这种方式克服了JDBC Store的不足,JDBC每次消息过来,都需要去写库和读库
- ActiveMQ Journal,使用高速缓存写入技术,大大提高了性能
- 当消费者的消费速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要写入到DB中的消息
- 举个例子:
- 生产者生产了1000条消息,这1000条消息会保存到journal文件,如果消费者的消费速度很快的情况下,在journal文件还没有同步到DB之前,消费者已经消费了90%以上的消息,那么这个时候只需要同步剩余的10%的消息到DB
- 如果消费者的消费速度很慢,这个时候journal文件可以使消息以批量方式写到DB
B.配置
C.重启配置文件激活
./activemq stop
D.运行
./activemq start
- 先执行queue生产者,再查询msgs数据库,没有任何信息,因为高速缓存挡在mysql数据库前面了,过一会才会同步到mysql数据库。queue消费者消费后msgs数据库自然也是没有的
7.ActiveMQ持久化机制小总结
-
持久化消息主要是指
- MQ所在的服务器宕机了消息不会丢失的机制
-
持久化机制演化过程
- 从最初的AMQ Message Store方案到ActiveMQ V4版本中推出的High performance journal(高性能事务支持)附件,并且同步推出了关于关系型数据库的存储方案。ActiveMQ 5.3版本中又推出对KahaDB的支持(V5.4版本后称为ActiveMQ默认的持久化方案),后来ActiveMQ V5.8版本开始支持LevelDB,到现在,V5.9+版本又提供了标准的zookeeper+LevelDB集群化方案。我们重点介绍了KahaDB,LevelDB和mysql数据库这三种持久化存储方案,下面介绍使用Zookeeper搭建LevelDB集群存储方案
-
ActiveMQ的消息持久化机制有
-
无论使用哪种持久化方式,像爱惜的存储逻辑都是一致的