1.MQ
mq稱爲消息隊列或稱爲消息中間件。(利用高效可靠的消息傳遞機制進行與平臺無關的數據交流,並基於數據通信來進行分佈式系統的集成)
作用:
(1)降低系統的耦合度。
(2)異步請求模式。
(3)降低峯值流量的訪問,保護主業務功能。
實現過程:發送方把消息發送給消息服務器,消息服務器將消息存放在隊列/主題中,在合適的時候,消息服務器會將消息發送給接收方。在這個過程中,發送和接受是異步的,發送無需等待,而且發送方和接收方的生命週期也沒有必然關係。如果是在主題的模式下,可以完成一對多的通信,既讓一個消息有多個接收者。
2、linux安裝ActiveMq
(1) 官網下載
http://activemq.apache.org/components/classic/download/
(2)上傳壓縮包到/opt文件夾下
(3)解壓到/usr/local文件下下
(4)普通啓動 ./activemq start
(4-1)自定義日誌啓動 ./activemq start > 日誌位置
./activemq start > /usr/local/activeMq_log/run_activemq.log
(5)activeMq默認進程端口號 61616 前端web項目訪問端口 8161
(6)查詢ps -ef |grep activemq |grep -v grep
(7)lsof -i : 61616
3、訪問activemq控制檯
(1)關閉linux防火牆或者配置白名單
service iptables stop
(2)用戶名:admin 密碼:admin
4、java編碼MQ
(1)創建maven工程,引入activemq所需jar包
<dependencies>
<!-- activemq所需要的jar包配置-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.10</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!-- broker 需要-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
</dependencies>
(2)JMS消息結構
(3)java編碼簡易DEMO---生產者
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
*/
public class Demo01 {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String QUEUE_NAME = "queue_ldd_01";
public static void main(String[] args) {
//1創建一個鏈接工廠
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME,PASSWORD,ACTIVEMQ_URL);
try {
//2根據鏈接工廠創建鏈接
Connection connection = connectionFactory.createConnection();
//2-1 啓動鏈接
connection.start();
//3根據鏈接創建session對象
//transacted 事務 Session.AUTO_ACKNOWLEDGE 簽收(自動簽收)
Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
//4創建消息類型 是隊列(queue)或者主題(topic)
Queue queue = session.createQueue( QUEUE_NAME );
//5創建消息的生產者
MessageProducer producer = session.createProducer( queue );
//6創建一個字符串消息
TextMessage message = session.createTextMessage();
message.setText( "這是一條字符串消息---queue" );
//7生產者發送消息
producer.send( message );
//釋放資源
producer.close();
session.close();
connection.close();
System.out.println("消息發送完成");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
Number Of Pending Messages :等待消費的消息 。【當前未出隊列的消息。等待消費的消息=總接收消息-總出隊列的消息】
Number Of Consumers :消費者數量。【消費者端的消費者數量】
Messages Enqueued :進隊消息數量。【包含出隊列的,只增加不減少】
Messages Dequeued :出隊消息數。【消費者消費掉的消息數量】
(4)java編碼簡易DEMO---消費者
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
* 消費者
*/
public class ConsumersDemo01 {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String QUEUE_NAME = "queue_ldd_01";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD,ACTIVEMQ_URL );
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
Queue queue = session.createQueue( QUEUE_NAME );
//創建消費者
MessageConsumer consumer = session.createConsumer( queue );
while(true){
/**
* 拿不到textMessage就卡住無法向下執行 如果爲null可能就出現異常 跳出循環結束
* receive(); 持續等待
* receive(longTime);設置等待時長,超時不再獲取消息 返回null
*/
TextMessage textMessage = (TextMessage) consumer.receive();
if(textMessage != null){
System.out.println("消費消息----");
System.out.println("消費內容:[]"+textMessage.getText());
}else{
break;
}
}
consumer.close();
session.close();
connection.close();
}
}
5、監聽方式消費消息
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
* 消費者
*/
public class ConsumersDemo02 {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String QUEUE_NAME = "queue_ldd_01";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD,ACTIVEMQ_URL );
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
Queue queue = session.createQueue( QUEUE_NAME );
//創建消費者
MessageConsumer consumer = session.createConsumer( queue );
//監聽消息消費
consumer.setMessageListener( new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("監聽事件消費消息---");
System.out.println("消息內容[]"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
} );
//保證控制檯不關閉
System.in.read();
consumer.close();
session.close();
connection.close();
}
}
6、消費情況
兩種消費方式:
(1)同步阻塞方式(receive())
訂閱者或者接收者調用MessageConsumer的receive()方法來接收消息,receive()方法在能夠接收到消息之前(或超時之前)將一直阻塞。
(2)異步非阻塞方式(監聽器onMessage())
訂閱者或接收者通過MessageConsumer的setMessageListener(MessageListener listener)註冊一個消息監聽器。
當消息到達之後,系統自動調用監聽器MessageListener的onMessage(Message message)的方法。
隊列消費情況---------------------
一個生產者,生產6條消息,2個消費者依次開啓服務,消費消息數平分。
7、java編程topicDemo
(1)生產者將消息發佈到topic中,每個消息可以有多個消費者,屬於一對多關係。
(2)生產者和消費者之間有時間上的相關性,訂閱某個主題的消費者只能消費自它訂閱之後發佈的消息。
(3)生產者產生消息,topic不保存消息它是無狀態的,無人訂閱就生產消息,所產生的消息是廢消息。所有,先訂閱後發佈消息。
持久訂閱:JMS允許客戶創建持久訂閱,持久訂閱允許消費者消費它在未處於激活狀態時生產者發送的消息。
生產者
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
* 生產者
*/
public class TopicProducerDemo01 {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String TOPIC_NAME = "topic_ldd_01";
public static void main(String[] args) {
//1創建一個鏈接工廠
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME,PASSWORD,ACTIVEMQ_URL);
try {
//2根據鏈接工廠創建鏈接
Connection connection = connectionFactory.createConnection();
//2-1 啓動鏈接
connection.start();
//3根據鏈接創建session對象
//transacted 事務 Session.AUTO_ACKNOWLEDGE 簽收(自動簽收)
Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
//4創建消息類型 是隊列(queue)或者主題(topic)
Topic topic = session.createTopic( TOPIC_NAME );
//5創建消息的生產者
MessageProducer producer = session.createProducer( topic );
//6創建一個字符串消息
for( int i = 0; i < 7; i++ ) {
TextMessage message = session.createTextMessage();
message.setText( "這是一條字符串消息---topic" );
//7生產者發送消息
producer.send( message );
}
//釋放資源
producer.close();
session.close();
connection.close();
System.out.println("消息發送完成");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
消費者
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
* 消費者
*/
public class TopicConsumersDemo01 {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String TOPIC_NAME = "topic_ldd_01";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD,ACTIVEMQ_URL );
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
Topic topic = session.createTopic( TOPIC_NAME );
//創建消費者
MessageConsumer consumer = session.createConsumer( topic );
while(true){
TextMessage textMessage = (TextMessage) consumer.receive();
if(textMessage != null){
System.out.println("消費者1號--內容:[]"+textMessage.getText());
}else{
break;
}
}
consumer.close();
session.close();
connection.close();
}
}
8、Topic與Queue區別
Queue | Topic | |
工作模式 | "負載均衡"模式,如果沒有消費者,消息不會丟棄,如果有多個消費者,消息只會被其中一條消費者消費,並且要求消費者ack信息 | "訂閱發佈"模式,如果沒有訂閱者,消息會被丟棄,如果存在多個訂閱者,那麼這些訂閱者都會收到消息 |
有無狀態 | Queue數據默認會在mq服務器上以文件形式保存。比如ActiveMq一般保存在&AMQ_HOME\data\kr-store\data下面 | 無狀態 |
傳遞完整性 | 消息不會丟失 | 如果沒有訂閱者,消息將被丟棄 |
處理效率 | 由於一條消息只對應一個消費者,所有就算消費者再多,性能也不會有明顯的降低。 | 由於一個消息對應多個訂閱者,隨着訂閱者增多,處理性能將會降低 |
9、JMS
Java Message Service(Java消息服務器是JavaEE中的一個技術)
Java消息服務器是指兩個應用程序之間進行異步通信的API,它爲標準消息協議和消費服務提供了一組通用接口,包括創建,發送,讀取消息等,用於支持Java應用程序的開發。在JavaEE中,當兩個應用程序使用JMS進行通信時,它們之間並不是直接相連的,而是通過一個共同的消息收發服務組件關聯起來,以達到解耦、異步、削峯的效果。
10、Message
消息頭:
JMSDestination:設置消息發送的目的地,主要指Queue和Topic。
JMSDeliveryMode:設置持久模式和非持久模式。
持久模式:消息應該被傳送一次僅僅一次。如果JMS提供者出現故障,該消息不會丟失,它會在服務器恢復之後再傳遞。
非持久消息:消息最多被傳遞一次,意味着服務器故障,該消息將永遠丟失。
JMSExpiration:設置消息在一定時間後過期,默認是永不過期。如果發送後,在消息過期時間之後消息還沒有被髮送到目的地,則改消息被清除。
JMSPriority:設置消息優先級,從0-9十個級別,0-4是普通消息,5-9是加急消息。JMS不要求MQ嚴格按照這十個優先級發送消息,但必須保證加急消息要先於普通消息到達。普通消息級別是4。
JMSMessageID:唯一識別每個消息的標識由MQ產生。
消息體:
封裝具體的消息數據。
5種消息體格式:
(1)TextMessage:普通字符串消息,包含一個String
(2)MapMessage:一個Map類型的消息,key爲String類型,而值爲Java的基本類型。
(3)BytesMessage:二進制數組消息,包含一個Byte[]
(4)StreamMessage:Java數據流消息,用標準流操作填充和讀取。
(5)ObjectMessage:對象消息,包含一個可序列化的Java對象。
消息屬性:
消息頭的擴展,K-V形式的數據。
set*Property(k,v)
11、JMS的可靠性
持久化Queue:
注意:Queue默認是持久化消息。
(1)非持久化:
服務器宕機,消息丟失。producer.setDeliveryMode( DeliveryMode.NON_PERSISTENT );
(2)持久化:
服務器宕機,消息依然存在。producer.setDeliveryMode(DeliveryMode.PERSISTENT);
持久化Topic:
注意:設置爲持久化訂閱,需要啓動一次,等於向MQ註冊,消費者在線會立刻收到發佈的消息,消費者不在線,下次啓動時會收到消息。
topic消息發佈者(持久化)
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/28.
* 持久化消息 生產者
*/
public class TopicProducerPersist {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String TOPIC_NAME = "topic_persist_01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME,PASSWORD,ACTIVEMQ_URL );
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
Topic topic = session.createTopic( TOPIC_NAME );
//創建topic消息生產者
MessageProducer producer = session.createProducer( topic );
//設置爲持久化消息
producer.setDeliveryMode( DeliveryMode.PERSISTENT );
//連接
connection.start();
for(int i = 0; i < 3; i++){
TextMessage textMessage = session.createTextMessage();
textMessage.setText( "我是持久化消息"+i );
producer.send( textMessage );
}
session.close();
connection.close();
}
}
topic消息訂閱者(持久化)
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
* 持久化訂閱者
*/
public class TopicConsumersPersist {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String TOPIC_NAME = "topic_persist_01";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD,ACTIVEMQ_URL );
Connection connection = connectionFactory.createConnection();
//設置訂閱者的名字
connection.setClientID( "司大雕1號" );
Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
Topic topic = session.createTopic( TOPIC_NAME );
//創建一個持久訂閱者
TopicSubscriber subscriber = session.createDurableSubscriber( topic, "remark..." );
connection.start();
Message message = subscriber.receive();
while (message != null){
TextMessage textMessage = (TextMessage)message;
System.out.println("接收持久化消息:"+textMessage.getText());
message = subscriber.receive();
}
session.close();
connection.close();
}
}
12、事務
手動提交事務,確保程序的可靠性。若在消息發送期間出現異常,可手動回滾。
事務消息生成者
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/11/9.
* <p>
* 事務發送消息
* 確保程序的可靠性
* 出現異常可手動回滾
*/
public class TransactionProducer {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String TOPIC_NAME = "transaction_queue";
public static void main(String[] args) {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
Connection connection = null;
Session session = null;
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession( true, Session.AUTO_ACKNOWLEDGE );
Queue queue = session.createQueue( TOPIC_NAME );
MessageProducer producer = session.createProducer( queue );
TextMessage textMessage = session.createTextMessage();
for (int i = 0; i < 3; i++) {
textMessage.setText( "我是事務消息" + i );
producer.send( textMessage );
}
//手動提交事務,若沒有提交事務 消息無法存在消息服務器中
session.commit();
} catch (JMSException e) {
e.printStackTrace();
//出現異常手動回滾
try {
session.rollback();
} catch (JMSException e1) {
e1.printStackTrace();
}
} finally {
if (session != null) {
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
事務消息接收者
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
* /**
* 接收事務消息
* <p>
* 如果已非事務模式接收 消息會被消費一次
* <p>
* 如果已事務模式接收 消息會被消息一次 但必須手動 commit提交。
* <p>
* 若不commit手動提交,消息不會確認消息,可重複消費。
*/
public class TransactionConsumers {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String QUEUE_NAME = "transaction_queue";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
Connection connection = connectionFactory.createConnection();
connection.start();
/**
* 接收事務消息
*
* 如果已非事務模式接收 消息會被消費一次
*
* 如果已事務模式接收 消息會被消息一次 但必須手動 commit提交。
*
* 若不commit手動提交,消息不會確認消息,可重複消費。
*
*/
Session session = connection.createSession( true, Session.AUTO_ACKNOWLEDGE );
Queue queue = session.createQueue( QUEUE_NAME );
//創建消費者
MessageConsumer consumer = session.createConsumer( queue );
while (true){
TextMessage textMessage = (TextMessage) consumer.receive(4000L);
if(textMessage != null){
System.out.println(textMessage.getText());
}else{
break;
}
}
//手動接收消息 提交事務
session.commit();
session.close();
connection.close();
}
}
13、Acknowledge(簽收)
非事務簽收
消息生產者
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/11/9.
* <p>
* 非事務下消息發送者,設置簽收模式 手動模式
*/
public class NotTransactionProducerAck {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String TOPIC_NAME = "not_transaction_queue_ack";
public static void main(String[] args) {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
Connection connection = null;
Session session = null;
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession( false, Session.CLIENT_ACKNOWLEDGE );
Queue queue = session.createQueue( TOPIC_NAME );
MessageProducer producer = session.createProducer( queue );
TextMessage textMessage = session.createTextMessage();
for (int i = 0; i < 3; i++) {
textMessage.setText( "ack手動簽收消息" + i );
producer.send( textMessage );
}
} catch (JMSException e) {
e.printStackTrace();
//出現異常手動回滾
try {
session.rollback();
} catch (JMSException e1) {
e1.printStackTrace();
}
} finally {
if (session != null) {
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
消息接收者
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
* 非事務手動簽收消息
*/
public class NotTransactionConsumersAck {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String QUEUE_NAME = "not_transaction_queue_ack";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession( false, Session.CLIENT_ACKNOWLEDGE );
Queue queue = session.createQueue( QUEUE_NAME );
//創建消費者
MessageConsumer consumer = session.createConsumer( queue );
while (true){
TextMessage textMessage = (TextMessage) consumer.receive(4000L);
if(textMessage != null){
System.out.println(textMessage.getText());
//手動簽收消息
textMessage.acknowledge();
}else{
break;
}
}
session.close();
connection.close();
}
}
事務簽收
以事務方式發送消息,接收者必須手動commit確認消息已經消費,否則會存在重複消費的問題。(事務模式下消息消費與簽收類型無關,都必須手動提交事務)
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/11/9.
* <p>
* 事務下消息發送者,設置簽收模式 手動模式
*/
public class TransactionProducerAck {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String TOPIC_NAME = "transaction_queue_ack_lmr";
public static void main(String[] args) {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
Connection connection = null;
Session session = null;
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession( true, Session.CLIENT_ACKNOWLEDGE );
Queue queue = session.createQueue( TOPIC_NAME );
MessageProducer producer = session.createProducer( queue );
TextMessage textMessage = session.createTextMessage();
for (int i = 0; i < 3; i++) {
textMessage.setText( "ack手動簽收消息" + i );
producer.send( textMessage );
}
session.commit();
} catch (JMSException e) {
e.printStackTrace();
//出現異常手動回滾
try {
session.rollback();
} catch (JMSException e1) {
e1.printStackTrace();
}
} finally {
if (session != null) {
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
package com.ldd.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/10/17.
*
* 事務模式下,不區分消息的簽收類型,
* 都必須手動commit 確認消息消費。
*/
public class TransactionConsumersAck {
public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
public static final String USERNAME = "admin";
public static final String PASSWORD = "admin";
public static final String QUEUE_NAME = "transaction_queue_ack_lmr";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession( true, Session.CLIENT_ACKNOWLEDGE );
Queue queue = session.createQueue( QUEUE_NAME );
//創建消費者
MessageConsumer consumer = session.createConsumer( queue );
while (true){
TextMessage textMessage = (TextMessage) consumer.receive(4000L);
if(textMessage != null){
System.out.println(textMessage.getText());
//事務模式下,不區分消息的簽收類型都必須手動commit確認消息消費。
//textMessage.acknowledge();
}else{
break;
}
}
//提交事務
session.commit();
session.close();
connection.close();
}
}
14、Broker
相當於一個ActiveMq服務器實例。
Broker實現了用代碼形式啓動ActiveMQ將MQ嵌入到JAVA代碼中,以便隨時啓動。
activeMQ指定配置文件啓動:
package com.ldd.activemq;
import org.apache.activemq.broker.BrokerService;
/**
* Created by ldd on 2019/11/10.
*/
public class Broker {
public static void main(String[] args) throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setUseJmx( true );
brokerService.addConnector( "tcp://localhost:61616" );
brokerService.start();
}
}
15、Spring整合ActiveMQ
(1)Maven POX.XML
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.10</version>
</dependency>
<!-- Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
(2)Spring配置文件
<?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:context="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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 開啓自動掃包-->
<context:component-scan base-package="com.ldd.*" />
<!-- 連接對象-->
<bean id="pooledConnectionFactory" 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.182.129:61616"/>
</bean>
</property>
<property name="maxConnections" value="100" />
</bean>
<!-- 隊列-->
<bean id="activeMQQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="spring-active-queur"/>
</bean>
<!-- spring提供JMS工具類,它可以進行消息發送、接收 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="pooledConnectionFactory"/>
<property name="defaultDestination" ref="activeMQQueue"/>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
</property>
</bean>
</beans>
(3)隊列
隊列----生成者
package com.ldd.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.jms.core.MessageCreator;
import org.springframework.stereotype.Service;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
/**
* Created by ldd on 2019/11/10.
*/
@Service
public class Spring_produce {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" );
Spring_produce spring_produce = (Spring_produce) applicationContext.getBean( "spring_produce" );
spring_produce.jmsTemplate.send( new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage();
textMessage.setText( "Spring整合ActiveMq消息測試---" );
return textMessage;
}
} );
System.out.println("消息發送完畢");
}
}
隊列----消費者
package com.ldd.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;
/**
* Created by ldd on 2019/11/10.
*/
@Service
public class Spring_consume {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" );
Spring_consume Spring_consume = (Spring_consume) applicationContext.getBean( "spring_consume" );
String message = (String) Spring_consume.jmsTemplate.receiveAndConvert();
System.out.println(message);
}
}
(4)主題
<!-- 主題-->
<bean id="activeMQTopic" class="org.apache.activemq.command.ActiveMQTempTopic">
<constructor-arg index="0" value="spring-active-topic"/>
</bean>
(5)Spring配置監聽器
配置消費者監聽器,配置完成後只需要啓動生產者,便可自動監聽接收消息。
<!-- 監聽器-->
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="pooledConnectionFactory"/>
<property name="destination" ref="activeMQQueue"/>
<property name="messageListener" ref="myMessageListener"/>
</bean>
package com.ldd.spring;
import org.springframework.stereotype.Component;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
/**
* Created by ldd on 2019/11/11.
*/
@Component
public class MyMessageListener implements MessageListener{
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage){
try {
String text = ((TextMessage) message).getText();
System.out.println(text + "Spring監聽消息消費");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
16、SpringBoot整合ActiveMq生產者--Queue
pom.xml文件引入activemq
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
application.yml文件
server:
port: 9999
#activeMq服務器地址
spring:
activemq:
broker-url: tcp://192.168.182.129:61616
user: admin
password: admin
#設置消息模式 false == queur true == topic 默認是false
jms:
pub-sub-domain: false
#自定義queue的名稱
myqueue: spring-boot-queue
java文件
package com.ldd.activemq.config;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
/**
* Created by ldd on 2019/12/4.
配置類
*/
@Component
@EnableJms
public class ConfigBean {
/* queue名*/
@Value( "${myqueue}" )
private String queueStr;
@Bean
public Queue queue(){
return new ActiveMQQueue( queueStr );
}
}
package com.ldd.activemq.produce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
import java.util.UUID;
/**
* Created by ldd on 2019/12/4.
生產者
*/
@Component
public class Queue_Produce {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue queue;
public void send_message(){
jmsMessagingTemplate.convertAndSend( queue,"SpringBoot-queue消息"+ UUID.randomUUID() );
System.out.println("消息發送完成");
}
}
17、SpringBoot整合ActiveMq消費者--Queue
package com.ldd.activemq.consumer;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.TextMessage;
/**
* Created by ldd on 2019/12/4.
* 消費者
*/
@Component
public class Queue_Consumer {
/**
* @JmsListener 容器自動監聽queue消息
* @param message
* @throws Exception
*/
@JmsListener( destination = "${myqueue}")
public void receiveMessage(TextMessage message) throws Exception{
System.out.println("接收消息服務器消息:"+message.getText());
}
}
18、SpringBoot整合ActiveMq生產者--Topic
server:
port: 7777
#activeMq\u670D\u52A1\u5668\u5730\u5740
spring:
activemq:
broker-url: tcp://192.168.182.129:61616
user: admin
password: admin
jms: #消息類型爲主題
pub-sub-domain: true
#\u8BBE\u7F6E\u6D88\u606F\u6A21\u5F0F false == queur true == topic \u9ED8\u8BA4\u662Ffalse
#jms:
## pub-sub-domain: false
#\u81EA\u5B9A\u4E49queue\u7684\u540D\u79F0
#myqueue: spring-boot-queue
#主題消息名
mytopic: spring-boot-topic
package com.ldd.activemq.config;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.jms.Topic;
/**
* Created by ldd on 2019/12/6.
*/
@Component
public class TopicConfigBean {
@Value( "${mytopic}" )
private String topicName;
@Bean
public Topic topic(){
return new ActiveMQTopic( topicName );
}
}
package com.ldd.activemq.produce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.jms.Topic;
import java.util.UUID;
/**
* Created by ldd on 2019/12/6.
*/
@Component
public class Topic_Produce {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Topic topic;
@Scheduled(fixedDelay = 3000)
public void sendTopicMessage(){
jmsMessagingTemplate.convertAndSend( topic,"主題消息:"+ UUID.randomUUID() );
}
}
19、SpringBoot整合ActiveMq消費者--Topic
package com.ldd.activemq.consumer;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.TextMessage;
/**
* Created by ldd on 2019/12/6.
*/
@Component
public class Topic_Consumer {
@JmsListener( destination = "${mytopic}")
public void receiveTopic(TextMessage message) throws Exception{
System.out.println("主題消息接收:"+message.getText());
}
}
20、ActiveMq傳輸協議
協議 | 描述 |
TCP | 默認協議,性能相對可以 |
NIO | 基於TCP協議之上的,進行了擴展和優化,具有更好的擴展性。 |
UDP | 性能比TCP好,但是不可靠 |
SSL | 安全鏈接 |
HTTP(S) | 基於HTTP或者HTTPS |
VM | VM本身不是協議,當客戶端和代理在同一個VM中運行時,它們之間需要通信,但不想佔用網絡通道,而是直接通信,可以使用VM |
默認配置的協議爲以下5中:openwire、amqp、stomp、mqtt、ws。
openwire默認使用的傳輸協議tcp協議
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?
maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="amqp" uri="amqp://0.0.0.0:5672?
maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?
maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?
maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="ws" uri="ws://0.0.0.0:61614?
maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
(1)TCP協議
1、這是默認的Broker配置,TCP的Client默認端口號:61616
2、網絡傳輸數據前,通過一個叫wire protocol來序列化成字節流數據。默認情況下,ActiveMq把wire protocol叫做OpenWire,它的目的是促使網絡上的效率和數據快速交互。
3、Tcp連接的URL形式:tcp://hostname:prot?key=value&key=value,後面參數可選
4、Tcp傳輸的優點:
Tcp協議傳輸可靠性高,穩定性強。
高效性:字節流方式傳輸、效率高。
有效性、可用性:應用廣泛,支持任何平臺。
(2)NIO協議
NIO協議與TCP協議類似但NIO協議更側重於底層訪問操作。它允許開發人員對同一資源可有更多的client調用和服務端有更多的負載。
使用場景:
可能有大量的Client去連接Broker上,一般情況下,大量的Client去連接Broker是被操作系統的線程限制。因此NIO的實現比TCP需要更少的線程去運行,所以建議使用NIO。
NIO連接的URL形式:nio://hostname:port?key=value
(3)amqp協議
支持版本5.8++
Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開發標準,爲面向消息的中間件設計,基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件不同產品,不同開發語言等條件限制。
(4)stomp協議
STOMP流文本定向消息協議,是一種MOM面向消息的中間件設計的簡單文本協議。
(5)MQTT協議
MQTT 消息隊列遙測傳輸是IBM開發的一個即使通訊協議,有可能成爲物聯網的重要組成部分。該協議支持所有平臺,幾乎可以把所有聯網物品和外部連接起來,被用來當做傳感器和致動器的通信協議。
(6)ws
WebSockets協議。
21、ActiveMq之NIO傳輸協議
設置支持NIO網絡IO模型,並且自動支持多協議傳輸。(默認是TCP傳輸)
(1)修改activemq.xml
(2)重啓actviemq服務
(3)生產者、消費者demo
package com.ldd.activemq.nio.produce;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/12/10.
* nio協議 生產者
*/
public class Nio_Produce {
private static final String URL = "nio://192.168.182.129:61618";
private static final String USER = "admin";
private static final String PASSWORD = "admin";
private static final String NIO_QUEUE = "nio_queue";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USER,PASSWORD,URL );
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(NIO_QUEUE);
MessageProducer producer = session.createProducer( queue );
for(int i = 0;i < 3; i ++){
TextMessage message = session.createTextMessage();
message.setText( "nio隊列消息--->"+i );
producer.send( message );
}
}
}
package com.ldd.activemq.nio.consumer;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Created by ldd on 2019/12/10.
*
* nio 消費者
*/
public class Nio_Consumer {
private static final String URL = "nio://192.168.182.129:61618";
private static final String USER = "admin";
private static final String PASSWORD = "admin";
private static final String NIO_QUEUE = "nio_queue";
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USER,PASSWORD,URL );
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(NIO_QUEUE);
MessageConsumer consumer = session.createConsumer( queue );
while(true){
TextMessage message = (TextMessage) consumer.receive();
if (message == null){
break;
}else{
System.out.println("接收到消息:->"+message.getText());
}
}
}
}
22、ActiveMQ消息持久化理論
事務、持久化消息類型、簽收都是MQ內部的持久化操作。外部持久化消息到文件或數據庫中存儲。
ActiveMQ的消息持久化機制有JDBC、AMQ、KahaDB、LevelDB,無論使用哪種持久化方式,消息的存儲邏輯都是一致的。
發送者發送消息,消息中心首先將消息存儲到本地數據文件、內存數據庫或者遠程數據庫等再試圖將消息發送給接收者,成功則將消息從存儲中刪除,失敗則繼續發送。
消息中心啓動以後首先要檢查指定的存儲位置,如果有未發送成功的消息,則把消息發送出去。
AMQ持久化:ActiveMQ5.3之前適用,AMQ是一種以文件存儲形式,它具有寫入速度快和容易被恢復的特點。消息存儲在一個文件中,文件默認大小是32M,當一個存儲文件中的消息已經被全部消息,那麼這個文件標記被可刪除。在下一個清楚階段,這個文件將被刪除。
KahaDB持久化:基於日誌文件,從ActiveMQ5.4開始默認的持久化插件。
KahaDB原理:KahaDB是目前默認的存儲方式,可用於任何場景,提高了性能和恢復能力,消息存儲使用一個事務日誌和一個索引文件來存儲它所有的地址。KahaDB是一個專門針對消息持久化的解決方案,它對典型的消息使用模型進行了優化。數據被追加到data logs中。當不在需要log文件中的數據的時候,log文件會被丟棄。
<!--
activemq.xml配置中,持久化默認使用的是kahaDB
Configure message persistence for the broker. The default persistence
mechanism is the KahaDB store (identified by the kahaDB tag).
For more information, see:
http://activemq.apache.org/persistence.html
-->
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter
1、db-<number>.log KahaDB存儲消息到數據記錄中,文件命名爲db-<number>.log。當數據文件已滿時,一個新的文件會隨之創建,number數值也會隨之遞增。當不再有引用到數據文件中的任何消息時,文件會被刪除或歸檔。
2、db.data該文件包含了持久化的BTree索引,索引了消息數據記錄中的消息,它是消息的索引文件,本質上是B-Tree(B樹),使用B-Tree作爲索引執行db-<number>.log裏面存儲的消息。
3、db.free當前db.data文件裏哪些頁面是空閒的,文件具體內容是所有空閒頁的ID。
4、db.redo用來進行消息恢復,如果KahaDB消息存儲在強制退出後啓動,用於恢復BTree索引。
5、lock文件鎖,表示當前獲得kahaDb讀寫權限的broker。
LevelDB持久化:從ActiveMQ5.8以後引進的,它和kahaDB非常相似,也是基於文件的本地數據庫存儲形式,但是它提供比KahaDB更快的持久性。但它不使用自定義B-Tree實現來索引預習日誌,而是使用基於LevelDB的索引。
23、ActiveMQ配置JDBC持久化
(1)上傳Mysql.jar包到activemq lib目錄下