目錄
消息中間件的引用場景
異步處理、應用解耦、流量削峯
JMS消息模型:
點對點模型(point to point):即生產者和消費者之間的消息來往;
發佈/訂閱模型(Pub/Sub):包含三個角色:主題(Topic),發佈者(publisher),訂閱者(subscriber),多個發佈者將消息發送到topic,系統將這些消息投遞到訂閱此Topic的訂閱者;
點對點模型的特點:
a).每個消息只有一個消費者(即一旦被消費,消息就不再消息隊列中);
b).發送者和接收者之間在時間上沒有依賴性,也就是說當發送者發送消息之後,不管接收者有沒有在運行,它不會影響消息被髮送到隊列;
c).接收者在成功接收消息之後需向隊列應帶成功;
發佈/訂閱模型的特點:
a).每個消息可以有多個消費者;
b).發佈者和訂閱者之間有時間上的依賴(消費者先訂閱主題,發佈者再來發送消息);
c).訂閱者必修保持運行的狀態,才能接收發布者發佈的消息;
核心API:
a).Destination:表示消息所走通道的名稱;
b).ConnectionFactory:用於創建連接對象;
c).Connection:連接接口,用來創建session;
d).Session:會話接口,這是一個重要的接口,消息發送者、消息接收者以及消息對象本身,都是通過這個會話創建,創建destination;
e).MessageConsumer:消息的消費者;
f).MessageProducer:消息的生產者;
使用API創建生產者
創建連接工廠
創建連接
打開鏈接
創建session會話(兩個參數:第一個是是否開啓事務,第二個消息的確認機制)
創建destination目的地
創建消息生產者
創建消息
發送消息
釋放資源
使用API創建消費者
創建連接工廠
創建連接
打開鏈接
創建session會話(兩個參數:第一個是是否開啓事務,第二個消息的確認機制)
創建distination目的地
創建消息消費者
獲取消息(使用receive方法)/設置消息監聽器來接收消息(不要釋放資源)
JMS消息組成格式
結構:
JMS Provider(消息中間件/消息服務器/消息提供者);
JMS Producer(消息生產者);
JMS Consumer(消息消費者);
JMS Message(消息----重要部分)
JMS Message組成
JMS Message消息由三部分組成:
消息頭
名稱 |
概述 |
JMSDestination |
消息發送的目的地,在發送過程中由提供者設置; |
JMSMessageID |
唯一識別每個消息的標識,由提供者產生,客戶機只能在消息發送後才能確定消息的JMSMessageID; |
JMSDeliveryMode |
消息持久化。有兩種 :持久模式(DeliveryMode.PERSISTENT)和非持久模式(DeliveryMode.NON_PERSISTENT); |
JMSTimestamp |
提供者發送消息的時間,由提供者在發送過程中設置; |
JMSExpiration |
消息過期時間,毫秒單位,值0表明消息不會過期,默認值爲0; |
JMSPriority |
消息優先級,從 0-9 十個級別,0最低,9最高,0-4 是普通消息,5-9 是加急消息。ActiveMQ不要求提供者嚴格按照這十個優先級發送消息,但必須保證加急消息要先於普通消息發送,默認是4級; |
JMSCorrelationID |
用來連接到另外一個消息,典型的應用是在回覆消息中連接到原消息。在大多數情況下,JMSCorrelationID用於將一條消息標記爲對JMSMessageID標示的上一條消息的應答,不過,JMSCorrelationID可以是任何值,不僅僅是JMSMessageID,由開發者設置 |
JMSReplyTo |
提供本消息回覆消息的目的地址,由開發者設置 |
JMSType |
消息類型的識別符,由開發者設置 |
JMSRedelivered |
如果一個客戶端收到一個設置了JMSRedelivered屬性的消息,則表示可能客戶端曾經在早些時候收到過該消息,但並沒有簽收(acknowledged)。消息的重發標識,false,代表第一次發送,true,代表消息爲重發消息 |
只有JMSCorrelationID、JMSReplyTo、JMSType可以由開發者設置的
消息體
消息體,JMS API定義了5種消息體格式,也叫消息類型,可以使用不同形式發送接收數據,並可以兼容現有的消息格式。包括:
TextMessage(字符串)
MapMessage(鍵值對)
BytesMessage(字節的數據流)
ObjectMessage(序列化的對象)
StreamMessage(java原始值的數據流,可以傳遞字符串,整型等不同類型的數據,數據生產與消費順序要一致)
注意:ActiveMQ5.12後,爲了安全考慮,ActiveMQ默認不接受自定義的序列化對象,需要將自定義的對象加入到受信任的列表
消息屬性
消息屬性,包含以下三種類型的屬性:
1:應用程序自定義設置和添加的屬性,比如:Message.setStringProperty("hello", "my name is wangsaichao!");
2:JMS定義的屬性:使用“JMSX”作爲屬性名的前綴,connection.getMetaData().getJMSXPropertyNames(), 方法返回所有連接支持的JMSX 屬性的名字。
3:JMS供應商特定的屬性:JMS定義的屬性如下:
1:JMSXUserID:發送消息的用戶標識,發送時提供商設置
2:JMSXAppID:發送消息的應用標識,發送時提供商設置
3:JMSXDeliveryCount:轉發消息重試次數,第一次是1,第二次是2,… ,發送時提供商設置
4:JMSXGroupID:消息所在消息組的標識,由客戶端設置
5:JMSXGroupSeq:組內消息的序號第一個消息是1,第二個是2,…,由客戶端設置
6:JMSXProducerTXID :產生消息的事務的事務標識,發送時提供商設置
7:JMSXConsumerTXID :消費消息的事務的事務標識,接收時提供商設置
8:JMSXRcvTimestamp :JMS 轉發消息到消費者的時間,接收時提供商設置
9:JMSXState:假定存在一個消息倉庫,它存儲了每個消息的單獨拷貝,且這些消息從原始消息被髮送時開始。每個拷貝的狀態有:1(等待),2(準備),3(到期)或4(保留)。由於狀態與生產者和消費者無關,所以它不是由它們來提供。它只和在倉庫中查找消息相關,因此JMS沒有提供這種API。由提供商設置
ActiveMQ的高級特性
ActiveMQ消息持久化
消息持久化是保證消息不丟失的重要方式。
ActiveMQ提供了一下三種的消息存儲方式
- Memory消息存儲-基於內存的消息存儲;
- 基於日誌消息存儲方式,KahaDB是ActiveMQ的默認日誌存儲方式,它提供了容量和恢復能力;
- 基於JDBC的消息存儲方式-數據存儲於數據庫中(例如:Mysql);
ActiveMQ持久化機制流程:默認是基於日誌消息存儲方式
ActiveMQ基於JDBC消息存儲(springBoot)
a).修改application.yml文件:
spring:
activemq:
broker-url: tcp://192.168.1.129:61616
user: admin
password: admin
jms:
template:
delivery-mode:
b).修改ActiveMQ/conf下的activemq.xml文件:
<bean name="mysql-ds" class="com.alibaba.druid.pool.DruidDataSource" destory-method="close">
<property name="driverClassName" valuue="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.1.49:3306/test" />
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>
c).拷貝mysql及druid數據源的jar包到activemq/lib目錄下
d).重啓activemq
消息事務
消息事務,是保證消息傳遞原子性的一個重要特徵,和JDBC的事務特徵類似;
一個事務性發送,其中一組(比如:5條消息爲一組)消息要麼能夠全部保證到達服務器,要麼都不到達服務器;
生產者、消費者與消息服務器直接都支持事務性;
ActiveMQ的事務主要偏向在生產者的應用;
ActiveMQ消息事務流程圖:
實現方式(使用SpringBoot):使用原生JMS,使用spring的JmsTransactionManager
方式一:使用原生JMS
生產者:
@Test
public void ptpProducerWithTx() {
Session session = null;
try {
//獲取連接工廠
ConnectionFactory connectionFactory = jmsMessagingTemplate.getConnectionFactory();
//創建連接
Connection connection = connectionFactory.createConnection();
//打開連接
connection.start();
/**
* 參數一: 是否開啓消息事務
* 參數二:消息的確認機制
* Session.AUTO_ACKNOWLEDGE 自動確認
* Session.CLIENT_ACKNOWLEDGE
* Session.DUPS_OK_ACKNOWLEDGE
* Session.SESSION_TRANSACTED
*/
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//創建目的地
Destination destination = session.createQueue(name);
//創建生產者
MessageProducer producer = session.createProducer(destination);
for (int i = 1; i <= 10; i++) {
//異常模擬
//if (i == 4) {
// int a = 10 / 0;
//}
//創建消息
TextMessage textMessage = session.createTextMessage("springboot-activemq-with-tx-message" + i);
//發送消息
producer.send(destination, textMessage);
}
//注意:一旦開啓事務發送,那麼就必修使用commint方法進行事務的提交,否則消息無法到達MQ服務器
session.commit();
System.out.println("消息發送成功");
} catch (Exception e) {
e.printStackTrace();
//事務的回滾
try {
session.rollback();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
消費者:
@JmsListener(destination = "${activemq.name}")
public void receiveMessage(Message message, Session session) {
if (message instanceof TextMessage) {
TextMessage objectMessage = (TextMessage) message;
try {
String object = objectMessage.getText();
System.out.println(name + " 接收消息:" + object);
//模擬異常
// int a = 10/0;
//提交事務
session.commit();
} catch (JMSException e) {
e.printStackTrace();
//消息回滾
try {
session.rollback();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
}
方式二:使用spring的JmsTransactionManager
生產者:
配置ActiveMQ的事務管理器
@Configuration
public class ActiveMQConfig {
/**
* 添加ActiveMQ事務管理器配置
*/
@Bean
public PlatformTransactionManager createPlatformTransactionManager(ConnectionFactory connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
}
模擬消息發送業務
@Service
public class MessageService {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Value("${activemq.name}")
private String name;
@Transactional //對消息發送加入事務管理(同事也對JDBC數據庫的事務生效)
public void sendMessage() {
for (int i = 1; i <= 10; i++) {
//異常模擬
//if (i == 4) {
// int a = 10 / 0;
//}
jmsMessagingTemplate.convertAndSend(name, "springboot-activemq-no-tx-message" + i);
}
}
}
發送消息:
@Autowired
private MessageService messageService;
/**
* 加入事務的消息發送--方案二:spring的JmsTransactionManager功能(JMS事務管理器)
*/
@Test
public void ptpProducerWithTx2() {
messageService.sendMessage();
}
消費者:同方案一的消費者
消息確認機制
JMS消息只有在被確認之後,才認爲已經被成功地消費了。消息的成功消費通常包含三個階段:客戶接受消息、客戶處理消息和消息被確認。在事務性會話中,當一個事務被提交的時候,確認自動發生。在非事務性會話中,消息何時被確認取決於創建會話時的應答模式(acknowledgement mode)。該參數有以下三個可選值(只在非事務下生效,事務下只有自動確認 ):
值 |
描述 |
Session.AUTO_ACKNOWLEDGE = 1 (自動確認) |
當客戶成功的從receive方法返回的時候,或者從MessageListener.onMessage方法成功返回的時候,會話自動確認客戶收到的消息。 |
Session.CLIENT_ACKNOWLEDGE = 2 (客戶端手動確認) |
客戶通過消息的Message.acknowledge方法確認消息。需要注意的是,在這種模式中,確認實在會話層上進行:確認一個被消費的消息將自動確認所有已被會話消費的消息。例如,如果一個消息消費者消費了10個消息,然後確認第5個消息,那麼所有10個消息都被確認。 |
Session.DUPS_OK_ACKNOWLEDGE = 3 (自動批量確認/延遲確認) |
該選擇只是會話遲鈍確認消息的提交。如果JMS provider失敗,那麼可能會導致一些重複的消息。如果是重複的消息,那麼JMS provider必修把消息頭的JMSRedelivered字段設置爲true。 |
Session.SESSION_TRANSACTED=0 (事務提交併確認) |
|
INDIVIDUAL_ACKNOWLEDGE = 4 (單條消息確認) |
AcitveMQ補充了一個自定義的ACK_MODE,只有ActiveMQ支持,當然開發者也可以使用它 |
注意:消息確認機制與事務機制是衝突的,只能選其中一種,所以演示消息確認前,先關閉事務
Client端指定了ACK_MODE,但是在Client與broker在交換ACK指令的時候,還需要告知ACK_TYPE,ACK_TYPE表示此確認指令的類型,不同的ACK_TYPE將傳遞着消息的狀態,broker可以根據不同的ACK_TYPE對消息進行不同的操作。
ActiveMQ中定義瞭如下幾種ACK_TYPE(參看MessageAck類):
DELIVERED_ACK_TYPE = 0 消息"已接收",但尚未處理結束;
STANDARD_ACK_TYPE = 2 "標準"類型,通常表示爲消息"處理成功",broker端可以刪除消息了;
POSION_ACK_TYPE = 1 消息"錯誤",通常表示"拋棄"此消息,比如消息重發多次後,都無法正確處理時,消息將會被刪除或者DLQ(死信隊列);
REDELIVERED_ACK_TYPE = 3 消息需"重發",比如consumer處理消息時拋出了異常,broker稍後會重新發送此消息;
INDIVIDUAL_ACK_TYPE = 4 表示只確認"單條消息",無論在任何ACK_MODE下;
UNMATCHED_ACK_TYPE = 5 BROKER間轉發消息時,接收端"拒絕"消息;
消息投遞方式
消息投遞方式包含三種方式:異步投遞、延遲投遞、定時投遞
1、異步投遞 VS 同步投遞
同步發送:消息生產者使用持久(Persistent)傳遞模式發送消息的時候,Producer.send()方法會被阻塞,直到broker發送一個確認消息給生產者(ProducerAck),這個確認消息按時broker已經成功接收到消息並把消息保存到二級存儲中。
異步發送:如果應用程序能夠容忍一些消息的丟失,那麼可以使用異步發送。異步發送不會在收到broker的確認之前一直阻塞Producer.send()方法。
想要使用異步,在brokerUrl中增加jms.alwaysSyncSend=false&jms.userAsyncSend=true屬性
a).如果設置了alwaysSyncSend=true(同步)系統會忽略userAsyncSend設置的值都採用同步;
b).當alwaysSyncSend=false(異步)時,“NON_PERSISTENT”(非持久化)、事務中的消息將使用“異步發送”;
c).當alwaysSyncSend=false(異步)時,如果指定userAsyncSend=true(異步),“PERSISTENT”(持久化)類型的消息使用異步發送。如果userAsyncSend=false(同步),“PERSISTENT”(持久化)類型的消息使用同步發送;
總結:默認情況(alwaysSyncSend=false,userAsyncSend=false),非持久化消息、事務內的消息均採用異步發送;對於持久化消息採用同步發送!
沒有使用事務並且正在發送持久性消息,則每個發送都是同步並阻塞
配置異步投遞方式
方式一(原生JMS):
a).在連接上配置
new ActiveMQConnectionFactory("tcp://192.168.1.128:61616?jms.userAsyncSend=true");
b).通過ConnectionFactory
((ActiveMQConnectionFactory)connectionFactory).setUseAsyncSend(true);
c).通過connection
((ActiveMQConnection) connection).setUseAsyncSend(true);
注意:如果是Spring或者SpringBoot項目,通過修改JmsTemplate的默認參數實現異步或同步投遞
方式二:SpringBoot的配置
/**
* 配置用於異步發送的非持久化JmsTemplate
*/
@Autowired
@Bean
public JmsTemplate asyncJmsTemplate(ConnectionFactory connectionFactory){
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
jmsTemplate.setExplicitQosEnabled(true);//deliveryMode, priority, timeToLive 的開關
jmsTemplate.setDeliveryMode(DeliveryMode.NON_PERSISTENT);//非持久化
return jmsTemplate;
}
/**
* 配置用於同步發送的非持久化JmsTemplate
* 默認爲同步
*/
@Autowired
@Bean
public JmsTemplate syncJmsTemplate(ConnectionFactory connectionFactory){
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
return jmsTemplate;
}
異步投遞如何確認發送成功?
異步發送丟失場景:生產者設置了useAsyncSend=true(異步),使用producer.send(message)持續發送消息。由於消息不阻塞,生產者會認爲所有send的消息都被成功發送到MQ了。如果此時MQ突然宕機,生產者端內存中尚未發送至MQ的消息就會丟失。
這時,可以給異步投遞方法接收回調函數,已確認消息是否發送成功。
@Value("${activemq.name}")
private String name;
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
* 測試異步/同步投遞
*/
@Test
public void asyncAndSyncSend() {
Session session = null;
try {
//獲取連接工廠
ActiveMQConnectionFactory connectionFactory = (ActiveMQConnectionFactory) jmsMessagingTemplate.getConnectionFactory();
// 設置消息發送模式是AsyncSend模式,異步模式
connectionFactory.setUseAsyncSend(true);
//創建連接
Connection connection = connectionFactory.createConnection();
//打開連接
connection.start();
/**
* 參數一: 是否開啓消息事務
* 參數二:消息的確認機制
* Session.AUTO_ACKNOWLEDGE 自動確認
* Session.CLIENT_ACKNOWLEDGE
* Session.DUPS_OK_ACKNOWLEDGE
* Session.SESSION_TRANSACTED
*/
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//創建目的地
Destination destination = session.createQueue(name);
//創建生產者
ActiveMQMessageProducer producer = (ActiveMQMessageProducer) session.createProducer(destination);
//非持久化
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
for (int i = 1; i <= 10; i++) {
//創建消息
TextMessage textMessage = session.createTextMessage("springboot-activemq-with-async-message" + i);
//設置消息唯一的ID
String msgId = UUID.randomUUID().toString().replaceAll("-", "");
//這一步沒有效果,因爲即使開發者設置值,但是消息提供者會自己生成一個id,該id使用來記錄本地發送失敗的數據的
textMessage.setJMSMessageID(msgId);
//發送消息
producer.send(textMessage, new AsyncCallback() {
@Override
public void onSuccess() {
//使用msgId標識來進行消息發送成功的處理
System.out.println("msgId:" + msgId);
}
@Override
public void onException(JMSException exception) {
//使用msgId標識來進行消息發送失敗的處理
System.out.println("msgId:" + msgId + "發送失敗");
exception.printStackTrace();
}
});
}
//注意:一旦開啓事務發送,那麼就必修使用commint方法進行事務的提交,否則消息無法到達MQ服務器
session.commit();
System.out.println("消息發送成功");
} catch (Exception e) {
e.printStackTrace();
//事務的回滾
try {
session.rollback();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
2、延遲投遞
生產者提供兩個發送消息的方法,一個是即使發送消息,一個是延時發送消息。
步驟如下:
1、修改activemq.xml
注意:修改activemq/conf目錄下的activemq.xml文件中的broker標籤,添加schedulerSupport=”true”配置 開啓延時投遞,重啓activemq;
<broker xmlns="http://activemq.apache.org/schema/core"
brokerName="localhost" dataDirectory="${activemq.data}"
schedulerSupport="true" />
2、在代碼中設置延時時長
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
* 延時投遞
*/
@Test
public void lazySendMessage() {
Session session = null;
try {
//獲取連接工廠
ConnectionFactory connectionFactory = jmsMessagingTemplate.getConnectionFactory();
//創建連接
Connection connection = connectionFactory.createConnection();
//打開連接
connection.start();
/**
* 參數一: 是否開啓消息事務
* 參數二:消息的確認機制
* Session.AUTO_ACKNOWLEDGE 自動確認
* Session.CLIENT_ACKNOWLEDGE
* Session.DUPS_OK_ACKNOWLEDGE
* Session.SESSION_TRANSACTED
*/
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//創建目的地
Destination destination = session.createQueue(name);
//創建生產者
ActiveMQMessageProducer producer = (ActiveMQMessageProducer) session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);//非持久化
//創建消息
TextMessage textMessage = session.createTextMessage("springboot-activemq-with-tx-message");
//設置延時時長(延時10s)
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 10000);
//發送消息
producer.send(destination, textMessage);
//注意:一旦開啓事務發送,那麼就必修使用commint方法進行事務的提交,否則消息無法到達MQ服務器
session.commit();
System.out.println("消息發送成功");
} catch (Exception e) {
e.printStackTrace();
//事務的回滾
try {
session.rollback();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
ScheduledMessage投遞的四個屬性
屬性 |
描述 |
ScheduledMessage.AMQ_SCHEDULED_DELAY |
延遲投遞的時間 |
ScheduledMessage.AMQ_SCHEDULED_PERIOD |
重複投遞的時間間隔 |
ScheduledMessage.AMQ_SCHEDULED_REPEAT |
重複投遞次數 |
ScheduledMessage.AMQ_SCHEDULED_CRON |
Cron表達式 |
定時投遞
步驟如下:
1、啓動類添加定時註解
@SpringBootApplication
@EnableScheduling //開啓定時任務
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
2、在生產者添加@Scheduled設置定時
/**
* 定時任務消息發送
*/
@Component
public class Producer {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Value("${activemq.name}")
private String name;
/**
* 定時發送消息
*/
@Scheduled(fixedDelay = 3000)
public void sendMessage() {
jmsMessagingTemplate.convertAndSend(name, "springboot-activemq-scheduled");
}
}
死信隊列
DLQ-死信隊列(Dead Letter Queue)用來保存處理失敗或者過期的消息。
出現以下情況時,消息會被redelivered(重新交付):
A transacted session is used and rollback() is called.
A transacted session is closed before commit() is called.
A session is using CLIENT_ACKNOWLEDGE and Session.recover() is called.
A client connection times out (perhaps the code being executed takes longer than the configured time-out period).
(
1.事務會話中,當還未進行session.commit()時,進行session.rollback(),那麼所有還沒commit的消息都會進行重發。
2.所有未ack的消息,在沒有commint()之前進行session.closed()關閉事務,那麼所有還沒ack的消息broker端都會進行重發,而且是馬上重發。
3.使用客戶端手動確認的方式時(非事務會話),還未進行確認並且執行Session.recover(),那麼所有還沒acknowledge的消息都會進行重發。
4.消息被消費者拉取之後,超時沒有響應ack,消息會被broker重發。
)
當一個消息被redelivered超過maximumRedeliveries(缺省爲6次)次數時,會給broker發送一個" poison_ack",這個消息被認爲是a poison pill,這時broker會將這個消息發送到DLQ,以便後續處理。
注意兩點:
1、缺省持久消息過期,會被送到DLQ,非持久消息不會送到DLQ
2、缺省的死信隊列是ActiveMQ.DLQ,如果沒有特別指定,死信都會被髮送到這個隊列。
可以通過配置文件(activemq.xml)來調整死信發送策略,方案如下:
官網鏈接:http://activemq.apache.org/message-redelivery-and-dlq-handling
- (方案一)爲每個隊列創建死信隊列
修改activemq/conf目錄下的activemq.xml文件:
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue=">" >
<!--爲每個隊列建立死信隊列 -->
<deadLetterStrategy>
<individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="true"/>
</deadLetterStrategy>
</policyEntry>
<policyEntry topic=">" >
<!--死信隊列的默認配置 -->
<pendingMessageLimitStrategy>
<constantPendingMessageLimitStrategy limit="1000"/>
</pendingMessageLimitStrategy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
2、(方案二)RedeliveryPolicy重發策略設置
在消費者方配置如下:
@Configuration
public class ActiveMQConfig {
/**
* RedeliveryPolicy
*/
@Bean
public RedeliveryPolicy redeliveryPolicy() {
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
//是否在每次嘗試重新發送失敗後,增長這個等待時間
redeliveryPolicy.setUseExponentialBackOff(true);
//重發次數,默認爲6次,這裏設置爲10次
redeliveryPolicy.setMaximumRedeliveries(10);
//重發時間間隔,默認爲1s
redeliveryPolicy.setInitialRedeliveryDelay(2000);
//第一次失敗後重新發送之前等待500毫秒,第二次失敗再等待500*2毫秒,第三次500*2*2,這裏的2就是value
redeliveryPolicy.setBackOffMultiplier(2);
//是否避免消息碰撞
redeliveryPolicy.setUseCollisionAvoidance(false);
//設置重發最大拖延時間,-1表示沒有拖延只有UseExponentialBackOff(true)爲true時生效
//當重連時間間隔大的最大重連時間間隔時,以後每次重連時間間隔都爲最大重連時間間隔。
redeliveryPolicy.setMaximumRedeliveryDelay(-1);
return redeliveryPolicy;
}
@Bean
public ActiveMQConnectionFactory activeMQConnectionFactory(@Value("${spring.activemq.broker-url}") String url, RedeliveryPolicy redeliveryPolicy) {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory("admin", "admin", url);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
return activeMQConnectionFactory;
}
/**
* 添加ActiveMQ事務管理器配置
*/
@Bean
public PlatformTransactionManager createPlatformTransactionManager(ConnectionFactory connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
/**
* 消費者 消息確認機制配置
*
* @param connectionFactory
* @return
*/
@Bean("jmsListenerContainerFactory")
public DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory(ConnectionFactory connectionFactory, PlatformTransactionManager platformTransactionManager) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);//連接工廠
factory.setTransactionManager(platformTransactionManager);//事務管理器
//是否關閉事務,開啓事務
factory.setSessionTransacted(true);
//修改消息確認機制
//自動確認
factory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
return factory;
}
}
@Component
public class MyListener {
@Value("${activemq.name}")
private String name;
private int count = 0;
/**
* 消費方事務的配置,使用session形參控制
*
* @param message
* @param session
*/
@JmsListener(destination = "${activemq.name}",containerFactory = "jmsListenerContainerFactory")
public void receiveMessage(Message message, Session session) {
if (message instanceof TextMessage) {
TextMessage objectMessage = (TextMessage) message;
try {
String object = objectMessage.getText();
System.out.println("重複提交次數count:" + (++count));
System.out.println(name + " 接收消息:" + object);
//模擬異常
int a = 10 / 0;
//提交事務
session.commit();
} catch (JMSException e) {
e.printStackTrace();
//消息回滾
try {
session.rollback();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
}
}
其他方案:https://www.cnblogs.com/rainwang/p/5146223.html
包括:
非持久消息保存到死信隊列
<deadLetterStrategy>
<sharedDeadLetterStrategy processNonPersistent="true"/>
</deadLetterStrategy>
過期消息不保存到死信隊列
<deadLetterStrategy>
<sharedDeadLetterStrategy processExpired="false" />
</deadLetterStrategy>
持久消息不保存到死信隊列
ActiveMQ企業經典面試問題
ActiveMQ宕機了怎麼辦?
- ActiveMQ主從集羣方案:Zookeeper集羣+Replicate LevelDB(kahadb)+ActiveMQ集羣
官網鏈接:http://activemq.apache.org/replicated-leveldb-store
(LevelDB存儲已被棄用,不再受支持或建議使用。推薦的store是KahaDB)
如何防止消費者消費重複消息?
如果因爲網絡延遲等原因,MQ無法及時接收到消息方的應答,導致MQ重試。在重試過程中造成重複消費的問題。
解決思路:
- 如果消費方是做數據庫操作,那麼可以報消息的ID作爲表的唯一主鍵或者唯一約束,這樣在重試的情況下,會觸發衝突,從而避免數據出現髒數據。
- 如果消費方不是數據庫操作,那麼可以藉助第三方的應用,例如redis,來記錄消費記錄。每次消費被消費完成的時候,把當前消息的ID作爲key存入redis,每次存入redis,每次消費前,先到redis查詢有沒有該消息的消費者記錄。
如何防止消息丟失?
以下手段可以防止消息丟失:
- 在消息生產者和消費者使用事務;
- 在消費者採用手動消息確認(ACK)
- 消息持久化,例如JDBC或kahadb
什麼是死信隊列?
DLQ-死信隊列(Dead Letter Queue)用來保存處理失敗或者過期的消息。