消息隊列學習-ActiveMQ(一)
1 前言
1.1 是什麼
MQ = 消息中間件
1.2 消息中間件的特性要求:
- api發送和接受
- MQ的高可用性
- MQ的集羣和容錯配置
- MQ的持久化
- 延遲發送/定時投遞
- 簽收機制
- Spring整合
- …
1.3 產品種類
- Kafka
- RabbitMQ
- RocketMQ
- ActiveMQ
- 其他
1.4 能幹嘛
存在的問題:
- 系統之間接口耦合比較嚴重
- 面對大流量併發時,容易被沖垮
- 等待同步存在性能問題
要達到的目標:
- 解耦
- 削峯
- 異步
2 安裝步驟
2.1 官網下載
2.2 複製到opt目錄下面
2.3 解壓縮apache-activemq-5.15.9-bin.tar.gz
2.4 在根目錄下mkdir /myacitveMQ
2.5 cp -r apache-activemq-5.15.9 /myacitveMQ
2.6 普通啓動mq: ./activemq start (關閉 stop)
2.7 mq的默認端口是:61616
2.8 三種查看進程的方法 1)ps -ef|grep “activemq”|grep -v grep 2)lsof -i:61616 3)netstat -anp|grep 61616
2.9 帶日誌啓動 ./activemq start > xxx.log
3 啓動控制檯
目標:windows訪問虛擬機上的activemq控制檯
(1)查詢window IP及虛擬機IP
//windows
ipconfig
// linux
ifconfig
(2)測試兩個地址可以相互ping即可
windows:
linux:
(3)關閉window防火牆、linux防火牆
命令如下:
// windows 自己點吧...
// linux
service iptables stop 或 systemctl stop firewalld
(4)訪問activemq控制檯
// 1. 虛擬機linux啓動activemq
./activemq start
// 2. windows瀏覽器訪問 http://192.168.188.128:8161/
注:
(1) 默認的用戶名和密碼是admin和admin
(2)採用61616端口提供JMS服務
(3)採用8161端口提供管理控制檯服務
4 Java編碼實現ActiveMQ通信
4.1 IDEA建maven工程
4.2 pom.xml
<!--activemq所需jar包-->
<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>
<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.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
4.3 JMS編碼總體架構
4.4 粗說目的地Destination(隊列或主體)
4.5 在點對點的消息傳遞域中,目的地被稱爲隊列(queue)
點對點消息傳遞域的特點
(1)每個消息只能有一個消費者,類似1對1的關係。
(2)消息的生產者和消費者之間沒有時間上的相關性。無論消費者在生產者發送消息的時候是否處於運行狀態,消費者都可以提取信息。
(3)消息被消費後,隊列中不會再存儲,所以消費者不會消費到已經被消費掉的信息。
生產者代碼:
package com.sky.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private 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);
// 4. 創建目的地(具體是隊列還是主題)
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("***********消息發佈到MQ完成");
}
}
運行代碼,執行結果如下:
在查看控制檯:
消費者代碼:(Receive())
同步阻塞方式(receive())
訂閱者或接收者調用messageConsumer的receive()方法來接受消息,receive方法能夠在接受消息之前(或超時之前)將一直阻塞。
package com.sky.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsConsumer {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private 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);
// 4. 創建目的地(具體是隊列還是主題)
Queue queue = session.createQueue(QUEUE_NAME);
// 5.創建消費者
MessageConsumer messageConsumer = session.createConsumer(queue);
while (true) {
TextMessage textMessage = (TextMessage) messageConsumer.receive();
if (textMessage != null) {
System.out.println("****消費者收到消息:msg---" + textMessage.getText());
}else {
break;
}
}
messageConsumer.close();
session.close();
connection.close();
}
}
運行代碼,執行結果如下:
查看隊列控制檯:
消費者代碼:(監聽方式)
異步非阻塞方式(監聽器 onMessage())
訂閱者或接收者通過 MessageConsumer 的 setMessageListener(MessageListener listener),當消息達到後,系統自動調用監聽器 MessageListener 的 onMessage(Message message)
package com.sky.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class JmsConsumer {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private 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);
// 4. 創建目的地(具體是隊列還是主題)
Queue queue = session.createQueue(QUEUE_NAME);
// 5.創建消費者
MessageConsumer messageConsumer = session.createConsumer(queue);
/*
while (true) {
TextMessage textMessage = (TextMessage) messageConsumer.receive();
if (textMessage != null) {
System.out.println("****消費者收到消息:msg---" + textMessage.getText());
}else {
break;
}
}*/
// 通過監聽的方式來消費消息
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("****消費者收到消息:ListenerMsg---" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
結果同上面類似,不再截圖。
情景小節
- 先生產,只啓動1號消費者。問題:1號消費者能消費信息嗎?Y
- 先生產,先啓動1號消費者,在啓動2號消費者。
問題1:1號消費者可以消費嗎?Y
問題2:2號消費者可以消費嗎?N - 先啓動2個消費者,再生產6個消息,請問,消費情況如何?一人一半
4.6 在發佈訂閱消息傳遞域中,目的地被稱爲主體(topic)
發佈訂閱傳遞域的特點
(1)生產者將消息發佈到 topic 中,每個消息可以有多個消費者,屬於1:N的關係。
(2)生產者和消費者之間有時間上的相關性。訂閱某個主題的消費者只能消費自它訂閱之後發佈的消息。
(3)生產者生產時,topic 不保存消息,它是無狀態的不落地,假如無人訂閱就去生產,那就是一條廢消息,所以一般先啓動消費者再啓動生產者。
JMS規範允許客戶創建持久訂閱,這在一定程度上放鬆了時間上的相關性要求。持久訂閱允許消費者消費它在未處於激活狀態時發送的消息。(類比微信公衆號訂閱)
生產者代碼:
package com.sky.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce_Topic {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private static final String TOPIC_NAME = "topic01";
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);
// 4. 創建目的地(具體是隊列還是主題)
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完成");
}
}
消費者代碼:
package com.sky.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class JmsConsumer_Topic {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private static final String TOPIC_NAME = "topic01";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("我是2號消費者");
// 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);
// 4. 創建目的地(具體是隊列還是主題)
Topic topic = session.createTopic(TOPIC_NAME);
// 5.創建消費者
MessageConsumer messageConsumer = session.createConsumer(topic);
// 通過監聽的方式來消費消息
messageConsumer.setMessageListener((message) -> {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("****消費者收到topic消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
先執開啓兩個消費者,在生產三條消息,執行結果如下:
4.7 兩大模式的比較
比較項目 | Topic模式隊列 | Queue模式隊列 |
---|---|---|
工作模式 | “訂閱-發佈模式”,如果當前沒有訂閱,消息將會被丟棄,如果有多個訂閱者都會收到消息 | “負載均衡”模式,如果當前沒有消費者,消費者也不會丟棄;如果有多個消費者,那麼一條消息也只會發送給其中一個消費者,並且要求消費者ack消息 |
有無狀態 | 無狀態 | Queue數據默認會在mq服務器上以文件形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data 下面,也可以配置成DB存儲。 |
傳遞完整性 | 如果每日喲訂閱者,消息會被丟棄 | 消息不會被丟棄 |
處理效率 | 由於消息要按照訂閱者的數量進行復制,所以處理性能會隨着訂閱者的增加而明顯降低,並且還要結合不同消息協議自身的性能差異 | 由於一條消息只能發送給一個消費者,所以就算消費者就算再多,性能也不會有明顯降低。當然不同的消息協議的具體性能也是有差異的 |
未完待續…