学习地址:https://www.bilibili.com/video/BV164411G7aB?vd_source=461545ff50a35eaeaa8218ecdc5f7152
时长:11小时25分钟
ActiveMQ官方文档:https://activemq.apache.org/
MQ=消息中间件/消息队列
消息:微信、短信、语音…
中间件:类似nacos、Zookeeper…
主要作用:解耦、削锋、异步
2.两种讲授闲聊
MQ的产品种类
ActiveMQ
Kafka:https://www.bilibili.com/video/BV1vr4y1677k?spm_id_from=333.999.0.0
RabbitMQ:https://www.bilibili.com/video/BV1vr4y1677k?spm_id_from=333.999.0.0
RocketMQ:https://www.bilibili.com/video/BV1cf4y157sz?vd_source=461545ff50a35eaeaa8218ecdc5f7152
ActiveMQ ( MQ 都需要满足的技术 )
引入MQ前:学生问问题,非常多的学生,除了第一个再问,其余人都要一直等待
引入MQ后:老师给班长说,让学生把问题按照一定的格式写在纸上,按照先后顺序给班长,不用一直排队耗着
为什么要使用 MQ ?
解决了耦合调用、异步模型、抵御洪峰流量,保护了主业务,消峰。
系统之间直接调用实际工程落地和存在的问题
系统之间耦合比较严重
面对大流量并发时,容易被冲垮
等待同步存在性能问题
发送者和接收者不必了解对方只需要确认消息
发送者和接收者不必同时在线
ActiveMQ 的官网 : http://activemq.apache.org
只能下载Linux,不能再Windows上集成服务器
怎么玩
最主要的功能:实现高可用、高性能、可伸缩、易用和安全的企业级面向消息服务的系统
异步消息的消费和处理
控制消息的消费顺序
可以和Spring/SpringBoot整合简化编码
配置集群容错的MQ集群
http://IP地址:8161
默认的用户名和密码是admin/admin
先用ping目录检查一下
通过61616端口提供JMS服务
采用8161端口提供管理控制台服务
访问不到的坑:
1 可能是你的linux 和 windows 没有在一个网关下
2 可能你windows 的防火墙或者 linux 的防火墙没有关掉(是的,先得关掉防火墙)
3 你忘记启动activemq 的服务了
4 你启动失败了,可能是你得java 环境没配好,必须是jdk 8 或者以上
public class JmsProduce {
//linux 上部署的activemq 的 IP 地址 + activemq 的端口号
public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
//队列的名字
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws Exception{
//1.创建连接工厂,按照给定的url创建连接工程,这个构造器采用默认的用户名密码admin/admin
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂获得连接connection并启动
javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
//启动
connection.start();
//3.创建回话session
//两个参数,第一个事务,第二个签收(自动)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(两种:队列/主题,这里用队列)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.通过messageProducer生产3条消息发送到消息队列中
for (int i = 1; i < 4 ; i++) {
//7.创建消息
//理解为一个字符串,好比学生按照固定的格式要求写好的问题
TextMessage textMessage = session.createTextMessage("msg--" + i);
//8.通过messageProducer发布消息
messageProducer.send(textMessage);
}
//9.关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println(" **** 消息发送到MQ完成 ****");
}
}
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
public static final String QUEUE_NAME = "queue01"; // 1对1 的队列
public static void main(String[] args) throws Exception{
//1.按照给定的url创建连接工程,这个构造器采用默认的用户名密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂连接connection和启动
javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
//启动
connection.start();
//3.创建回话session
//两个参数,第一个事务, 第二个签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地 (两种 : 队列/主题 这里用队列)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消息的消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
while(true){
//这里是 TextMessage是因为消息发送者是 TextMessage,接受处理的也应该是这个类型的消息
TextMessage message = (TextMessage)messageConsumer.receive();
if (null != message){
System.out.println("****消费者的消息:"+message.getText());
}else {
break;
}
}
messageConsumer.close();
session.close();
connection.close();
}
}
待处理的消息:0
消费者数量:1个
消息的入队:3
消息的出队:3
//这里是 TextMessage是因为消息发送者是 TextMessage,接受处理的也应该是这个类型的消息
TextMessage message = (TextMessage)messageConsumer.receive();
//receive()不带时间,死等,灯不灭
//receive(4000L)带时间,过时不候,消费者数量变为0
// 通过监听的方式来消费消息
// 通过异步非阻塞的方式消费消息
// 通过messageConsumer 的setMessageListener 注册一个监听器,
// 当有消息发送来时,系统自动调用MessageListener 的 onMessage 方法处理消息
messageConsumer.setMessageListener(new MessageListener() { // 可以用监听器替换之前的同步receive 方法
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();
}
}
}
});
16.队列案例小总结
Topic生产者
public class JmsProduce {
//linux 上部署的activemq 的 IP 地址 + activemq 的端口号
public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
//队列的名字
public static final String TOPIC_NAME = "topic";
public static void main(String[] args) throws Exception{
//1.创建连接工厂,按照给定的url创建连接工程,这个构造器采用默认的用户名密码admin/admin
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂获得连接connection并启动
javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
//启动
connection.start();
//3.创建回话session
//两个参数,第一个事务,第二个签收(自动)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(两种:队列/主题,这里用队列)
Topic topic = session.createTopic(TOPIC_NAME);
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
//6.通过messageProducer生产3条消息发送到消息队列中
for (int i = 1; i < 4 ; i++) {
//7.创建消息
//理解为一个字符串,好比学生按照固定的格式要求写好的问题
TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i);
//8.通过messageProducer发布消息
messageProducer.send(textMessage);
}
//9.关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println(" **** TOPIC_NAME消息发送到MQ完成 ****");
}
}
比较项目 | Topic模式队列 | Queue模式队列 |
---|---|---|
工作模式 | “订阅-发布”模式,如果当前没有订阅者,消息将会被丢弃,如果有多个订阅者,那么这些订阅者都会收到消息 | “负载均衡” 模式,如果当前没有消费者,消息也不会丢弃;如果有多个消费者,那么一条消息也只会发送给其中的一个消费者,并且要求消费者ack信息 |
有无状态 | 无状态 | Queue数据默认会在mq服务器上以文件的形式保存,比如ActiveMQ一般保存在$AMQ_HOME\data\kr-store\data下面,也可以配置成DB存储 |
传递完整性 | 如果没有订阅者,消息会被丢弃 | 消息不会被丢弃 |
处理效率 | 由于消息要按照订阅者的数量进行复制,所以处理性能会随着订阅者的增加而明显降低,并且还要结合不同消息协议自身的性能差异 | 由于一条消息只能发送给一个消费者,所以就算消费者再多,性能也不会有明显降低,当然不同消息协议的具体性能也是有差异的 |
JAVAEE 是一套使用Java 进行企业级开发的13 个核心规范工业标准
JDBC 数据库连接
JNDI Java的命名和目录接口
EJB Enterprise java bean
RMI 远程方法调用 一般使用TCP/IP 协议
Java IDL 接口定义语言
JSP
Servlet
XML
JMS Java 消息服务
JTA
JTS
JavaMail
JAF
JMS的可靠性:持久性、事务、签收
JMS部件 | JMS provider | JMS producer | JMS consumer | JMS message |
---|---|---|---|---|
含义 | 实现JMS 的消息中间件,也就是MQ服务器 | 消息生产者,创建和发送消息的客户端 | 消息消费者,接收和处理消息的客户端 | JMS 消息,分为消息头、消息属性、消息体 |
JMS message组成:消息头、消息属性、消息体
消息头 | JMSDestination | JMSDeliveryMode | JMSExpiration | JMSPriority | JMSMessageId |
---|---|---|---|---|---|
含义 | 头在哪儿 | 是持久还是非持久 | 过期时间,默认永不过期 | 优先级,默认是4有0~9 ,5-9 是紧急的,0-4 是普通的 | 唯一的消息ID |
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
//6.通过messageProducer生产3条消息发送到消息队列中
for (int i = 1; i < 4 ; i++) {
//7.创建消息
//理解为一个字符串,好比学生按照固定的格式要求写好的问题
TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i);
//textMessage.set各种消息头
//8.通过messageProducer发布消息
messageProducer.send(textMessage);
//send也可以设置各种消息头
}
发送和接收的消息类型必须一致
5种消息体 | TextMessage | Mapmessage | BytesMessage | StreamMessage | ObjectMessage |
---|---|---|---|---|---|
含义 | 普通字符串消息,包含一个String | Map 类型的消息,k-> String v -> Java 基本类型 | 二进制数组消息,包含一个byte[] | Java 数据流消息,用标准流操作来顺序的填充读取 | 对象消息,包含一个可序列化的Java 对象 |
消息属性:识别、去重、重点标注
他们是以属性和属性值对的形式制定的。可以将属性是为消息头得扩展,属性指定一些消息头没有包括的附加信息,比如可以再属性里指定消息选择器。
如果需要除消息头字段以外的值,那么可以使用消息属性
识别/去重/重点标注等操作非常有用的方法
消息的可靠性:持久性、事务、签收
// 在队列为目的地的时候持久化消息
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
// 队列为目的地的非持久化消息
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
持久化的消息,服务器宕机后消息依旧存在,只是没有入队,当服务器再次启动,消息任就会被消费。
非持久化的消息,服务器宕机后消息永远丢失。
而当你没有注明是否是持久化还是非持久化时,默认是持久化的消息。
对于目的地为主题(topic)来说,默认就是非持久化的,让主题的订阅支持化的意义在于:对于订阅了公众号的人来说,当用户手机关机,在开机后任就可以接受到关注公众号之前发送的消息。
持久化topic 的消费者
// 前面代码相同,不复制了
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark...");
// 5 发布订阅
connection.start();
Message message = topicSubscriber.receive();// 一直等
while (null != message){
TextMessage textMessage = (TextMessage)message;
System.out.println(" 收到的持久化 topic :"+textMessage.getText());
message = topicSubscriber.receive(3000L); // 等1秒后meesage 为空,跳出循环,控制台关闭
}
持久化生产者
MessageProducer messageProducer = session.createProducer(topic);
// 6 通过messageProducer 生产 3 条 消息发送到消息队列中
// 设置持久化topic 在启动
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
for (int i = 1; i < 4 ; i++) {
// 7 创建字消息
TextMessage textMessage = session.createTextMessage("topic_name--" + i);
// 8 通过messageProducer发布消息
messageProducer.send(textMessage);
MapMessage mapMessage = session.createMapMessage();
// mapMessage.setString("k1","v1");
// messageProducer.send(mapMessage);
}
// 9 关闭资源
当生产者启动后:
消息被消费,并且: (因为我在receive方法中设置了如果接收到消息后3秒还没有消息就离线,也也可以设置永久存活)
createSession的第一个参数为true 为开启事务,开启事务之后必须在将消息提交,才可以在队列中看到消息
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
提交:
session.commit();
事务开启的意义在于,如果对于多条必须同批次传输的消息,可以使用事务,如果一条传输失败,可以将事务回滚,再次传输,保证数据的完整性。
对于消息消费者来说,开启事务的话,可以避免消息被多次消费,以及后台和服务器数据的不一致性。
举个栗子:
如果消息消费的 createSession 设置为 ture ,但是没有 commit ,此时就会造成非常严重的后果,那就是在后台看来消息已经被消费,但是对于服务器来说并没有接收到消息被消费,此时就有可能被多次消费。
29.消息的消费者事务介绍
Acknowledge 签收 (俗称ack)
非事务 :
Session.AUTO_ACKNOWLEDGE //自动签收,默认
Session.CLIENT_ACKNOWLEDGE //手动签收
//手动签收需要acknowledge
textMessage.acknowledge();
对于开启事务时,设置手动签收和自动签收没有多大的意义,都默认自动签收,也就是说事务的优先级更高一些,事务>签收。
Session session = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);
//Session session = connection.createSession(true,Session.CLIENT_ACKNOWLEDGE); // 也是自动签收
……
session.commit();
但是开启事务没有commit就会重复消费
点对点是模型是基于队列的,生产者发送消息到队列,消费者从队列接收消息,队列的存在使消息的异步传输成为可能。和我们平时给朋友发送短信类似。
1.如果在session关闭时有部分罅隙已被接收到但还没有被签收,那么当消费者下次连接到相同的队列时,这些消息还会被再次接收
2.队列可以长久的保存消息直到消费者接收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的连接状态,充分体现了异步传输模式的优势
发布订阅
1.主题可以被认为是消息的传输中介,发布者(publisher)发布消息到主题,订阅者(subscribe)从主题订阅消息。
2.主题使得消息订阅者和消息发布者保持相互独立,不需要接触即可保证消息的传送。
非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态下才能收到发送到某个主题的消息
如果消费者处于离线状态,生产者发送的主题消息将会丢失作废,消费者永远不会收到。
一句话:先要订阅注册才能收到发布,只给订阅者发布消息
broker 就是实现了用代码形式启动 ActiveMQ 将 MQ 内嵌到 Java 代码中,可以随时启动,节省资源,提高了可靠性。
就是将 MQ 服务器作为了 Java 对象
使用多个配置文件启动 activemq
cp activemq.xml activemq02.xml
// 以active02 启动mq 服务器
./activemq start xbean:file:/myactivemq/apache-activemq-5.15.9/conf/activemq02.xml
把小型 activemq 服务器嵌入到 java 代码: 不在使用linux 的服务器
导入依赖
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.5version>
dependency>
代码实现
public class Embebroker {
public static void main(String[] args) throws Exception {
// broker 服务
BrokerService brokerService = new BrokerService();
// 把小型 activemq 服务器嵌入到 java 代码
brokerService.setUseJmx(true);
// 原本的是 192.…… 是linux 上的服务器,而这里是本地windows 的小型mq服务器
brokerService.addConnector("tcp://localhost:61616");
brokerService.start();
}
}
所需jar 包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jmsartifactId>
<version>4.3.23.RELEASEversion>
dependency>
<dependency>
<groupId>org.apache.activemqgroupId>
<artifactId>activemq-poolartifactId>
<version>5.15.9version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>4.3.23.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.3.23.RELEASEversion>
dependency>
写xml 配置文件(applicationContext.xml)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://camel.apache.org/schema/spring"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<context:commponent-scan base-package="com.at.activemq"/>
<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.17.3: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>
<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>
编写代码:
//生产者
@Service
public class SpringMQ_producer {
//导入模板
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
//读配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//类似SpringMQ_producer produce = new SpringMQ_producer();
SpringMQ_producer producer = (SpringMQ_producer) ctx.getBean("springMQ_Producer");
producer.jmsTemplate.send((session) -> {
TextMessage textMessage = session.createTextMessage("spring 和 activemq 的整合");
return textMessage;
});
System.out.println(" *** send task over ***");
}
}
//消费者
@Service
public class Spring_MQConsummer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Spring_MQConsummer sm = (Spring_MQConsummer)ac.getBean("spring_MQConsummer");
String s = (String) sm.jmsTemplate.receiveAndConvert();
System.out.println(" *** 消费者消息"+s);
}
}
只需要修改配置文件(applicationContext.xml)
修改部分
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-topic">constructor-arg>
bean>
<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 xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://camel.apache.org/schema/spring"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<context:commponent-scan base-package="com.at.activemq"/>
<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.17.3: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>
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-topic">constructor-arg>
bean>
<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>
1.添加配置文件(applicationContext.xml)配置监听程序
2.添加MyMessageListener类实现消息监听
3.只需要启动生产者,消费者不用启动,自动会监听记录
1.建立boot 项目,配置 pom.xml 配置 application.yml 配置 bean
2.编写生产者 编写启动类 测试类
按键触发消息和定时发送消息的业务代码:
// 调用一次一个信息发出
public void produceMessage(){
jmsMessagingTemplate.convertAndSend(queue,"****"+ UUID.randomUUID().toString().substring(0,6));
}
// 带定时投递的业务方法
@Scheduled(fixedDelay = 3000) // 每3秒自动调用
public void produceMessageScheduled(){
jmsMessagingTemplate.convertAndSend(queue,"** scheduled **"+ UUID.randomUUID().toString().substring(0,6));
System.out.println(" produceMessage send ok ");
}
对于消息消费者,在以前使用单独的监听器类,编写监听器代码,但是在spring boot 中,使用注解 JmsListener 即可:
@Component
public class Queue_consummer {
@JmsListener(destination = "${myqueue}") // 注解监听
public void receive(TextMessage textMessage) throws Exception{
System.out.println(" *** 消费者收到消息 ***"+textMessage.getText());
}
}
编写消费者项目
编写主题的消息生产者和消费者项目,运行demo
ActiveMQ 支持的协议有 TCP 、 UDP、NIO、SSL、HTTP(S) 、VM
这是activemq 的activemq.xml 中配置文件设置协议的地方
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumCon nections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnect ions=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConn ections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnect ions=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnection s=1000&wireFormat.maxFrameSize=104857600"/>
transportConnectors>
默认是使用 openwire 也就是 tcp 连接
默认的Broker 配置,TCP 的Client 监听端口 61616 ,在网络上传输数据,必须序列化数据,消息是通过一个 write protocol 来序列化为字节流。默认情况 ActiveMQ 会把 wire protocol 叫做 Open Wire ,它的目的是促使网络上的效率和数据快速交互。
使用Tcp 的一些优化方案:tcp://hostname:port?key=value
它的参数详情参考:http://activemq.apache.org/tcp-transport-reference
NIO协议和TCP协议类似但NIO更侧重于底层的访问操作。它允许开发人员对同一资源有更多的client调用和服务端有更多的负载,NIO 协议为ActiveMQ 提供更好的性能
适合NIO 使用的场景:
1.当有大量的Client 连接到Broker 上 , 使用NIO 比使用 tcp 需要更少的线程数量,所以使用 NIO
2.可能对于 Broker 有一个很迟钝的网络传输, NIO 的性能高于 TCP
NIO连接形式:nio://hostname:port?key=value
各种协议对比 : http://activemq.apache.org/configuring-version-5-transports.html
修改配置文件activemq.xml
如果不特别指定ActiveMQ的网络监听端口,那么这些端口都将使用BIO网络IO模型
所以为了首先提高单节点的网络吞吐性能,我们需要明确指定Active的网络IO模型
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumCon nections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnect ions=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConn ections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnect ions=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnection s=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="nio" uri="nio://0.0.0.0:61618?trace=true"/>
transportConnectors>
访问ActiveMQ客户端
修改消息生产者和消费者的 URL即可
//public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
public static final String ACTIVEMQ_URL = "nio://192.168.17.3:61618";
问题:URI 格式以 nio 开头,表示这个端口使用 tcp 协议为基础的NIO 网络 IO 模型,但这样设置方式,只能使这个端口支持Openwire协议。那么我们怎么既让这个端口支持NIO网络IO模型,又让他支持多个协议呢?
Starting with version 5.13.0, ActiveMQ supports wire format protocol detection. OpenWire, STOMP, AMQP, and MQTT can be automatically detected. This allows one transport to be shared for all 4 types of clients.
使用 : auto+nio+ssl
官网介绍 :http://activemq.apache.org/auto
使用 auto 的方式就相当于四合一协议 : STOMP AMQP MQTT TCP NIO
<transportConnector name="auto+nio" uri="auto+nio://localhost:5671"/>
auto 就像是一个网络协议的适配器,可以自动检测协议的类型,并作出匹配
<transportConnector name="auto" uri="auto://localhost:5671?auto.protocols=default,stomp"/>
配置文件修改:
……
<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61608?maximumConnections=1000
&wireFormat.maxFrameSize=104857600&org.apache.activemq.transport.nio.SelectorManager.corelPoolSize=20
&org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50"/>
public static final String ACTIVEMQ_URL = "nio://192.168.17.3:61608";
对于 NIO 和 tcp 的代码相同,但不代表使用其他协议代码相同,因为底层配置不同,其他协议如果使用需要去修改代码
官网:http://activemq.apache.org/persistence
将MQ收到的消息存储到文件、硬盘、数据库等、 则叫MQ的持久化,这样即使服务器宕机,消息在本地还是有,仍旧可以访问到。
采用持久化机制
。从存储中删除
,失败则继续尝试发送。AMQ 是文件存储
形式,写入快、易恢复 默认 32M 在 ActiveMQ 5.3 之后不再适用
KahaDB 的属性配置 :http://activemq.apache.org/kahadb
KahaDB : 5.4 之后基于日志文件
的持久化插件,默认
持久化插件,提高了性能和恢复能力,类似Redis的AOF。它使用一个事务日志
和索引文件
来存储所有的地址。
四类文件+一把锁 ==> KahaDB
1.db-<数字>.log
:kahaDB存储消息到预定义大小的数据记录文件中,一个存满(如每32M一个文件)会再次创建db-2.log、db-3.log …… 。当不会有引用到数据文件的内容时,文件会被删除或归档。
2.db.data
:该文件包含了持久化的BTree 索引,索引了消息数据记录的消息,是消息索引文件,本质上是B-Tree(B树),它作为索引指向了 db-.log 里的消息。
一点题外话:就像mysql 数据库,新建一张表,就有这个表对应的 .MYD 文件,作为它的数据文件,就有一个 .MYI 作为索引文件。
3.db.free
:文件的具体内容是所有空闲页的ID,存储空闲页 ID有时会被清除
4.db.redo
:当 KahaDB 消息存储在强制退出后启动,用于恢复 BTree 索引
5.lock
顾名思义就是锁
LeavelDB :5.8 以后引进,希望作为以后的存储引擎,也是基于文件的本地数据存储形式,但是比 KahaDB更快。
它比KahaDB 更快的原因是她不使用B-Tree 索引,而是使用本身自带的 LeavelDB索引
题外话:为什么LeavelDB 更快,并且5.8 以后就支持,为什么还是默认 KahaDB 引擎?
因为 activemq 官网本身没有定论,LeavelDB之后又出了可复制的LeavelDB比LeavelDB 更性能更优越,但需要基于 Zookeeper 所以这些官方还没有定论,仍旧使用 KahaDB。
JDBC : 有一部分数据会真实的存储到数据库中
使用JDBC 的持久化,
①修改配置文件,默认 kahaDB
修改之前:
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
persistenceAdapter>
修改之后:
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
persistenceAdapter>
②在activemq 的lib 目录下添加 jdbc 的jar 包 (connector.jar 我使用5.1.41 版本)
③修改配置文件 : activemq.xml 使其连接自己windows 上的数据库,并在本地创建名为activemq 的数据库
④让linux 上activemq 可以访问到 mysql ,之后产生消息。
ActiveMQ 启动后会自动在 mysql 的activemq 数据库下创建三张表:activemq_msgs 、activemq_acks、activemq_lock
activemq_acks:用于存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存
activemq_lock:在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker
activemq_msgs:用于存储消息,Queue和Topic都存储在这个表中
点对点会在数据库的数据表 ACTIVEMQ_MSGS 中加入消息的数据,且在点对点时,消息被消费就会从数据库中删除。
但是对于主题,订阅方式接受到的消息,会在 ACTIVEMQ_MSGS 存储消息,即使MQ 服务器下线,并在 ACTIVEMQ_ACKS 中存储消费者信息 。 并且存储以 activemq 为主,当activemq 中的消息被删除后,数据库中的也会自动被删除。
如果是queue
在没有消费者消费的情况下会将消息保存到activemq_msgs表中,只要有任一一个消费者已经消费过了,消费者之后这些消息将会立即被删除。
如果是topic
一般是先启动消费订阅然后再生产的情况下会将消息保存到activemq_acks
是什么
JDBC 改进: 加入高速缓存机制 Journal
高速缓存在 activemq.xml 中的配置:
持久化消息是指:
MQ 所在的服务器down 了消息也不会丢失
持久化机制演化过程:
从最初的AMQ Message Store 方案到 ActiveMQ V4版本推出的High performance journal (高性能事务)附件,并且同步推出了关系型数据库的存储方案, ActiveMQ 5.3 版本有推出了KahaDB 的支持,(也是5.4之后的默认持久化方案),后来ActiveMQ 从5.8开始支持LevelDB ,现在5.9 提供了 Zookeeper + LevelDB 的集群化方案。
ActiveMQ 消息持久化机制有:
机制 | 基于什么 |
---|---|
AMQ | 基于日志文件 |
KahaDB | 基于日志文件,5.4 之后的默认持久化 |
JDBC | 基于第三方数据库 |
LevelDB | 基于文件的本地数据库存储,从5.8 之后推出了LevelDB 性能高于 KahaDB |
ReplicatedLevelDB Store | 从5.8之后提供了基于LevelDB 和Zookeeper 的数据复制方式,用于Master-slave方式的首数据复制选方案 |
但是无论使用哪种持久化方式,消息的存储逻辑都一样
如何保证高可用:基于Zookeeper和LeveIDB搭建ActiveMQ集群。集群仅提供主备方式的高可用集群功能,避免单点故障。
官方文档:http://activemq.apache.org/replicated-leveldb-store
这幅图的意思就是当Master 宕机后,zookeper监测到没有心跳信号, 则认为master 宕机了,然后选举机制会从剩下的Slave中选出一个作为新的Master
集群搭建: 新建 /mq_cluster 将原始的解压文件复制三个,修改端口 (jetty.xml)
增加IP 到域名的映射(/etc/hosts 文件)
修改 为相同的borkername
改为 replica levelDB (3个都配,这里列举一个)
<persistenceAdapter>
<replicatedLevelDB
directory="{activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:63631"
zkAddress="localhost:2191,localhost:2192,localhost:2193"
zkPassword="123456"
sync="local_disk"
zkPath="/activemq/leveldb-stores"
hostname="wh-mq-server"
/>
persistenceAdapter>
改端口 02 节点 =》 61617 03 节点 =》 61618
想要启动replica leavel DB 必须先启动所有的zookeper 服务,zookeper 的单机伪节点安装这里不细说了,主要说zookeper 复制三份后改配置文件,并让之自动生成 myid 文件,并将zk的端口改为之前表格中对应的端口 。这是conf 下的配置文件
其具体配置为:
tickTime=2000
initLimit=10
syncLimit=5
clientPort=2191 // 自行设置
server.1=192.168.17.3:2888:3888
server.2=192.168.17.3:2887:3887
server.3=192.168.17.3:286:3886
dataDir=/zk_server/data/log1 // 自行设置
设置了三个,此时方便起见可以写批处理脚本
#!/bin/sh // 注意这个必须写在第一行
cd /zk_server/zk_01/bin
./zkServer.sh start
cd /zk_server/zk_02/bin
./zkServer.sh start
cd /zk_server/zk_03/bin
./zkServer.sh start
编写这个 zk_batch.sh 之后, 使用
chmod 700 zk_batch.sh
命令即可让它变为可执行脚本, ./zk_batch.sh start 即可 (即启动了三个zk 的服务)
同理可以写一个批处理关闭zk 服务的脚本和 批处理开启mq 服务 关闭 mq 服务的脚本。
完成上述之后连接zk 的一个客户端
./zkCli.sh -server 127.0.0.1:2191
连接之后:
表示连接成功
查看我的三个节点: 我的分别是 0…… 3 …… 4 …… 5
查看我的节点状态
get /activemq/leveldb-stores/00000000003
此次验证表明 00000003 的节点状态是master (即为63631 的那个mq 服务) 而其余的(00000004 00000005) activemq 的节点是 slave
如此集群顺利搭建成功 !
ActiveMQ的客户端只能访问Master的Broker,其它处于Slave的Broker不能访问,所以客户端连接的Broker应该使用failover协议(失败转移)
当一个ActiveMQ节点挂掉或者一个Zookeeper节点挂掉,ActiveMQ服务仍然正常运转,如果仅剩一个ActiveMQ节点,由于不能选举Master,所以ActiveMQ不能正常运行
同样的,如果Zookeeper仅剩一个结点活动,不管ActiveMQ各节点存活,ActiveMQ也不能正常提供服务。(ActiveMQ集群的高可用,依赖Zookeeper集群的高可用)
1.引入消息队列之后该如何保证其高可用性
zookeeper+replicated-leceldb-store的主从集群
2.异步投递Async Sends
// 开启异步投递
activeMQConnectionFactory.setUseAsyncSend(true);
如何在投递快还可以保证消息不丢失 ?
异步发送消息丢失的情况场景是: UseAsyncSend 为 true 使用 producer(send)持续发送消息,消息不会阻塞,生产者会认为所有的 send 消息均会被发送到 MQ ,如果MQ 突然宕机,此时生产者端尚未同步到 MQ 的消息均会丢失 。
故正确的异步发送方法需要接收回调
同步发送和异步发送的区别就在于 :
同步发送send 不阻塞就代表消息发送成功
异步发送需要接收回执并又客户端在判断一次是否发送
在代码中接收回调的函数 :
activeMQConnectionFactory.setUseAsyncSend(true);
……
for (int i = 1; i < 4 ; i++) {
textMessage = session.createTextMessage("msg--" + i);
textMessage.setJMSMessageID(UUID.randomUUID().toString()+"-- orderr");
String msgid = textMessage.getJMSMessageID();
messageProducer.send(textMessage, new AsyncCallback() {
@Override
public void onSuccess() {
// 发送成功怎么样
System.out.println(msgid+"has been successful send ");
}
@Override
public void onException(JMSException e) {
// 发送失败怎么样
System.out.println(msgid+" has been failure send ");
}
});
}
① 在配置文件中设置定时器开关 为 true
② 代码编写
Java 代码中封装的辅助消息类型 ScheduleMessage
可以设置的常用参数如下:
long delay = 3 * 1000 ;
long perid = 4 * 1000 ;
int repeat = 7 ;
for (int i = 1; i < 4 ; i++) {
TextMessage textMessage = session.createTextMessage("delay msg--" + i);
// 消息每过 3 秒投递,每 4 秒重复投递一次 ,一共重复投递 7 次
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,perid);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT,repeat);
messageProducer.send(textMessage);
}
官网介绍:https://activemq.apache.org/redelivery-policy
死信队列的一些设置
修改,当嫌6 次太多,设置为 3次 // 三次的意思是不计算本来发送的第一次 ,之后再次发送的第三次就被废弃
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(3);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
在spring 中使用 死信机制
在业务逻辑中,如果一个订单系统没有问题,则使用正常的业务队列,当出现问题,则加入死信队列 ,此时可以选择人工干预还是机器处理 。
死信队列默认是全部共享的,但是也可以设置独立的死信队列
独立的死信队列配置
如何保证消息不被重复消费,幂等性的问题
如果消息是做数据库的插入操作,给这个消息一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据 。
如果不是,可以用redis 等的第三方服务,给消息一个全局 id ,只要消费过的消息,将 id ,message 以 K-V 形式写入 redis ,那消费者开始消费前,先去 redis 中查询有没消费的记录即可。
观察者模式 、 发布订阅者设计模式 :
观察者模式 : 对象间的一对多的依赖关系
何谓观察者模式?观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。
在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。
发布订阅者 : 是观察者模式的一个概念的变种,
发布/订阅者模式与观察者模式主要有以下几个不同点: