ActiveMQ学习笔记
ActiveMQ与RocketMQ之间的区别
该文章读完你会了解
- api发送与接收
- MQ高可用
- MQ的集群和容错配置
- MQ的持久化
- 延时发送和定时投递
- 签收机制
- Spring整合
主要作用就是解耦 异步 削峰
大致流程是:
发送者把消息发送给消息服务器,消息服务器把消息存放在队列/主题中,在合适的时候 消息服务器会将消息转发给接受者 在这个过程中发送和接受是异步的 也就是发送无需等待 而且发送者和接受者的生命周期没有必然的关系 尤其是在发布p/订阅sub的模式下 也可以实现一对多的通信 即让一个消息有多个接受者
能够实现的功能
- 主要的功能就是 实现高可用 高性能 可伸缩 易用 和安全的企业
- 异步消息的消费和处理
- 控制消息的消费顺序
- 可以和Spring/SpringBoot整合 简化代码
- 配置集群容错的MQ集群
知识点: ps -ef | grep activemq 或者使用 netstat -anp | grep 端口 或者 lsof -i:61616
安装activemq
- 下载apache-activemq-5.15.13-bin.tar
- 解压 拷贝到单独的文件下面
- 进入到bin目录下面 ./activemq start进行执行 ./activemq stop关闭 默认端口61616
测试Activemq的demo
1、添加配置
<!--activemq所需要的jar包-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.13</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!--下面是junit/long4j的基础配置-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<!--Brock需要的jar包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.0</version>
</dependency>
<!--ActiceMQ对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.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.1.6.RELEASE</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.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
在点对点的消息传递过程中 目的地被称为队列(queue)
在发布订阅消息传递中 目的地被称为主题(topic)
队列模式
生产者:
- 定义activeMq的地址和队列的名字
- 创建连接工厂 开启异步发送 通过连接工厂获取连接
- 创建会话 第一个参数事务 我二个参数签收
- 创建消息的生产者 发送消息
- 创建消息
- 设置消息的请求头
- 发送消息
- 关闭生产者 会话 连接
/**
* 创建生产者
*/
public class JmsQueue {
public static final String ACTIVE_MQ="tcp://192.168.120.32:61616";
public static final String QUEUE_NAME="queue01";
public static void main(String[] args) throws JMSException {
//创建连接工厂,按照给定的url地址 采用默认的用户名和密码
ActiveMQConnectionFactory mq=new ActiveMQConnectionFactory(ACTIVE_MQ);
//开始异步发送
mq.setUseAsyncSend(true);
//通过连接工厂 获取连接connection
Connection connection = mq.createConnection();
connection.start();
//创建会话session
//第一个:事务 第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地
/**
* 生产者将消息发布到Queue上面 每个消息有一个消费者 属于1:1的过程
*/
Queue queue = session.createQueue(QUEUE_NAME);
//创建消息的生产者
//MessageProducer producer = session.createProducer(queue);
ActiveMQMessageProducer producer = (ActiveMQMessageProducer)session.createProducer(queue);
//producer.setDeliveryMode(DeliveryMode.PERSISTENT);
//通过消息生产者生产3条消息 发送到mq的队列里面
for (int i=1;i<=6;i++){
//创建消息
TextMessage message = session.createTextMessage("message" + i);
//设置一个请求头
message.setJMSMessageID(UUID.randomUUID().toString()+"--->");
//通过producer发送给mq
//producer.send(message);
final String msgID=message.getJMSMessageID();
producer.send(message, new AsyncCallback() {
//就可以知道那些消息失败那些消息成功
public void onSuccess() {
System.out.println(msgID+"成功");
}
public void onException(JMSException e) {
System.out.println(msgID+"失败");
}
});
}
producer.close();//关闭资源
session.close();
connection.close();
System.out.println("消息发送到MQ完成");
//执行上面的步骤 只是生产者将信息放入到队列里面
//消费者并没有进行消费
}
}
对于消费者来说
-
前三步与生产者一样
-
创建消费者
-
同步阻塞的方式接受信息
同步阻塞方式:receive() 订阅者或者接受者的调用MessageConsumer的receive方法来接受数据,receive方法能够接受到数据之前(或者一直处于阻塞的状态) while (true){ //Message receive = consumer.receive();消费者一直处于等待状态 TextMessage receiveMessage = (TextMessage)consumer.receive(3000);//一百ms之后不消费就走了 if (receiveMessage!=null){ System.out.println("****消费者接收到消息****"+receiveMessage.getText()); }else break; } consumer.close(); session.close(); connection.close(); System.out.println("接受完成");
通过监听
/** * 通过监听的方式 * 异步非阻塞的方式(监听onMessage()) * 订阅者或者接受者通过MessageConsumer的setMessageListener(MessageListener listener)注册一个消息注册器 * 当消息到达之后 系统自动调用监听器MessageListener的onMessage(Message message)方法 */ consumer.setMessageListener(new MessageListener() { public void onMessage(Message message) { if (message!=null && message instanceof TextMessage){ TextMessage textMessage=(TextMessage) message; try { System.out.println("textMessage"+textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } } }); System.in.read();//保证控制台不灭 防止还没有消费就关了 consumer.close(); session.close(); connection.close();
同步阻塞(receive())和异步非阻塞方式地对比
同步阻塞:
订阅者或接受者调用MessageConsumer的receive方法来接受消息 receive方法在能够接受到消息之前将一直处于阻塞的状态
异步非阻塞方式
订阅者或者接受者通过MessageConsumer的setMessageListener(MessageListener listener)注册一个消息监听器,当消息到达之后 系统自动调用监听器MessageListener的onMessage方法
点对点消息传递的特点:
(1)每个消息只能有一个消费者,类似1对1的关系。好比个人快递自己领取自己的。
(2)消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,消费者都可以提取消息。好比我们的发送短信,发送者发送后不见得接收者会即收即看。
(3)消息被消费后队列中不会再存储,所以消费者不会消费到已经被消费掉的消息。
发布订阅模式
- 生产者将消息发布到topic中 每个消息可以有多个消费者 属于1:n的关系(一条消息可以让多个消费者同时接受到)
- 生产者和消费者之间有时间上的相关性 消费者只能消费在他订阅之后的消息
- 生产者生产的时候 topic不保存消息 他是无状态地不落地的 假如无人订阅就去生产 那么就是一条废消息 所以一般先启动消费者在启动生产者
- 类似微信公众号
/**
* 发布者
*/
public class JmsTopic {
public static final String ACTIVE_MQ="tcp://192.168.120.132:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory mq = new ActiveMQConnectionFactory(ACTIVE_MQ);
Connection connection = mq.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageProducer producer = session.createProducer(topic);
//非持久化
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
for (int i=1;i<=3;i++){
TextMessage textMessage = session.createTextMessage("Topic" + i);
textMessage.setStringProperty("c1","v1");
//textMessage.setJMSDeliveryMode();
producer.send(textMessage);
}
producer.close();
session.close();
connection.close();
System.out.println("发送完成");
}
/**
* 生产者将消息发送到topic上面 每一个消息可以有多个消费者 属于1:N的过程
* 某一个主题的消费者只能够订阅在他之后发布的消息
* 所以一般来说是先启动消费者在启动生产者
*/
}
/**
* 订阅者
*/
public class JmsConsumer1 {
public static final String ACTIVE_MQ="tcp://192.168.120.132:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory mq=new ActiveMQConnectionFactory(ACTIVE_MQ);
//通过连接工厂 获取连接connection
final Connection connection = mq.createConnection();
connection.start();
//创建会话session
//第一个:事务 第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地
/**
* 生产者将消息发布到Queue上面 每个消息有一个消费者 属于1:1的过程
*/
Topic topic = session.createTopic(TOPIC_NAME);
//创建消费者
final MessageConsumer consumer = session.createConsumer(topic);
/**
* 通过监听的方式
* 异步非阻塞的方式(监听onMessage())
* 订阅者或者接受者通过MessageConsumer的setMessageListener(MessageListener listener)注册一个消息注册器
* 当消息到达之后 系统自动调用监听器MessageListener的onMessage(Message message)方法 这样就不用先启动消费者在启动监听者了
*/
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
if (message!=null && message instanceof TextMessage){
TextMessage textMessage=(TextMessage) message;
try {
System.out.println("消息属性--->"+textMessage.getStringProperty("c1"));
//System.out.println("接收Topic的信息"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();//保证控制台不灭 防止还没有消费就关了
consumer.close();
session.close();
connection.close();
/**
* 每一个消费者都有3条消息 先启动消费者在启动生产者
*/
}
}
两种模式详细对比
Queue
- 如果当前没有消费者 消息不会丢弃 如果有多个消费者 那么一条消息也只能发送给一个消费者并且要求消费者ack消息
- 因为消费者再多 消息也只能发送给一个 所以性能不会有明显的降低
- 队列的数据会在mq服务器上以文件的形式进行保存
Topic
- 如果没有订阅者 消息会被丢弃 如果有多个订阅者 那么这些订阅者都会收到消息
- 数据不会存储
- 由于消息要按照订阅者的数量进行复制,所以处理性能会随着订阅者的增加而明显降低,并且还要结合不同消息协议自身的性能差异
JMS
JavaMessageService(Java消息服务是JavaEE中的一个技术)
Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果
JMS的组成结构和特点
- JMS Provider 实现JMS接口和规范的消息中间件,也就是我们说的MQ服务器
- JMS Producter 消息生产者,创建和发送JMS消息的客户端应用
- JMSConsumer 消息消费者,接收和处理JMS消息的客户端应用
- JMS Message
- 消息头
- JMSDestination消息发送的目的地,主要是指Queue和Topic
- JMSDeliveryMode 设置这个消息是持久的还是非持久的一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递。一条非持久的消息:最多会传递一次,这意味着服务器出现故障,该消息将会永远丢失。
- JMSExpiration 设置过期时间 默认是永不过期的 等于Destination的send方法中的timeToLive值加上发送时刻的GMT时间值。如果timeToLive值等于0,则JMSExpiration被设为0,表示该消息永不过期。如果发送后,在消息过期时间之后还没有被发送到目的地,则该消息被清除。
- JMSMessageID唯一标识每个消息的标识由MQ产生
- JMSPriority 设置优先级消息优先级,从0-9十个级别,O-4是普通消息5-9是加急消息。JMS不要求MQ严格按照这十个优先级发送消息但必须保证加急消息要先于普通消息到达。默认是4级。
消息属性
主要的两种
- TxtMessage 普通字符串消息 包含一个String
- MapMessage 一个Map类型的消息 Key为String类型而值为Java基本类型
- 要求发送和接受的消息体类型必须是一致的
消息体
识别 / 去重 /重点标注等操作非常有用的方法
例如message.setStringProperty/setString等设置消息体 在接收方getStringProperty来接受消息体中的信息
消息的可靠性(重点)
持久性
- 持久化messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT)当服务器宕机消息依然存在
- 非持久化 messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT) 当服务器宕机 消息不存在
- 队列模式默认是持久化的
这里主要讲解主题模式 队列模式不在描述
消费者
public static final String ACTIVE_MQ="tcp://192.168.120.32:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("消费者1");
ActiveMQConnectionFactory mq=new ActiveMQConnectionFactory(ACTIVE_MQ);
//通过连接工厂 获取连接connection
Connection connection = mq.createConnection();
connection.setClientID("li");//设置Id
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);//设置目的地
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark...");//开启持久化
connection.start();
Message message = topicSubscriber.receive();
while (message!=null){
TextMessage textMessage=(TextMessage)message;
System.out.println("***收到持久化的topic"+textMessage.getText());
message = topicSubscriber.receive(1000);
}
System.in.read();//保证控制台不灭 防止还没有消费就关了
session.close();
connection.close();
}
/**
* 订阅模式
* 生产者
*/
public class JmsTopic_Persit {
public static final String ACTIVE_MQ="tcp://192.168.120.32:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory mq = new ActiveMQConnectionFactory(ACTIVE_MQ);
Connection connection = mq.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("Topic" + i);
//textMessage.setStringProperty("c1","v1");
//textMessage.setJMSDeliveryMode();
producer.send(textMessage);
}
producer.close();
//session.commit();如果是true的话
session.close();
connection.close();
System.out.println("发送完成");
}
}
一般对于事务而言基本在生产者使用 对于签收在消费者使用
事务 :
什么是事务?
事务的四大特性?
事务的隔离级别?
如果session在创建的时候开启了事务 那么在关闭session之前要执行session.commit()进行提交 这样消息才会被真正的提交到队列里面
一旦执行过程中出现了错误那么在finally里面执行sesssion.rollback()
对于生产者来说:Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);需要commit
对于消费者来说:Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);需要commit
签收 :
- 自动签收Session.AUTOACKNOWLEDGE
- 手动签收 Session.CLIENT ACKNOWLEDGE客户端需要调用acknowledge方法手动签收 消费者收到消息之后执行message.acknowledge()进行确认消费
- 允许重复签收
总结:在事务性会话中当一个事务被成功提交则消息被自动签收如果事务回滚则消息会被再次传送
非事务性会话中 消息何时被确认取决于创建会话时的应答模式
Spring整合ActiveMQ
配置jar包
<!--ActiceMQ对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.2.3.RELEASE</version>
</dependency>
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:contest="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 http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启包的自动扫描 -->
<contest:component-scan base-package="com.ljx"/>
<!-- 配置生产者 -->
<bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<!-- 正真可以生产Connection的ConnectionFactory,由对应的JMS服务商提供 -->
<bean class="org.apache.activemq.spring.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.120.132:61616"/>
</bean>
</property>
<property name="maxConnections" value="100"/>
</bean>
<!-- 这个是队列目的地,点对点的Queue -->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!-- 通过构造注入Queue名 -->
<constructor-arg index="0" value="spring-active-queue"/>
</bean>
<!-- 这个是发布订阅目的地, 发布订阅的主题Topic-->
<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="connectionFactory"/>
<!-- 传入目的地 -->
<property name="defaultDestination" ref="destinationTopic"/>
<!-- 引用的是队列或者主题 -->
<!-- 消息自动转换器 -->
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
</beans>
编写队列的消费者和生产者
//业务逻辑层
@Service
public class SpringMQ_producer {
@Autowired
private JmsTemplate jmsTemplate;
//在配置文件配置的能够实现消息的发送和订阅
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
SpringMQ_producer springMQ_producer = applicationContext.getBean(SpringMQ_producer.class);
springMQ_producer.jmsTemplate.send(session -> session.createTextMessage("Spring整合ActiveMQ的案例"));
System.out.println("发送成功");
}
}
@Service
public class SpringMQ_Consumer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext applicationContext=
new ClassPathXmlApplicationContext("applicationContext.xml");
SpringMQ_Consumer springMQ_consumer = applicationContext.getBean(SpringMQ_Consumer.class);
String returnValue = (String) springMQ_consumer.jmsTemplate.receiveAndConvert();
System.out.println("接受者接受的消息是:"+returnValue);
}
}
对于订阅发布模式 只需要在配置文件的JMT里面设置对应的方式就可以了
通过监听来接受消息 不启动消费者便能接受到消息
<!-- 配置Jms消息监听器 -->
<bean id="defaultMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!-- Jms连接的工厂 -->
<property name="connectionFactory" ref="connectionFactory"/>
<!-- 设置默认的监听目的地 -->
<property name="destination" ref="destinationTopic"/>
<!-- 指定自己实现了MessageListener的类 -->
<property name="messageListener" ref="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 (Exception e) {
e.printStackTrace();
}
}
}
}
SpringBoot整合activemq
引入配置文件
<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>
</dependency>
配置application.properties
server.port=7777
spring.activemq.broker-url=tcp://192.168.120.32:61616
spring.activemq.user=admin
spring.activemq.password=admin
#false是队列 true指的是topic
spring.jms.pub-sub-domain=false
#自定义队列的名字
myqueue=boot-active-queue
配置Bean
@Component
@EnableJms
//@EnableJms开启了JMS适配的注解 JmsMessagingTemplate
public class ConfigBean {
//获取队列的名字
@Value("${myqueue}")
private String myQueue;
@Bean //相当于在applicationcontext.xml里面的配置
public Queue queue(){
return new ActiveMQQueue(myQueue);
}
}
编写发送者
@Component
/**
* 将这个实体类添加到Spring容器里面
* 这个类不属于@Controller @Service等这些的时候
*/
public class Queue_Produce {
@Resource
private JmsMessagingTemplate jmsMessagingTemplate;
@Resource
private Queue queue; //jms包下面的public interface Queue extends Destination
//因为通过@Bean将容器里面的进行了管理 就可以通过@Resource获取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("--->scheduled");
}
}
在启动类上面添加注解
@EnableScheduling
补充内容:
每个一段时间向MQ推送信息
//实现定时发送信息 每个3秒自动的向队列里面发送信息
//间隔定投
@Scheduled(fixedDelay = 3000)
public void produceMsgScheduled(){
jmsMessagingTemplate.convertAndSend(queue,"----scheduled"+UUID.randomUUID().toString().substring(0,6));
System.out.println("--->scheduled");
}
对于消费者来说**
配置文件和pom与发送者是一样的
@Component
public class Queue_Consumer {
@JmsListener(destination = "${myqueue}")
public void receive(TextMessage textMessage)throws JMSException{
System.out.println("消费者消费信息--->"+textMessage.getText());
}
}
发送者的类
@Component
public class Queue_Consumer {
@JmsListener(destination = "${myqueue}")
public void receive(TextMessage textMessage)throws JMSException{
System.out.println("消费者消费信息--->"+textMessage.getText());
}
}
由于消息的一致性 所以要求队列的名字要是一致的
基于订阅模式的生产者与消费者
订阅者
server.port=8888
#模拟两个消费者 8888和5566
#spring.activemq.broker-url=tcp://192.168.120.32:61616
#使用NIO协议
spring.activemq.broker-url=nio://192.168.120.32:61618
spring.activemq.user=admin
spring.activemq.password=admin
spring.jms.pub-sub-domain=true
mytopic=boot-activemq-topic
消费者操作的容器
@Component
public class Topic_Consumer {
@JmsListener(destination = "${mytopic}")
public void receive(TextMessage textMessage)throws JMSException{
System.out.println("消费者topic收到的消息是:"+textMessage.getText());
}
}
发布者
server.port=6666
#spring.activemq.broker-url=tcp://192.168.120.32:61616
#使用NIO协议
spring.activemq.broker-url=nio://192.168.120.32:61618
spring.activemq.user=admin
spring.activemq.password=admin
spring.jms.pub-sub-domain=true
#自定义主题名称
mytopic=boot-activemq-topic
配置ConfigBean
@Component
@EnableJms
public class ConfigBean {
@Value("${mytopic}")
private String topicName;
@Bean
public Topic topic(){
return new ActiveMQTopic(topicName);
}
}
配置发布者
@Component
public class Topic_Produce {
@Resource
private JmsMessagingTemplate jmsMessagingTemplate;
@Resource
public Topic topic;
@Scheduled(fixedDelay = 3000)
public void topicMsg(){
jmsMessagingTemplate.convertAndSend(topic,"主题--->"+ UUID.randomUUID().toString().substring(0,3));
}
}
针对于启动类
@SpringBootApplication
@EnableScheduling
public class ApplicationTopic {
public static void main(String[] args) {
SpringApplication.run(ApplicationTopic.class, args);
}
}
ActiveMQ的传输协议
默认的61616端口如何进行修改–>在activemq.xml里面进行修改
在生产中连接协议如何配置 使用tcp吗
可以使用NIO的形式进行传输
<transportConnector name="nio"uri="nio://0.0.0.0:61618?trace=true"/>
在里面进行添加
一旦配置未NIO那么在生产者和消费者就要进行修改
对应的地址就要修改为nio://ip:61618
为什么使用OpenWire 模式
在网络进行传输之前必须要序列化数据 消息是通过一个叫wire protocol的来序列化成字节流的
默认情况下 ActiveMQ把wire protocol叫做OpenWire 他的目的是促使网上的效率和数据快速交互
如何能够支持既支持NIO网络模型又可以支持其他的网络模型
-
使用auto关键字
-
使用+来为
-
<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:616 08? maximumConnections=1000&wireFormat.maxFrameSize=104857600& org.apache.activemq.transport.nio.SelectorManager.corePoo1Size=20& org.apache.activemq.transport.niio.SelectorManager. maximumPoolSize=50"
-
这样在设置连接的时候 既可以使用tcp也可以使用nio
ActiveMQ的消息存储和持久化
消息持久化机制主要有
AMQ Message Store(了解)是一种基于文件的存储方式
KahaDB消息存储(重要)
- 是一种基于日志文件进行存储的 是ActiveMQ5.4开始默认的持久化插件 存放在本地硬盘里面
- 如何知道
- 在activemq.xml文件里面
- 消息存储使用一个事务日志和一个索引文件来存储的所有的地址
- 地址在data/kahadb文件里面
- 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 使索引指向db-number.log里面存储的数据
- db.free 访问当前db.data文件里面那些页面是空闲的 文件具体内容是所有的空闲也的ID
- db.redo 用来进行消息恢复 如果在运行期间KahaDB消息存储在强制退出后启动 可以用来恢复BTree索引
- lock 文件所 表示当前kahadb读写权限的broker
JDBC消息存储(重要 企业中常用)
-
MQ+MySQL 将MySql的jar拷贝到lib文件夹下面
-
将<persistenceAdapter> <kahaDB directory="${activemq.data}/kahadb"/> </persistenceAdapter> 变为 <persistenceAdapter> <jdbcPersistenceAdapter dataSource="#mysql-ds" createTableOnStartup="true"/> </persistenceAdapter> dataSource是指定将要引用的持久化数据库的bean名称。 createTableOnStartup是否在启动的时候创建数据库表,默认是true,这样每次启动都会去创建表了,一般是第一次启动的时候设置为true,然后再去改成false。
-
在activemq.xml里面配置数据库连接池 <bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.10.132:3306/activemq?relaxAutoCommit=true"/> <property name="username" value="root"/> <property name="password" value="123123"/> <property name="poolPreparedStatements" value="true"/> </bean>
-
创建一个activemq的数据库并且在程序启动之后会创建三张表
- ACTIVEMQ_MSGS Queue和Topic都存在里面
- ACTIVEMQ_ACKS 用来控制消息的签收信息
- ACTIVEMQ_LOCK 在集群环境中进行使用 只有一个Broker可以获取消息 其他的只能作为备份等待Master Broker不可用才会成为下一个Master Broker
-
代码运行测试
-
前提是开启了持久化messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); 因为如果是非持久化的会保存在内存里面 持久化的会保存在相应的文件或者数据库里面
-
对于队列模式来说 生产者 public class Producer { private static final String ACTIVEMQ_URL = "nio://192.168.10.130:61616"; private static final String ACTIVEMQ_QUEUE_NAME = "Queue-JdbcPersistence"; public static void main(String[] args) throws JMSException { ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(); activeMQConnectionFactory.setBrokerURL(ACTIVEMQ_URL); Connection connection = activeMQConnectionFactory.createConnection(); Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME); MessageProducer messageProducer = session.createProducer(queue); messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); connection.start(); for (int i = 0; i < 3; i++) { TextMessage textMessage = session.createTextMessage("Queue-JdbcPersistence测试消息" + i); messageProducer.send(textMessage); } session.commit(); System.out.println("消息发送完成"); messageProducer.close(); session.close(); connection.close(); } } 消费者 public class Consumer { private static final String ACTIVEMQ_URL = "nio://192.168.10.130:61616"; private static final String ACTIVEMQ_QUEUE_NAME = "Queue-JdbcPersistence"; public static void main(String[] args) throws JMSException, IOException { ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(); activeMQConnectionFactory.setBrokerURL(ACTIVEMQ_URL); Connection connection = activeMQConnectionFactory.createConnection(); Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME); MessageConsumer messageConsumer = session.createConsumer(queue); connection.start(); messageConsumer.setMessageListener(new MessageListener() { @SneakyThrows @Override public void onMessage(Message message) { if (message instanceof TextMessage) { TextMessage textMessage = (TextMessage) message; session.commit(); System.out.println("消费者收到消息" + textMessage.getText()); } } }); System.in.read(); } }s
-
对于主题订阅发布模式 发布者 public class Producer { private static final String ACTIVEMQ_URL = "nio://192.168.10.130:61616"; private static final String ACTIVEMQ_TOPIC_NAME = "Topic-JdbcPersistence"; public static void main(String[] args) throws JMSException { ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(); activeMQConnectionFactory.setBrokerURL(ACTIVEMQ_URL); Connection connection = activeMQConnectionFactory.createConnection(); connection.setClientID("我是生产者张三"); Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); Topic topic = session.createTopic(ACTIVEMQ_TOPIC_NAME); MessageProducer messageProducer = session.createProducer(topic); messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); connection.start(); for (int i = 0; i < 3; i++) { TextMessage textMessage = session.createTextMessage("Topic-JdbcPersistence测试消息" + i); messageProducer.send(textMessage); } session.commit(); System.out.println("主题发送到MQ完成"); messageProducer.close(); session.close(); connection.close(); } } 消费者 public class Consumer1 { private static final String ACTIVEMQ_URL = "nio://192.168.10.130:61616"; private static final String ACTIVEMQ_TOPIC_NAME = "Topic-JdbcPersistence"; public static void main(String[] args) throws JMSException, IOException { ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(); activeMQConnectionFactory.setBrokerURL(ACTIVEMQ_URL); Connection connection = activeMQConnectionFactory.createConnection(); connection.setClientID("我是消费者李四"); Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); Topic topic = session.createTopic(ACTIVEMQ_TOPIC_NAME); TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "我是消费者李四我要订阅这个消息"); connection.start(); topicSubscriber.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { if (message instanceof TextMessage) { TextMessage textMessage = (TextMessage) message; try { System.out.println("消费者李四收到的消息: " + textMessage.getText()); session.commit(); } catch (JMSException e) { e.printStackTrace(); } } } }); System.in.read(); } }
-
对于队列模式来说
- 非持久话的时候会被保存在内存里面
- 持久化的时候会被保存在对应的文件和数据库里面
- 并且一旦被消费者消费之后 数据就会从数据库中进行删除但是在消费前还是保存在数据库里面的
对于发布订阅模式来说
- 设置了持久化订阅,数据库里面会保存订阅者的信息
总结
如果是queue在没有消费者消费的情况下会将消息保存到activemq_msgs表中,只要有任意一个消费者消费了,就会删除消费过的消息
如果是topic 一般是先启动消费订阅者然后再生产的情况下会将持久订阅者永久保存到qctivemq_acks,而消息则永久保存在activemq_msgs,facks表中的订阅者有一个last ack id对应了activemq_msgs中的id字段,这样就知道订阅者最后收到的消息是哪一条。
开发过程中遇到的问题
- 需要将对应版本的数据库jar或者自己使用的数据库连接池的jar包放到lib文件夹下面
- 对于配置文件里面的createTablesOnStartup属性默认为true 就是每一次启动activeMQ的时候会进行自动的创建表 所以在第一次启动之后 将值设置为false 来避免一些不必要的损失
- 注意主机的名称不能够存在下划线 否则会出现一个java.lang.IllegalStateException: LifecycleProcessor not initialized因为在这里下划线是一个不能进行编译的字符
LevelDB消息存储(了解)
JDBC Message Store with ActiveMQ Journal(JDBC加强)
这种技术克服了JDBC Store的不足 因为每一次JDBC消息发送过来地时候都要去读写数据库ActiveMQ Journal使用了告诉缓存的技术 大大的提高了性能
当消费者消费消息的速度能够及时的跟上生产者生产的速度 那么ActiveMQ Journal文件就能大大的减少需要写入DB的消息
比如:生产者生产了1000条消息 这1000条消息会被保存在journal问阿金 如果消费者消费的速度很快 在Journal文件还没有同步到DB之前 消费者已经消费了90% 那么这个时候只需要同步剩余的10%的消息到DB 如果消费者的速度很慢 这个时候Journal文件就可以让信息以批量的方式写到DB中
在配置文件里面进行配置
修改
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" />
</persistenceAdapter>
为
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="5"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="../activemq-data" />
</persistenceFactory>
总结:以前实时的写入到mysql里面 像Redis的RDB在使用了journal之后 数据会被journal处理如果在一定的时间内 journal处理完了 就不写入mysql 如果没消费完 就写入mysql 起到一个缓存的作用
启动activeMQ的时候会进行自动的创建表 所以在第一次启动之后 将值设置为false 来避免一些不必要的损失
3. 注意主机的名称不能够存在下划线 否则会出现一个java.lang.IllegalStateException: LifecycleProcessor not initialized因为在这里下划线是一个不能进行编译的字符
LevelDB消息存储(了解)
JDBC Message Store with ActiveMQ Journal(JDBC加强)
这种技术克服了JDBC Store的不足 因为每一次JDBC消息发送过来地时候都要去读写数据库ActiveMQ Journal使用了告诉缓存的技术 大大的提高了性能
当消费者消费消息的速度能够及时的跟上生产者生产的速度 那么ActiveMQ Journal文件就能大大的减少需要写入DB的消息
比如:生产者生产了1000条消息 这1000条消息会被保存在journal问阿金 如果消费者消费的速度很快 在Journal文件还没有同步到DB之前 消费者已经消费了90% 那么这个时候只需要同步剩余的10%的消息到DB 如果消费者的速度很慢 这个时候Journal文件就可以让信息以批量的方式写到DB中
在配置文件里面进行配置
修改
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" />
</persistenceAdapter>
为
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="5"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="../activemq-data" />
</persistenceFactory>
总结:以前实时的写入到mysql里面 像Redis的RDB在使用了journal之后 数据会被journal处理如果在一定的时间内 journal处理完了 就不写入mysql 如果没消费完 就写入mysql 起到一个缓存的作用
好的本文就写到这里 如果有需要补偿和交流的地方 还请在评论区进行交流