这是我学习AvtiveMQ时所整理的笔记。里面包含了介绍,配置,使用等,希望对看到这篇博客的人有帮助。
MQ介绍:
队列 :1对1
主题: 1对多
系统解耦,消息异步
ZK : 生产者,消费者中间件
eureka : 生产者,消费者中间件
MQ:send 生产者 receive 消费者 中间件
各种MQ ,技术维度都差不多,写法不一样。
为什么用MQ(优劣势) :
如果没有MQ,系统请求服务要一直等待,等服务调用过之后在做别的,导致系统利用率低;
实例中 一个功能背后可能要调用多个模块,上百个接口, 比如下单要调用库存,支付,物流等,如果一个不通,后面的都要等着,系统性能下降。
有MQ:系统按照要求的格式/约定,把请求按照先后顺序放到MQ,之后可以去做别的。系统利用率高。可以达到 解耦,消峰,异步的需求
MQ作用定义:
下载配置及使用:
ActiveMQ 官网: http://activemq.apache.org/
启动 activemq.bat
启动完成之后,在浏览器中输入下面的网址:http://localhost:8161/admin
默认用户名密码: admin
这个文件可以配置 用户名密码
这个文件配置端口
创建一个mq队列
在操作列点击Send To操作项,发送消息到FIRST_QUEUE队列。如下图所示,发送了一条消息到first_queue队列,消息过期时间为300000毫秒,即5分钟
点击列表中的Browse操作项,查看first_queue队列中的消息列表。可以看到于2019-11-14 19:02:05:111 CST发送了一条持久化的消息。
点击ID,查看明细
终止直接按 ctrl+C即可
用java编写MQ
POM:
<!-- ActiveMQ -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
JMS创建流程:Java Message Service
这是流程图:
JMS开发基本步骤:
根据流程图编写对应代码:
创建生产者:(以发送字符串 “msg–” 为例)
public class JmsProduceTopic {
/**用户名**/
public static final String ACTIVEMQ_USERNAME = "admin";
/**密码**/
public static final String ACTIVEMQ_PASSWORD = "admin";
/**URL**/
public static final String ACTIVEMQ_URL = "tcp://192.168.1.73:61616";
/**主题名字**/
public static final String TOPIC_NAME = "topic-xy233";
public static void main(String[] args) throws JMSException {
//创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_USERNAME,ACTIVEMQ_PASSWORD,ACTIVEMQ_URL);
//通过工厂,获得connection
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//创建会话session(参数 1.事物 2.签收)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地(具体是队列queue 还是主题topic)
//队列用这个
//Queue queue = session.createQueue(QUEUE_NAME);
//主题用这个
Topic topic = session.createTopic(TOPIC_NAME);
//创建消息生产者
MessageProducer messageProducer = session.createProducer(topic);
//通过使用生产者MessageProducer,生产3条消息发送到MQ队列里面
for (int i = 0 ; i<3; i++){
//创建一个字符串消息
TextMessage textMessage = session.createTextMessage("msg--" + i);
//通过messageProducer发送给MQ
messageProducer.send(textMessage);
}
//关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("--------消息发送完成--------");
}
}
运行后可以看到有3条消息进入队列并且待处理
创建消费者 两种方式:
1.同步阻塞方式(receive)
订阅者或接收者调用MessageConsumer的receive()方法来接收消息,receive方法在接收消息(或超时)之前一直阻塞
2.异步非阻塞方式(监听器onMessage())
订阅者或接收者通过MessageConsumer的SetMessageListener(MessageListener listener)注册一个消息监听器,当消息到达后,系统自动调用监听器MessageListener的onMessage(Message message)方法。
三种情况:
1:先生产,只启动1号消费者 问题:1号消费者还能消费吗?
答: YES
2:先生产,先启动1号消费者,在启动2号消费者,问题:2号消费者还能消费吗?
答:1号可以消费,2号消费者不可以消费(因为1号消费者先进来已经消费完了)
3:先启动2个消费者,在生产6条消息:消费情况如何?
答:一人一半 1号消费者 1 3 5 ,1号消费者 2 4 6
public class JmsConsumer {
/**用户名**/
public static final String ACTIVEMQ_USERNAME = "admin";
/**密码**/
public static final String ACTIVEMQ_PASSWORD = "admin";
/**URL**/
public static final String ACTIVEMQ_URL = "tcp://192.168.1.73:61616";
/**队列名字**/
public static final String QUEUE_NAME = "queue02";
public static void main(String[] args) throws JMSException, IOException {
//创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_USERNAME,ACTIVEMQ_PASSWORD,ACTIVEMQ_URL);
//通过工厂,获得connection
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//创建会话session(参数 1.事物 2.签收)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地(具体是队列queue 还是主题topic)
//队列用这个
//Queue queue = session.createQueue(QUEUE_NAME);
//主题用这个
Topic topic = session.createTopic(TOPIC_NAME);
//创建消息消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
/*
1.同步阻塞方式(receive)
订阅者或接收者调用MessageConsumer的receive()方法来接收消息,receive方法在接收消息(或超时)之前一直阻塞
while (true){
TextMessage textMessage = (TextMessage) messageConsumer.receive();
if(null != textMessage){
System.out.println("消费者接收到消息: "+ textMessage.getText());
}else {
break;
}
}
//关闭资源
messageConsumer.close();
session.close();
connection.close();*/
/*
2.通过监听的方式来接收消息
有消息就消费,没消息就等待
*/
messageConsumer.setMessageListener(message -> {
if(null != message && message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者接收到消息: "+ textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.in.read();
//关闭资源
messageConsumer.close();
session.close();
connection.close();
}
}
如果是队列运行后可以看到队列3条消息已处理,消费者处理一次
如果是主题,我发了3条消息,打开了3个消费者,平均每人接收3条信息。
控制台打印
队列和主题的区别:
JMS属性
在之前我们先了解下JavaEE
JMS是JavaEE13个技术规范里面的一个子规范——消息服务:
里面有4个元素
消息头
5大属性:
MessageID:重要,唯一识别每个消息的标识由MQ产生,可以使用getMessageID 得到
Destination:传队列或者主题
DeliveryMode:
Expiration:
Priority:
消息体:
5大格式:一般用前2个,后3个使用较少。
生产者发送方式:
消费者接收方式:
消息属性:
如何保证JMS消息的可靠性:持久性、事务、签收
持久化:不写的话默认为持久化
假如MQ挂了,如果持久化,消息不会丢失;非持久化,消息会丢失
Topic持久化:
Topic持久化和队列持久化代码不太一样,消费者代码:
public class JmsConsumerTopic {
/**用户名**/
public static final String ACTIVEMQ_USERNAME = "admin";
/**密码**/
public static final String ACTIVEMQ_PASSWORD = "admin";
/**URL**/
public static final String ACTIVEMQ_URL = "tcp://192.168.1.73:61616";
/**主题名字**/
public static final String TOPIC_NAME = "topic-xy233";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("z3");
//创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_USERNAME,ACTIVEMQ_PASSWORD,ACTIVEMQ_URL);
//通过工厂,获得connection
Connection connection = activeMQConnectionFactory.createConnection();
connection.setClientID("z3");
//创建会话session(参数 1.事物 2.签收)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地(具体是队列queue 还是主题topic)
//主题用这个
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark...");
connection.start();
Message message = topicSubscriber.receive();
while (null != message){
TextMessage textMessage = (TextMessage) message;
System.out.println("*****收到持久化topic "+ textMessage.getText());
message = topicSubscriber.receive(1000L);
}
session.close();
connection.close();
}
}
生产者代码:
public class JmsProduceTopic {
/**用户名**/
public static final String ACTIVEMQ_USERNAME = "admin";
/**密码**/
public static final String ACTIVEMQ_PASSWORD = "admin";
/**URL**/
public static final String ACTIVEMQ_URL = "tcp://192.168.1.73:61616";
/**主题名字**/
public static final String TOPIC_NAME = "topic-xy233";
public static void main(String[] args) throws JMSException {
//创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_USERNAME,ACTIVEMQ_PASSWORD,ACTIVEMQ_URL);
//通过工厂,获得connection
Connection connection = activeMQConnectionFactory.createConnection();
//创建会话session(参数 1.事物 2.签收)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地(具体是队列queue 还是主题topic)
//队列用这个
//Queue queue = session.createQueue(QUEUE_NAME);
//主题用这个
Topic topic = session.createTopic(TOPIC_NAME);
//创建消息生产者
MessageProducer messageProducer = session.createProducer(topic);
connection.start();
//通过使用生产者MessageProducer,生产3条消息发送到MQ队列里面
for (int i = 0 ; i<3; i++){
//创建一个字符串消息
TextMessage textMessage = session.createTextMessage("msg--" + i);
//通过messageProducer发送给MQ
messageProducer.send(textMessage);
}
//关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("--------持久化Topic消息发送完成--------");
}
}
先运行消费者,在运行生产者,后台显示处理完已离线:
划重点:
一定要先运行一次消费者,等于向MQ注册,类似我订阅了这个主题,然后在运行生产者发送信息,此时无论消费者是否在线,都会接收到,不在线的话,下次连接的时候会把没有接受的消息接受下来!
事务:
事务偏生产者
//事务如果为false,运行成功后消息直接进去队列,如果为true则需要先commit提交才进入队列
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
session.commit();
事务操作例:
try {
//ok session.commit()
}catch (Exception e){
e.printStackTrace();
//err
session.rollback();
}finally {
if(null != session){
session.close();
}
}
签收:
一共4种状态 1:自动签收 2:手动签收 3:允许部分重复的签收 0:事务级
一般前2个用的比较多,其余较少
一些总结
事务/签收总结:
1、开事务加 commit() ,开手动签收加 ackowledge()
2、事务大于签收,如果开事务,则无论什么签收,都算自动签收,手动不写ackowledge() 也没关系
点对点总结:
发布和订阅:
持久/非持久订阅 : 建议持久订阅
Broker
可以用不同的config配置文件模拟不同的实例
编写嵌入Broker:
POM:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
代码:运行之前记得关掉Activemq.bat ,不然会报错端口占用
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();
}
}
运行结果控制台:
生产者 和 消费者URL地址修改:
public static final String ACTIVEMQ_URL = "tcp://localhost:61616";
运行结果和之前一样。
Spring整合MQ
流程:
1.POM:
<!--整合Spring和ActiveMQ-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
2.applicationContext.xml:
<context:component-scan basepackage="com.pzq.haisong.ActiveMQ"/>
<!-- 配置MQ生产者 -->
<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.1.73:61616"></property>
</bean>
</property>
</bean>
<!-- 这个是队列 -->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="spring-active-queue"></constructor-arg>
</bean>
<!-- 这个是主题 -->
<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"></bean>
<!-- Spring提供的JMS工具类,它可以进行消息发送,接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory"></property>
<property name="defaultDestination" ref="destinationQueue"></property>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"></bean>
</property>
</bean>
3.队列生产者:
package com.pzq.haisong.ActiveMQ.spring;
import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
import javax.jms.TextMessage;
@Service
public class SpringMQ_Produce {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/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");
}
}
运行之后后台显示有一条队列:
消费者:
package com.pzq.haisong.ActiveMQ.spring;
import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
@Service
public class SpringMQ_Consumer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
SpringMQ_Consumer consumer = (SpringMQ_Consumer) ctx.getBean("springMQ_Consumer");
String retValue = (String) consumer.jmsTemplate.receiveAndConvert();
System.out.println("*******消费者收到的消息:"+ retValue);
}
}
运行之后后台处理结果:
红色部分,ref如果是destinationQueue 则程序走上面写的队列,如果是destinationTopic,程序走上面写的主题:
在spring里面可以实现消费者不启动,直接通过配置监听完成,换言之不用启动消费者程序,设置一个监听,只要生产者发送消息,订阅就可以收到:
1.新建一个 MyMessageListener接口 实现MessageListener
package com.pzq.haisong.ActiveMQ.spring;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@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();
}
}
}
}
2.修改配置文件,加一个监听器:
<!-- 配置监听程序 -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"></property>
<property name="destination" ref="destinationQueue"></property>
<!-- 需要实现一个myMessageListener接口 -->
<property name="messageListener" ref="myMessageListener"></property>
</bean>
配置好后测试一下,不启动消费者,直接启动生产者:
生产者控制台直接显示发送的消息,表示已监听到:
后台显示该主题消息已被消费者处理:
最后总结所有配置文件如下:
<!-- 自动扫描MQ包 ,将带有注解的类 纳入spring容器管理 -->
<context:component-scan base-package="com.pzq.haisong.ActiveMQ"/>
<!-- 配置MQ生产者 -->
<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.1.73:61616"></property>
</bean>
</property>
</bean>
<!-- 这个是队列 -->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="spring-active-queue"></constructor-arg>
</bean>
<!-- 这个是主题 -->
<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>
<property name="defaultDestination" ref="destinationQueue"></property>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"></bean>
</property>
</bean>
<!-- 配置监听程序 -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"></property>
<property name="destination" ref="destinationTopic"></property>
<!-- 需要实现一个myMessageListener接口 -->
<property name="messageListener" ref="myMessageListener"></property>
</bean>
ActiveMQ 传输协议
建议用前2个!!!
协议比较:
TCP:默认
NIO:性能更好
配置NIO:
修改activemq.xml ,加上这句
<transportConnector name="nio" uri="nio://0.0.0.0:61618?trace=true"/>
打开activemq.xml 修改:
之后重新启动 activemq.bat ,然后打开mq后台,发现添加成功
//新添加功能之后 ,不影响之前的tcp协议
之后把路径改为nio协议:
nio://192.168.1.73:61618
运行看是否正常
如何让端口支持NIO,又支持多协议:
修改XML文件把之前nio那行注释掉,添加一行:
<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.SelectorManager.maximumPoolSize=50" />
</transportConnectors>
之后重启activemq.bat ,打开后台查看是否显示这个连接协议:
修改代码对应端口号:之后运行程序看后台是否正常
再尝试把协议换成tcp :
tcp://192.168.1.73:61608
运行看是否正常,如果正常,说明现在可以同时适配nio和tcp协议
MQ 消息可持久化
MQ的高可用:前三个是MQ自带的,假如MQ自身坏了就GG了;第四个可以同步MQ,是一个假如MQ服务器down机了,消息不会丢失的机制;
1、事务
2、持久
3、签收
4、可持久化
可持久化:
kahaDB 存储:
官网和默认存储路径可以在avtivemq.xml 里看到:
ActiveMQ5.4 以后,默认使用kahaDB 存储:
事务日志:正文
索引文件:目录
db.data文件存的为索引, 索引指向数据 db<1>.log 文件的数据。db.redo 用来容灾,恢复索引
JDBC消息存储:
1、copy一份mysql-connector-java-8.0.16.jar 到 ActiveMQ\apache-activemq-5.15.10\lib 路径下
2、配置jdbcPersitenceAdapter
修改activemq.xml 文件,把默认kahaDB换成jdbc持久化配置
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" createTablesOnStartup="true" />
</persistenceAdapter>
3、提供一个名为 mysql-ds 的数据源
<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://127.0.0.1:3306/activemq?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
把这段放到xml文件最底部 标签之间
至此MQ配置完成,接下来配置mysql
4、mysql配置
创建好activemq库表,然后重启activemq.bat 重新读取配置
会发现已经自动创建好了3张表:
!!生产者发送消息后,msgs表中会有数据; 但是被消费者消费后,会删除相应消息
MSGS:
ACK:
LOCK:
代码验证:
总结:
JDBC Message store with ActiveMQ journal:
相当于从mysql和生产者之间加了个缓存,生产的快,mysql慢的话,未消费的先存到journal里,要消费先在这里面找,然后慢慢同步到mysql里
把之前的注释掉,加上这个
<!-- 带高速缓存的journal持久化方案 -->
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="4"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="activemq-data"/>
</persistenceFactory>
总结: