消息中间件ActiveMQ学习笔记
目录
前言
让我们跟随尚硅谷周阳老师的讲解步入消息中间件MQ之ActiveMQ的学习吧。
1 入门概述
1.1 MQ的产品种类和对比
1.2 MQ的产生背景
1.2.1 系统之间接口耦合比较严重 ----解耦
1.2.2 面对大流量并发时,容易被冲垮 ----- 削峰
2.2.3 等待同步存在性能问题 ----- 异步
1.3 MQ的主要作用
1.4 MQ的定义
1.5 MQ的特点
2 ActiveMQ 安装和控制台
2.1 ActiveMQ安装
前提条件:linux服务器要安装jdk8以上的版本。
跟随老师讲课版本是5.15.9 安装在linux上。 官网下载地址
安装步骤:
- /opt 目录下解压
- 将解压的文件夹 移动或剪切到 我们专门去创建的myactiveMQ文件夹下。
- 启动
- 查看启动状态(三种方式)
- 关闭服务
- 带日志方式的启动
2.2 ActiveMQ控制台
首先将linux和windows的防火墙给关闭,这样才能进行相互连接。。因为我们将windows做客户端,将linux做服务器端,通过windows浏览器ip地址访问服务器端即可。
防火墙未关闭前:
防火墙关闭后:
注意linxu关闭防火墙服务的时候要区分是cnetos7还是centos6。
Linux关闭防火漆服务:
linux能连接到本机。
windows能ping通linux服务器
ActiveMQ端口号:
61616 :后台端口号
8161:前台端口号
在windows客户端中通过ip地址访问。如下图:(以前是localhost是因为安装就是在本地windows)
登录默认用户名和密码是:admin。
3 入门案例、MQ标准、API详解
3.1 pom导入依赖
<dependencies>
<!--activemq所需要的jar包配置-->
<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.xbean/xbean-spring -->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!--整合spring的时候需要用到的依赖-->
<!--下面是junit/log4j等基础通用配置-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
3.2 JMS 编码总体规范
3.3 Destination 简介
3.4 队列消息生产者的入门案例
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://192.168.76.100:61616";
public static final String QUEUE_NAME = "queue01";
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
//两个参数,第一个叫做事务/第二个叫做签收(boolean b,int i) AUTO_ACKNOWLEDGE 自动默认签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5. 创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.通过使用 MessageProducer 生产3条消息发送到mq队列里面
for (int i = 1; i <= 3 ; i++) {
//7.创建消息
TextMessage textMessage = session.createTextMessage("msg ---" + i); //理解为一个字符串
//8.通过 messageProducer 发送给mq
messageProducer.send(textMessage);
}
//9. 关闭资源 正着生产,倒着关闭
messageProducer.close();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
Queue queue 直接alt+enter是前面这个queue类型,但是我们可以改成destination类型(queue是destination的子接口,功能更加强大)
destination的子接口有queue topic 父接口一般是定义规范。子接口一般是实现更加强大的功能
和集合接口我们常用arraylist接口一样 collection collection = new arrayList;
Destination destination = session.createQueue(QUEUE_NAME);
3.5 ActiveMQ控制台之队列
3.6 队列消费者的入门案例(同步阻塞/异步监听)
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://192.168.76.100:61616";
public static final String QUEUE_NAME = "queue01";
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
//两个参数,第一个叫做事务/第二个叫做签收(boolean b,int i)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5. 创建消息的消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
/**
*
* 同步阻塞方式(recieve)
* 订阅者或接收者调用MessageConsumer的recieve()方法来接收消息,recieve方法在能够接收到消息之前(或超时之前)将一直阻塞。
*
*
while(true){
//因为生产者生产的是TextMessage这种类型的,所以消费者也要消费同种类型,所以要进行强制类型转换
//recieve()里面没有任何参数的话,消费者会一直等着接收消息,即使当前消息已经消费完了。
//recieve()设置时间,过时不候。
TextMessage textMessage = (TextMessage) messageConsumer.receive(4000L);
if(textMessage != null){
System.out.println("消费者接收到消息:"+textMessage.getText());
}else {
break;
}
}
//顺着申请,倒着关闭
messageConsumer.close();
session.close();
connection.close();
*/
/**
* 通过监听的方式
*/
messageConsumer.setMessageListener(new 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();
}
}
}
});
//防止执行太快,保证控制台不灭
//因为消费者进行连接到消息中间件,会有一系列验证,如果不写 System.in.read();这个,程序会马上执行完,但是消费者不会接收到任何消息
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
注意看上面的代码,其实我们不难发现消息的消费者有两种模式。
第一种是同步阻塞式
- 订阅者或接收者调用MessageConsumer的recieve()方法来接收消息,recieve方法在能够接收到消息之前(或超时之前)将一直阻塞
第二种是异步监听式
- 我们可以使用
MessageListener
来监听消息,通过注册一个监听器,当有消息发送来时,系统自动调用MessageListener的onMessage方法处理消息。
从上面两种方式的区别来看,我们大多数都会使用异步监听式来处理。
3.7 队列小结
3.7.1 队列特点
3.7.2 消费者情况
3.8 Topic 介绍
3.9 生产者 和消费者 案例
其实队列和主题这两种形式简单来说代码改动就在于session.createProducer(topic) / session.createTopic
,session.createProducer(queue) / session.createQueue
。
不信我们来看代码。
public class JmsProduce_Topic {
public static final String ACTIVEMQ_URL = "tcp://192.168.76.100:61616";
public static final String TOPIC_NAME = "topic-atguigu";
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
//两个参数,第一个叫做事务/第二个叫做签收(boolean b,int i)AUTO_ACKNOWLEDGE 自动默认签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
//5. 创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
//6.通过使用 MessageProducer 生产3条消息发送到mq队列里面
for (int i = 1; i <= 3; i++) {
//7.创建消息
TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i); //理解为一个字符串
//8.通过 messageProducer 发送给mq
messageProducer.send(textMessage);
}
//9. 关闭资源 正着生产,倒着关闭
messageProducer.close();
session.close();
connection.close();
System.out.println("TOPIC-NAME消息发送到MQ完成");
}
}
public class JmsConsumer_Topic {
public static final String ACTIVEMQ_URL = "tcp://192.168.76.100:61616";
public static final String TOPIC_NAME = "topic-atguigu";
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
//两个参数,第一个叫做事务/第二个叫做签收(boolean b,int i)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
//5. 创建消息的消费者
MessageConsumer messageConsumer = session.createConsumer(topic);
/**
* 通过监听的方式
*/
//传统方式 匿名内部类方式
/* messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者接收到topic消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});*/
//使用lambda表达式
messageConsumer.setMessageListener((message -> {
if(message != null && message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者接收到topic消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}));
//防止执行太快,保证控制台不灭
//因为消费者进行连接到消息中间件,会有一系列验证,如果不写 System.in.read();这个,程序会马上执行完,但是消费者不会接收到任何消息
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
3.10 topic和queue对比
4 JMS 规范
4.1 JMS 是什么
-
JAVAEE是什么?
-
什么是Java 消息服务?
Java 消息服务指的是两个应用程序之间进行异步通信的API,它为标准协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java 应用程序开发。在JavaEE 中,当两个应用程序使用JMS 进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果。
-
MQ几种落地产品对比:
4.2 消息头
JMS 的消息头有哪些属性:
- JMSDestination:消息目的地
- JMSDeliveryMode:消息持久化模式
- JMSExpiration:消息过期时间
- JMSPriority:消息的优先级
- JMSMessageID:消息的唯一标识符。后面我们会介绍如何解决幂等性。
说明: 消息的生产者可以set 这些属性,消息的消费者可以get 这些属性。这些属性在send 方法里面也可以设置。
// 这里可以指定每个消息的目的地
textMessage.setJMSDestination(topic);
/*
持久模式和非持久模式。
一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS 提供者出现故障,该消息并不会丢失,它
会在服务器恢复之后再次传递。
一条非持久的消息:最多会传递一次,这意味着服务器出现故障,该消息将会永远丢失。
*/
textMessage.setJMSDeliveryMode(0);
/*
可以设置消息在一定时间后过期,默认是永不过期。
消息过期时间,等于Destination 的send 方法中的timeToLive 值加上发送时刻的GMT 时间值。
如果timeToLive 值等于0,则JMSExpiration 被设为0,表示该消息永不过期。
如果发送后,在消息过期时间之后还没有被发送到目的地,则该消息被清除。
*/
textMessage.setJMSExpiration(1000);
/* 消息优先级,从0-9 十个级别,0-4 是普通消息5-9 是加急消息。
JMS 不要求MQ 严格按照这十个优先级发送消息但必须保证加急消息要先于普通消息到达。默认是4 级。
*/
textMessage.setJMSPriority(10);
// 唯一标识每个消息的标识。MQ 会给我们默认生成一个,我们也可以自己指定。
textMessage.setJMSMessageID("ABCD");
// 上面有些属性在send 方法里也能设置
messageProducer.send(textMessage);
4.3 消息体
在日常开发和生产中我们最主要使用的就是TextMessage和MapMessage这两种形式,几乎涵盖了百分之九十五的情况。
4.4 消息属性
如果需要除消息头字段之外的值,那么可以使用消息属性。他是识别/去重/重点标注等操作,非常有用的方法。他们是以属性名和属性值对的形式制定的。可以将属性是为消息头得扩展,属性指定一些消息头没有包括的附加信息,比如可以在属性里指定消息选择器。消息的属性就像可以分配给一条消息的附加消息头一样。
它们允许开发者添加有关消息的不透明附加信息。它们还用于暴露消息选择器在消息过滤时使用的数据。
下图是设置消息属性的API:
4.5 消息持久化
4.5.1 什么是持久化消息
4.5.2 queue持久化和非持久化
设置为非持久化:
- 启动生产者,查看消息:
- 将mq宕机:
- 重新启动
- 再启动消费者,发现获取不到消息。消息丢失
- 查看情况,消息不见,丢失。
持久化(默认情况下:)
重复和上面一样的操作
查看情况,消费者还是能消费到宕机前生产者生产的3条消息。
4.5.3 topic的持久化和非持久化
对于主题,我们都知道要先消费即订阅之后再生产消息,否则如果先生产消息,在订阅,原来生产的消息,会是废消息,就像微信公众号一样,你没有订阅某个公众号A,那么在你没订阅A之前,你是不会接收到这个A的生产的消息的。如果你订阅了A之后,那么A发布的消息就会被你接收到。所以我们得出结论,**对于主题而言,要先消费在生产。**所以对于主题而言,非持久化没有任何意义。所以我们主要考虑持久化。
持久化topic
改造生产者:先告诉我们生产的是一个持久化的topic。
改造消费者:
谁订阅了,订阅了哪个主题。
表明有一个z4的用户订阅了,订阅者订阅了这个主题。一定要先消费一次即表明先订阅这个主题,然后在执行生产者生产消息。无论消费者是否在不在线,因为你已经订阅了,所以你一定会收到这些消息。
控制台:
4.6 消息的事务性
/**
* 事务如果是true,要写commit
* 签收如果是手动签收,要写acknowledge
*/
public class JmsConsumer_ACK {
public static final String ACTIVEMQ_URL = "tcp://192.168.76.100:61616";
public static final String QUEUE_NAME = "queue01";
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);
//非事务状态下设置手动签收
// Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//事务状态下,手动签收
Session session = connection.createSession(true, Session.CLIENT_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5. 创建消息的消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者接收到消息:"+textMessage.getText());
//手动签收后要设置对消息的反馈
// textMessage.acknowledge();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
messageConsumer.close();
session.commit();
session.close();
connection.close();
}
}
public class JmsProduce_ACK {
public static final String ACTIVEMQ_URL = "tcp://192.168.76.100:61616";
public static final String QUEUE_NAME = "queue01";
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);
//事务状态下,签收与生产者关系不大,第二个签收参数都可以不写
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5. 创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.通过使用 MessageProducer 生产3条消息发送到mq队列里面
for (int i = 1; i <= 3 ; i++) {
//7.创建消息
TextMessage textMessage = session.createTextMessage(" tx msg ---" + i);
//8.通过 messageProducer 发送给mq
messageProducer.send(textMessage);
}
//9. 关闭资源 正着生产,倒着关闭
messageProducer.close();
//事务状态下
session.commit();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
4.7 消息的签收机制
4.7.1 签收的几种方式
4.7.2 事务和签收的关系
4.8 JMS 的点对点总结
4.9 JMS 的发布订阅总结
正常启动。
5 ActiveMQ 的broker
5.1 broker是什么?
相当于一个ActiveMQ 服务器实例。说白了,Broker 其实就是实现了用代码的形式启动ActiveMQ 将MQ 嵌入到Java 代码中,以便随时用随时启动,在用的时候再去启动这样能节省了资源,也保证了可用性。这种方式,我们实际开发中很少采用,因为他缺少太多了东西,如:日志,数据存储等等。
5.2 启动broker 时指定配置文件
启动broker 时指定配置文件,可以帮助我们在一台服务器上启动多个broker。实际工作中一般一台服务器只启动一个broker。
配置不同的配置文件和端口,启动不同的MQ实例。
5.3 嵌入式的broker 启动
用ActiveMQ Broker 作为独立的消息服务器来构建Java 应用。ActiveMQ 也支持在vm 中通信基于嵌入的broker,能够无缝的集成其他java 应用。
1.引入依赖
<!--Broker所需依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
2.编写代码
public class EmbedBroker {
public static void main(String[] args) throws Exception {
//ActiveMQ也支持在vm中通信基于嵌入式的broker
BrokerService brokerService = new BrokerService();
brokerService.setUseJmx(true);
//"tcp://192.168.119.100:61616" 这个是放在linux服务器上的,我们现在broker是嵌入式mq实例,是在本机上运行的
brokerService.addConnector("tcp://localhost:61616");
brokerService.start();
}
}
运行代码可以看出,在没有运行linux服务器上的mq实例的情况下,仍然能正常启动得到一个mq服务器。
6 Spring 整合ActiveMQ
6.1 依赖引入
基本依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>activemq</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--activemq所需要的jar包配置-->
<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.xbean/xbean-spring -->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!--spring整合mq-->
<!--activemq对JMS的支持,整合SPringle和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>
</dependencies>
6.2 编写applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启包的自动扫描-->
<context:component-scan base-package="com.atguigu"/>
<!--配置生产者-->
<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://192.168.76.100:61616"/>
</bean>
</property>
<!--最大连接数-->
<property name="maxConnections" value="100"></property>
</bean>
<!--这个是队列目的地,点对点的-->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!--队列的名字spring-active-queue-->
<constructor-arg index="0" value="spring-active-queue"/>
</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>
6.3 编写队列消费者和生产者代码
//说穿了是业务逻辑层,本来还应该写controller层的,此案例中为了简便
@Service
public class SpringMQ_Produce {
@Autowired
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(new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage("****spring和ActiveMQ的整合case......");
return textMessage;
}
});
*/
//lambda表达式 ---要求是接口,而且接口中只有一个方法
//口诀:拷贝,小括号,写死,右箭头,落地,大括号{},而且由于只有一个参数,可以将类型省略
produce.jmsTemplate.send((session) ->{
TextMessage textMessage = session.createTextMessage("****spring和ActiveMQ的整合case for queue......");
return textMessage;
} );
System.out.println("********send task over");
}
}
```java
@Service
public class SpringMQ_Consumer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SpringMQ_Consumer consumer = (SpringMQ_Consumer) ctx.getBean("springMQ_Consumer");
// Object --- SPring 因为生产者发送的是textMessage,是string
String retValue = (String) consumer.jmsTemplate.receiveAndConvert();
System.out.printf("*******消费者收到的消息:"+retValue);
}
}
6.4 编写主题消费者和生产者代码
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启包的自动扫描-->
<context:component-scan base-package="com.atguigu"/>
<!--配置生产者-->
<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://192.168.76.100:61616"/>
</bean>
</property>
<!--最大连接数-->
<property name="maxConnections" value="100"></property>
</bean>
<!--这个是队列目的地,点对点的-->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!--队列的名字spring-active-queue-->
<constructor-arg index="0" value="spring-active-queue"/>
</bean>
<!--这个是主题-->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-Topic"/>
</bean>
<!--Spring提供的JMS工具类,它可以进行消息发送、接收等-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory"/>
<!--主题-->
<property name="defaultDestination" ref="destinationTopic"/>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
</beans>
//说穿了是业务逻辑层,本来还应该写controller层的,此案例中为了简便
@Service
public class SpringMQ_Produce {
@Autowired
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(new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage("****spring和ActiveMQ的整合case......");
return textMessage;
}
});
*/
//lambda表达式 ---要求是接口,而且接口中只有一个方法
//口诀:拷贝,小括号,写死,右箭头,落地,大括号{},而且由于只有一个参数,可以将类型省略
produce.jmsTemplate.send((session) ->{
TextMessage textMessage = session.createTextMessage("****spring和ActiveMQ的整合case for topic......");
return textMessage;
} );
System.out.println("********send task over");
}
}
@Service
public class SpringMQ_Consumer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SpringMQ_Consumer consumer = (SpringMQ_Consumer) ctx.getBean("springMQ_Consumer");
// Object --- SPring 因为生产者发送的是textMessage,是string
String retValue = (String) consumer.jmsTemplate.receiveAndConvert();
System.out.printf("*******消费者收到的消息:"+retValue);
}
}
6.5 配置消费者的监听类
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启包的自动扫描-->
<context:component-scan base-package="com.atguigu"/>
<!--配置生产者-->
<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://192.168.76.100:61616"/>
</bean>
</property>
<!--最大连接数-->
<property name="maxConnections" value="100"></property>
</bean>
<!--这个是队列目的地,点对点的-->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!--队列的名字spring-active-queue-->
<constructor-arg index="0" value="spring-active-queue"/>
</bean>
<!--这个是主题-->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-Topic"/>
</bean>
<!--Spring提供的JMS工具类,它可以进行消息发送、接收等-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory"/>
<!--队列-->
<!-- <property name="defaultDestination" ref="destinationQueue"/>-->
<!--主题-->
<property name="defaultDestination" ref="destinationTopic"/>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
<!--配置监听程序-->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="destination" ref="destinationTopic"/>
<!--public class MyMessageListener implements MessageListener-->
<property name="messageListener" ref="myMessageListener"/>
</bean>
<!--以配置方式-->
<!-- <bean id="myMessageListener" class="com.atguigu.spring.MyMessageListener"/>-->
</beans>
监听类
@Component
public class MyMessageListener implements 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();
}
}
}
}
7 springboot整合AcitveMQ
7.1 队列
7.1.1 生产者
[1] 创建maven工程并完成包名准备
[2] 引入依赖 — pom.xml
最主要的是要引入spring-boot-starter-activemq
这个依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
[3] 创建application.yml
注意这个application.yml是我们自己创建的哈。
微服务端口+MQ服务器地址+主题/队列+主题/队列名称
#微服务端口
server:
port: 7777
spring:
activemq:
broker-url: tcp://192.168.119.100:61616 #自己的MQ服务器地址
user: admin
password: admin
jms:
cache:
consumers: false # 如果是false,表示是队列,如果是true,表示是主题
#自己定义队列名称(k:v键值对)
#yml配置文件遇到“:”或者“-”后面必须留一个空格!否则报错
myqueue: boot-activemq-queue
[4] 配置Bean — ConfigBean
类似Spring框架的application.xml文件,依赖注入。从application.yml中取值读出,进行依赖注入。
注意要开启基于Jms的注解,即@EnableJms。
//类似spring框架的applicationContext.xml文件
@Component
@EnableJms //开启Jms注解 重要
public class ConfigBean {
@Value("${myqueue}")
private String myQueue;
@Bean //<bean id= class= >
public Queue queue(){
return new ActiveMQQueue(myQueue);
}
}
[5] 创建Queue_Produce 生产者(两种投递方式)
@Component
public class Queue_Produce {
@Resource
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue queue;
//触发投递----工作中常用
public void produceMsg(){
jmsMessagingTemplate.convertAndSend(queue,"****:"+UUID.randomUUID().toString().substring(0,6));
}
//间隔定投----工作中常用
//间隔时间3秒钟定投
@Scheduled(fixedDelay = 3000)
public void produceMsgScheduled(){
jmsMessagingTemplate.convertAndSend(queue,"****Scheduled:"+UUID.randomUUID().toString().substring(0,6));
System.out.println("produceMsgScheduled send ok");
}
}
JmsMessagingTemplate
这个是我们在springboot项中要用的,后面代码都是通过这个模板来进行编写。
从上面的代码我们知道,在工作中常常有两种方式来进行消息的生产,第一种是触发投递,即要运行生产者才会进行消息的生产;第二种是间隔一定时间进行消息的产生,即间隔定投。注意,间隔投递要加入@Scheduled(fixedDelay = 3000)
,比如说这样就是每隔3秒进行一次消息的产生。
[6] 主启动类MainApp_Produce
//主启动类
@SpringBootApplication
//定时投递的注解使用了要使用这个注解才行 -----即激活定时投递那个注解
@EnableScheduling
class MainApp_Produce {
public static void main(String[] args) {
SpringApplication.run(MainApp_Produce.class,args);
}
}
注意主启动类中要加入@EnableScheduling
这个注解才会开启间隔投递。
[7] 进行测试
@SpringBootTest(classes = MainApp_Produce.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestActiveMQ {
@Autowired
private Queue_Produce queue_produce;
@Test
public void testSend() throws Exception{
queue_produce.produceMsg();
}
}
这个地方可进行可不进行。
7.1.2 消费者
[1] 创建相应工程并准备好工作
[2] 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
和上面生产者依赖一模一样。
[3] 创建application.yml
和上面生产者一模一样。
#微服务端口
server:
port: 8888
spring:
activemq:
broker-url: tcp://192.168.119.100:61616 #自己的MQ服务器地址
user: admin
password: admin
jms:
cache:
consumers: false # 如果是false,表示是队列,如果是true,表示是主题
#自己定义队列名称(k:v键值对)
#yml配置文件遇到“:”或者“-”后面必须留一个空格!否则报错
myqueue: boot-activemq-queue
这里注意端口号不要和生产者一样,因为springboot每一个单独项目都是一个个微服务。
[4] 创建Queue_Consumer 消费者
@Component
public class Queue_Consumer {
@JmsListener(destination = "${myqueue}") //以前spring中还要专门写一个监听类,springboot直接注解即可实现
public void receive(TextMessage textMessage) throws JMSException{
System.out.println("消费者收到消息:"+textMessage.getText());
}
}
和上面生产者依赖一模一样。
[5] 主启动类MainAPP_Consumer
@SpringBootApplication
public class MainApp_Consumer {
public static void main(String[] args) {
SpringApplication.run(MainApp_Consumer.class,args);
}
}
7.2 主题
7.2.1 生产者
[1] 创建工程并准备好工作
[2] 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
和上面队列是一模一样的。
[3] 创建application.yml
#微服务端口
server:
port: 6666
spring:
activemq:
broker-url: tcp://192.168.119.100:61616 #自己的MQ服务器地址
user: admin
password: admin
jms:
cache:
consumers: true # 如果是false,表示是队列,如果是true,表示是主题
#自己定义队列名称(k:v键值对)
#yml配置文件遇到“:”或者“-”后面必须留一个空格!否则报错
mytopic: boot-activemq-topic
[4] 配置Bean — ConfigBean
@Component
public class ConfigBean {
@Value("${mytopic}")
private String topicName;
@Bean
public Topic topic(){
return new ActiveMQTopic(topicName);
}
}
[5] 创建 Topic_Produce 生产者
@Component
public class Topic_Produce {
@Resource
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Topic topic;
@Scheduled(fixedDelay = 3000)
public void produceTopic(){
jmsMessagingTemplate.convertAndSend(topic,"主题消息:"+ UUID.randomUUID().toString().substring(0,6));
}
}
[6] 主启动类MainApp_TopicProduce
@SpringBootApplication
@EnableScheduling
public class MainApp_TopicProduce {
public static void main(String[] args) {
SpringApplication.run(MainApp_TopicProduce.class,args);
}
}
7.2.2 消费者
[1] 创建工程
[2] 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
[3] 创建application.yml
server:
port: 5555 #启动第二个消费者时候记得修改为5566端口号 模拟2个消费者
spring:
activemq:
broker-url: tcp://192.168.119.100:61616
user: admin
password: admin
jms:
pub-sub-domain: true
#将要消费的主题名称
mytopic: boot-activemq-topic
注意这个地方改为要订阅,改为true。
[4] 创建Topic_Consumer
@Component
public class Topic_Consumer {
@JmsListener(destination = "${mytopic}")
public void receive(TextMessage text) throws JMSException{
try {
System.out.println("消费者收到订阅的主题:"+text.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
[5] 主启动类MainApp_TopicConsumer
@SpringBootApplication
public class MainApp5555 {
public static void main(String[] args) {
SpringApplication.run(MainApp5555.class,args);
}
}
注意:目前这个地方有问题。消费者和生产者的打印不除了。
8 ActiveMQ 的传输协议
8.1 简介
ActiveMQ 支持的client-broker 通讯协议有:TVP、NIO、UDP、SSL、Http(s)、VM。其中配置TransportConnector 的文件在ActiveMQ 安装目录的conf/activemq.xml 中的标签之内。
activemq 传输协议的官方文档:http://activemq.apache.org/configuring-version-5-transports.html。
8.2 支持的传输协议
个人说明:除了tcp 和nio 协议,其他的了解就行。各种协议有各自擅长该协议的中间件,工作中一般不会使用activemq 去实现这些协议。如: mqtt 是物联网专用协议,采用的中间件一般是mosquito。ws是websocket 的协议,是和前端对接常用的,一般在java 代码中内嵌一个基站(中间件)。stomp 好像是邮箱使用的协议的,各大邮箱公司都有基站(中间件)。
注意:协议不同,我们的代码都会不同。
8.2.1 TCP 协议
8.2.2 NIO 协议
8.2.3 AMQP 协议
8.2.4 STOMP 协议
8.2.5 MQTT 协议
8.2.6 NIO 协议案例
[1] 修改配置文件 activemq.xml
我们
代码如下:<transportConnector name="nio" uri="nio://0.0.0.0:61618?trace=true"/>
操作截图:
MQ控制台截图:
[2] 代码
只需要如上即可。
8.2.7 NIO 协议案例增强
[1] 修改配置activemq.xml文件
如下图,只要我们在修改nio协议的位置,将上面新加的注释掉,加入以下代码即可。
修改之后我们可以在代码出,前缀改为tcp或者nio都可以正常运行程序。如下图,控制台也显示。
9 ActiveMQ 的消息存储和持久化
9.1 介绍
9.2 是什么
9.3 有哪些
9.4 kahaDB 消息存储
9.4.1 介绍
9.4.2 说明
9.4.3 存储原理
9.5 JDBC消息存储
9.5.1 设置步骤
- 原理图
- 添加mysql 数据库的驱动包到lib 文件夹
将mysql连接的jar包拷贝到mq的lib文件夹下面。
dbcp2 ----官网默认自带的数据库连接池,所以我们导入的时候就只是导入了一个jar包。
注意如果使用德鲁伊或者c3p0这些数据库连接池,导入的时候不止上面单独这一个。切记。和以前导入依赖所必须的一样。
操作截图:
- 修改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://192.168.119.1:3306/activemq?relaxAutoCommit=true"/>
<property name="username" value="root"/>
<property name="password" value="abc123"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
<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://192.168.76.1:3306/activemq?relaxAutoCommit=true"/>
<property name="username" value="root"/>
<property name="password" value="abc123"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
- 建库SQL 和创表说明
当我们设置好上面的步骤之后,先在windows本地计算机上创建一个activemq的数据库。当我们将mq进行重启之后,会自动生成三张表。
ACTIVEMQ_ACKS 数据表:
注意,如果没有成功创建,我们可以进行手动创建。
-- 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);
- 启动遇到的问题,可参考如下解决:
$ /usr/local/mysql/bin/mysql -u root -p
--输入密码
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| seckill |
| sys |
| test |
+--------------------+
6 rows in set (0.00 sec)
mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
--查询当前数据库相关信息
mysql> select host,user,authentication_string,plugin from user;
+-----------+------------------+------------------------------------------------------------------------+-----------------------+
| host | user | authentication_string | plugin |
+-----------+------------------+------------------------------------------------------------------------+-----------------------+
| localhost | mysql.infoschema | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED | caching_sha2_password |
| localhost | mysql.session | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED | caching_sha2_password |
| localhost | mysql.sys | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED | caching_sha2_password |
| localhost | root | *84AAC12F54AB666ECFC2A83C676908C8BBC381B1 | mysql_native_password |
+-----------+------------------+------------------------------------------------------------------------+-----------------------+
4 rows in set (0.00 sec)
--将root用户设置为所有地址可登录,原来是localhost表示只用本机可登录
mysql> update user set host='%' where user='root';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--刷新权限
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
--将用户root密码设置为永不过期
mysql> alter user 'root'@'%' identified by '12345678' password expire never;
Query OK, 0 rows affected (0.01 sec)
--将root用户密码加密方式改为mysql_native_password ,上面查到root用户密码的加密方式为caching_sha2_password
mysql> alter user 'root'@'%' identified with mysql_native_password by '12345678';
Query OK, 0 rows affected (0.00 sec)
--刷新权限,在别的机器上即可登录
mysql> flush privileges;
设置好后即可登录。
9.5.2 queue 验证和数据表变化
注意:
代码很简单,就是在原来代码中进行运行即可。我们主要是关注和数据库的变化。
9.5.3 topic 验证和说明
我们先启动一下持久化topic 的消费者。看到ACTIVEMQ_ACKS 数据表多了一条消息。
ACTIVEMQ_ACKS 数据表,多了一个消费者的身份信息。一条记录代表:一个持久化topic 消费者。
9.5.4 小结
9.6 JDBC Message Store with ActiveMQ Journal
9.6.1 说明
这种方式克服了JDBC Store 的不足,JDBC 每次消息过来,都需要去写库读库。ActiveMQ Journal,使用高速缓存写入技术,大大提高了性能。当消费者的速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要写入到DB 中的消息。
举个例子:生产者生产了1000 条消息,这1000 条消息会保存到journal 文件,如果消费者的消费速度很快的情况下,在journal 文件还没有同步到DB 之前,消费者已经消费了90%的以上消息,那么这个时候只需要同步剩余的10%的消息到DB。如果消费者的速度很慢,这个时候journal 文件可以使消息以批量方式写到DB。
为了高性能,这种方式使用日志文件存储+数据库存储。先将消息持久到日志文件,等待一段时间再将
未消费的消息持久到数据库。该方式要比JDBC 性能要高。
9.6.2 配置
还是在activemq.xml配置文件中进行修改。
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="4"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="activemq-data"/>
</persistenceFactory>
9.7 总结
10 ActiveMQ 多节点集群
10.1 面试题
引入消息中间件后如何保证其高可用?
10.2 是什么?
基于zookeeper和LevelDB搭建ActiveMQ集群。集群仅提供主备方式的高可用集群功能,避免单点故障。
10.3 zookeeper+replicated-leveldb-store的主从集群
10.3.1 ShareFileSystem
10.3.2 是什么?
从ActiveMQ5.9开始,ActiveMQ的集群实现方式取消了传统的Masster-Slave方式。增加了基于Zookeeper+LevelDB的Master-Slave实现方式,从5.9版本后也是官网的推荐。
基于Zookeeper和LevelDB搭建ActiveMQ集群,集群仅提供主备方式的高可用集群功能,避免单点故障。
网址:http://activemq.apache.org/replicated-leveldb-store
10.3.3 三种集群方式对比
10.3.4 官网集群原理图
地址:https://activemq.apache.org/replicated-leveldb-store。
10.3.5 部署规划和步骤
注意我是使用的三台虚拟主机进行配置集群的,即相同操作要进行三次,在三台虚拟主机上。
- 大纲
- 部署规划表
- 就是将当初opt目录解压的activemq文件夹复制到我们新创建的目录下并改名,到时候我们直接在这个地方进行启动。
- 修改管理控制台端口
就是conf目录下的jetty.xml中修改网址登陆端口,即后台管理端口。
- hostname名字映射(如果不映射只需要吧mq配置文件的hostname改成当前主机ip)
就是本来我们输入要写网址,但是映射之后就不用写网址了。
- ActiveMQ集群配置
在activemq.xml中讲上面配置的名字修改到borkerName。
持久化配置:
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:63631"
zkAddress="192.168.119.100:2191,192.168.119.110:2192,192.168.119.120:2193"
hostname="zzyymq-server"
sync="local_disk"
zkPath="/activemq/leveldb-stores"/>
</persistenceAdapter>
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:63632"
zkAddress="192.168.119.100:2191,192.168.119.110:2192,192.168.119.120:2193"
hostname="zzyymq-server"
sync="local_disk"
zkPath="/activemq/leveldb-stores"/>
</persistenceAdapter>
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:63633"
zkAddress="192.168.119.100:2191,192.168.119.110:2192,192.168.119.120:2193"
hostname="zzyymq-server"
sync="local_disk"
zkPath="/activemq/leveldb-stores"/>
</persistenceAdapter>
- 修改个节点的消息端口
如下图,就是连接端口改为61616,61617,61618。
- 先启动zk在启动mq
在启动mq的时候有时候会遇到以下问题。
有时候出现未找到命令,使用./开头
- zk集群节点状态说明
在zk集群下的节点即可看见有activemq。通过获取值来判断谁是主,谁是从。
10.3.6 集群可用性测试
代码修改很简单:
public static final String ACTIVEMQ_URL = "failover(tcp://192.168.119.100:61616,tcp://192.168.119.110:61617,tcp://192.168.119.120:61618)?randomize=false";
public static final String QUEUE_NAME = "queue-cluster";
消费者和生产者都进行修改,然后启动进行测试
宕机一台:
我们发现原来的访问不了,
自动切换到8162这个端口,即另一台zk服务器上了。
然后进行测试,一切正常。
11 高级特性及大厂面试题
11.1 异步投递
11.1.1 是什么
11.1.2 代码实现
官网配置:
public class JmsProduce_Async {
public static final String ACTIVEMQ_URL = "tcp://192.168.119.100:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
//1.创建连接工程,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//设置开启异步投递
activeMQConnectionFactory.setUseAsyncSend(true);
//2.通过连接工场,获得连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话session
//两个参数,第一个叫做事务/第二个叫做签收(boolean b,int i) AUTO_ACKNOWLEDGE 自动默认签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5. 创建消息的生产者----使用更加细粒度化的消息生产者
ActiveMQMessageProducer activeMQMessageProducer = (ActiveMQMessageProducer) session.createProducer(queue);
TextMessage message = null;
//6.通过使用 MessageProducer 生产3条消息发送到mq队列里面
for (int i = 1; i <= 3 ; i++) {
//7.创建消息
message = session.createTextMessage("msg ---" + i);
//给每条消息设置一个id,在回调函数中通过有无id来判断否发送成功
message.setJMSMessageID(UUID.randomUUID().toString()+"----测试");
String msgId = message.getJMSMessageID();
activeMQMessageProducer.send(message, new AsyncCallback() {
@Override
public void onSuccess() {
System.out.println(msgId + "has been ok send");
}
@Override
public void onException(JMSException e) {
System.out.println(msgId + "fail to send to mq");
}
});
}
//9. 关闭资源 正着生产,倒着关闭
activeMQMessageProducer.close();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
11.2 延迟投递和定时投递
11.2.1 介绍
11.2.2 修改配置文件并重启
11.2.3 代码实现
public class JmsConsumer_DelayAndSchedule {
public static final String ACTIVEMQ_URL = "tcp://192.168.119.100:61616";
public static final String QUEUE_NAME = "queue01";
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
//两个参数,第一个叫做事务/第二个叫做签收(boolean b,int i)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5. 创建消息的消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
while(true){
TextMessage textMessage = (TextMessage) messageConsumer.receive(4000L);
if(textMessage != null){
System.out.println("消费者接收到消息:"+textMessage.getText());
}else {
break;
}
}
//顺着申请,倒着关闭
messageConsumer.close();
session.close();
connection.close();
}
}
public class JmsProduce_DelayAndSchedule {
public static final String ACTIVEMQ_URL = "tcp://192.168.119.100:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageProducer messageProducer = session.createProducer(queue);
long delay = 3 *1000; //延迟3秒钟
long period = 4 * 1000; // 时间间隔4秒钟
int repeat = 5 ; //重复次数 5次
for (int i = 1; i <= 3 ; i++) {
TextMessage textMessage = session.createTextMessage("jdbcmsg ---" + i);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,period);
textMessage.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT,repeat);
messageProducer.send(textMessage);
}
messageProducer.close();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
测试
11.3 消息消费的重试机制
11.3.1 介绍
11.3.2 代码实现
以入门案例中的队列中的消费者和生产者,将消费者中的创建会话改为true,加入事务,但是我们人为干预不提交。进行1+6次,默认重试6次,进行消息重复消费,当到第8次的时候发现无法进行消息消费,即证明消息重试次数为6次。消息最后进入死信队列。
如下图
我们还可以进行自定义重试次数。代码只需在消费者中加入几行代码。
以后整合spring的时候,我们只需在application.xml中进行bean 创建和引用即可,相关配置见下图:
11.4 死信队列
11.5 消息不被重复消费,幂等性
后记
相信经过上面一系列的学习,对activemq有一定的理解和运用了。感谢尚硅谷周阳老师,感谢尚硅谷免费分享的课程资源。就像周阳老师在最后结尾所说,相信自己一定能入行。