MQ的处理流程
发送者把消息发送给消息服务器,消息服务器把消息存放在若干队列/主题topic中,在合适的时候,消息服务器会将消息转发给接受者,在这个过程中,发送和接受是异步的,也就是发送无需等待,而且发送者和接受者的生命周期没有必然关系;尤其是在发布pub/订阅sub模式下,也可以完成一对多的通信,即让一个消息有多个接受者.
activeMQ两个端口
开启ActiveMQ
./activemq start
./activemq start xbean:file:配置文件目录
./activemq start > ../data/activemq.log
关闭ActiveMQ
在ActiveMQ的bin目录下执行./activemq stop
activeMQ的broker
相当于一个activeMQ的服务器实例.
broker其实就是实现了用代码的形式启动activeMQ将MQ嵌入到java代码中,以便随时用随时启动,在用的时候再去启动,这样能节省资源,也保证了可靠性.
activeMQ broker使用代码
package com.atguigu.activemq.Embed;
import org.apache.activemq.broker.BrokerService;
public class EmbedBroker {
public static void main(String[] args) throws Exception {
// 将迷你版的activeMQ嵌入到java程序中
BrokerService brokerService = new BrokerService();
// jmx: Java Management Extensions 它是一个Java平台的管理和监控接口
brokerService.setUseJmx(true);
brokerService.addConnector("tcp://localhost:61616");
brokerService.start();
}
}
注: 启动该activeMQ服务,然后启动生产者和消费者便能消费消息.
activemq传输协议(重点是TCP和NIO)
TCP(Transmission Control Protocal 传输控制协议,默认)
NIO(New I/O API Protocal)
<broker>
...
<transportConnectors>
<transportConnector name="nio" uri="nio://0.0.0.0:61616"/>
<transportConnectors>
...
broker>
AMQP(Advanced Message Queuing Protocal 高级消息队列协议)
stomp(Streaming Text Orientated Message Protocal 流文本定向消息协议)
一种为MOM(Message Oriented Middleware 面向消息的中间件)设计的简单文本协议.
Secure Sockets Layer Protocal(SSL 安全套接字协议)
<broker>
...
<transportConnectors>
<transportConnector name="ssl" uri="ssl://localhost:61618?trace=true"/>
<transportConnectors>
...
broker>
mqtt(Message Queuing Telemetry Transport 消息队列遥测传输协议)
IBM开发的一个即使通讯协议,有可能成为物联网的重要组成部分.该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当作传感器和制动器(比如通过Twitter让房屋联网)的通信协议
ws(WebSockets)协议
用于前端
修改activemq.xml配置文件
注: 端口不能冲突.原本openwire(即TCP)的端口为61616,因为使用了docker,容器端口映射没添加成功,所以为了演示NIO,NIO使用了61616端口.
生产者和消费者代码只需要修改URL即可.其余代码参考:springboot整合activemq的queue/topic代码
server:
port: 6666
spring:
activemq:
# 只需要修改该行
broker-url: nio://localhost:61616
user: admin
password: admin
jms:
pub-sub-domain: true # true代表topic,false代表queue
# 自己定义的主题名称
mytopic: boot-activemq-topic
默认的NIO是基于TCP的,想要让NIO支持其它协议,可以使用auto+nio
修改activemq.xml配置文件(注意端口不要冲突)
<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61606?maximumConnections=1000" />
在代码中修改url为tcp/nio或其他协议即可使用相应协议
server:
port: 6666
spring:
activemq:
# 切换BIO的TCP协议和NIO的TCP协议只需要修改下面一行即可,如果使用其他协议,java代码不太一样.
# broker-url: nio://localhost:61616
broker-url: tcp://localhost:61616
user: admin
password: admin
jms:
pub-sub-domain: true # true代表topic,false代表queue
# 自己定义的主题名称
mytopic: boot-activemq-topic
注: 其余代码参考:springboot整合activemq的queue/topic代码
官网
KahaDB消息存储: 基于日志文件,从ActiveMQ5.4开始默认的持久化插件
update table set a = 1 where id = 1;
就会记录一条日志:把第10表空间的第90号页面的偏移量为1024处的值更新为1
JDBC消息存储
wget -P ./ https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.17/mysql-connector-java-8.0.17.jar
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
persistenceAdapter>
修改后:<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" />
persistenceAdapter>
注: dataSource:是指定将要引用的持久化数据库的bean名称。
createTableOnStartup:是否在启动的时候创建数据表,默认是true,这样每次启动都会去创建表了,一般是第一次启动的时候设置为true,然后再去改成false。
AMQ消息存储: 基于文件的存储方式,以前的默认消息存储,现在不用了
AMQ是一种文件存储形式,它具有写入速度快和容易恢复的特点。消息存储在一个个文件中,文件的默认大小为32M,当一个文件中的消息已经全部被消费,那么这个文件将被标识为可删除,在下一个清除阶段,这个文件被删除。AMQ适用于ActiveMQ5.3之前的版本
LevelDB消息存储(了解)
这种文件系统是从ActiveMQ5.8之后引进的,它和KahaDB非常相似,也是基于文件的本地数据库存储形式,但是它提供比KahaDB更快的持久性。但它不使用自定义B-Tree实现来索引独写日志,而是使用基于LevelDB的索引
默认配置如下:
<persistenceAdapter>
<levelDB directory="activemq-data"/>
persistenceAdapter>
集群仅提供主备方式的高可用集群功能,避免单点故障
解释
部署步骤
环境:
集群部署规划列表
主机 | zookeeper集群端口 | AMQ集群bind端口 | AMQ消息tcp端口 | AMQ管理控制台端口 | AMQ节点安装目录 |
---|---|---|---|---|---|
192.168.145.3 | 2181 | bind=“tcp://0.0.0.0:63631” | 61616 | 8161 | /mq_cluster/mq_node01 |
192.168.145.3 | 2182 | bind=“tcp://0.0.0.0:63632” | 61617 | 8162 | /mq_cluster/mq_node02 |
192.168.145.3 | 2183 | bind=“tcp://0.0.0.0:63633” | 61618 | 8163 | /mq_cluster/mq_node03 |
关闭防火墙并保证可以ping通activemq服务器
要求具备zookeeper集群并可以成功启动(配置见zookeeper)
#!/bin/sh
cd /usr/local/zk-cluster/zk1/bin
./zkServer.sh start
cd /usr/local/zk-cluster/zk2/bin
./zkServer.sh start
cd /usr/local/zk-cluster/zk3/bin
./zkServer.sh start
把上面的脚本内容中的start改为stop即可
sudo chmod 700 ./zk_batch_start.sh
sudo chmod 700 ./zk_batch_stop.sh
创建3台activemq集群目录
mkdir mq_cluster
cd mq_cluster
sudo cp -r /usr/local/apache-activemq-5.16.0/ mq_node01
sudo cp -r /usr/local/apache-activemq-5.16.0/ mq_node02
sudo cp -r /usr/local/apache-activemq-5.16.0/ mq_node03
修改管理控制台ip地址和端口(jetty.xml)
hostname名字映射(修改ubuntu虚拟机中的/etc/hosts文件)
# 192.168.145.3为ubuntu虚拟机的ip地址
192.168.145.3 mq-server
activemq集群配置
修改各节点的消息端口(activemq.xml)
mq_node01消息端口为61616,不需要改.
mq_node02消息端口为61617:
<transportConnector name="openwire" uri="tcp://0.0.0.0:61617?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
mq_node03消息端口为61618:
<transportConnector name="openwire" uri="tcp://0.0.0.0:61618?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
按顺序启动3个activemq节点(前提是zk集群已经成功启动运行)
#!/bin/sh
cd /usr/local/mq_cluster/mq_node01/bin
./activemq start
cd /usr/local/mq_cluster/mq_node02/bin
./activemq start
cd /usr/local/mq_cluster/mq_node03/bin
./activemq start
把上面的脚本内容中的start改为stop即可
sudo chmod 700 ./amq_batch_start.sh
sudo chmod 700 ./amq_batch_stop.sh
zk集群节点状态说明
# 任意启动一台zookeeper client
./zkCli.sh -server localhost:2181
# ls /查看节点
[zk: localhost:2181(CONNECTED) 4] ls /
[activemq, zookeeper]
[zk: localhost:2183(CONNECTED) 1] ls /activemq
[leveldb-stores]
[zk: localhost:2183(CONNECTED) 2] ls /activemq/leveldb-stores
[00000000006, 00000000007, 00000000005]
下面截图中可以看到以13为结尾的节点elected有值,而另外两个节点elected为null,所以节点13为master,另外两个为slaver
activemq管理控制台展示(activemq的客户端只能访问master的Broker,根据bind端口号63631确定master管理控制台端口为8161)
No IOExceptionHandler registered, ignoring IO exception | org.apache.activemq.broker.BrokerService | LevelDB IOException handler. java.io.IOException:
private static final String ACTIVEMQ_URL = "cp://localhost:61616?jms.useAsyncSend=true";
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
...
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
activeMQConnectionFactory.setUseAsyncSend(true);
...
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
ActiveMQConnection connection = (ActiveMQConnection) activeMQConnectionFactory.createConnection();
connection.setUseAsyncSend(true);
...
异步发送丢失的场景是生产者设置useAysncSend=true,使用produce.send(msg)持续发送消息.由于消息不堵塞,生产者会认为所有发送的消息均被成功发送至MQ.如果MQ突然宕机,那么生产者端内存中尚未发送到MQ的消息全部丢失.
所以正确的异步发送方式是需要回调的.
同步发送和异步发送的区别在此: 同步发送send()不堵塞了就表示一定发送成功了,异步发送需要接受回调并由客户端再判断一次是否发送成功.
异步发送+回调代码:
package com.atguigu.activemq.queue;
import lombok.extern.log4j.Log4j;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQMessageProducer;
import org.apache.activemq.AsyncCallback;
import javax.jms.*;
import java.util.UUID;
@Log4j
public class JmsSyncProduce {
// 单机
// private static final String ACTIVEMQ_URL = "tcp://localhost:61616";
// private static final String QUEUE_NAME = "queue01";
// 集群
// failover:失败转移协议,当某一个activemq节点挂掉时转移到另一个,如果仅剩一个activemq节点,由于不能选举master,所以activemq不能正常运行.
private static final String ACTIVEMQ_URL = "failover:(tcp://192.168.145.3:61616,tcp://192.168.145.3:61617,tcp://192.168.145.3:61618)?randomize=false";
private static final String QUEUE_NAME = "queue-cluster";
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 session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4。创建目的地(队列或主题)
Queue queue = session.createQueue(QUEUE_NAME);
// 5。创建消息生产者
ActiveMQMessageProducer activeMQMessageProducer = (ActiveMQMessageProducer) session.createProducer(queue);
// 6。通过使用messageProducer生产3条消息发送到MQ的队列中
TextMessage textMessage = null;
for(int i = 1; i <= 3; i++){
// 7。 创建消息
textMessage = session.createTextMessage("cluster msg---" + i); //理解为字符串
textMessage.setJMSMessageID(UUID.randomUUID().toString() + "---orderAtguigu");
String msgId = textMessage.getJMSMessageID();
// 8。通过messageProducer发送给mq
activeMQMessageProducer.send(textMessage, new AsyncCallback() {
@Override
public void onSuccess() {
log.info(msgId + "has send ok");
}
@Override
public void onException(JMSException e) {
log.info(msgId + "send failed");
}
});
}
// 9。关闭资源
activeMQMessageProducer.close();
session.close();
connection.close();
log.info("******消息发送到MQ完成");
}
}
应用场景
邮件发送等
具体操作
package com.atguigu.activemq.queue;
import lombok.extern.log4j.Log4j;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ScheduledMessage;
import javax.jms.*;
@Log4j
public class JmsProduct_DelayAndSchedule {
// 单机
// private static final String ACTIVEMQ_URL = "tcp://localhost:61616";
// private static final String QUEUE_NAME = "queue01";
// 集群
// failover:失败转移协议,当某一个activemq节点挂掉时转移到另一个,如果仅剩一个activemq节点,由于不能选举master,所以activemq不能正常运行.
private static final String ACTIVEMQ_URL = "failover:(tcp://192.168.145.3:61616,tcp://192.168.145.3:61617,tcp://192.168.145.3:61618)?randomize=false";
private static final String QUEUE_NAME = "queue-cluster";
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 = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4。创建目的地(队列或主题)
Queue queue = session.createQueue(QUEUE_NAME);
// 5。创建消息生产者
MessageProducer messageProducer = session.createProducer(queue);
// 延时投递时间
long delay = 3 * 1000;
// 重复投递的时间间隔,即每4s投递一次
long period = 4 * 1000;
// 重复投递次数
int repeat = 5;
// 6。通过使用messageProducer生产3条消息发送到MQ的队列中
for(int i = 1; i <= 3; i++){
// 7。 创建消息
TextMessage textMessage = session.createTextMessage("cluster msg---" + i); //理解为字符串
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
textMessage.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
// 8。通过messageProducer发送给mq
messageProducer.send(textMessage);
}
// 9。关闭资源
messageProducer.close();
session.close();
connection.close();
log.info("******消息发送到MQ完成");
}
}
那些情况会引发消息重发
消息重发时间间隔和最大重发次数(只算重发,不算第一次发送的)
间隔1 次数6
有毒消息Poison ACK
一个消息被重发超过默认最大重发次数(6次)时,消费端会给MQ发送一个“poison ack”表示这个消息有毒,告诉broker不要再发了,这个时候broker会把消息放到死信队列(DLQ, dead letter queue)