在使用mq作爲中間件做異步消息推送時,可能會遇到一個場景,就是消息在消費後執行一系列的邏輯到一半,突然遇到異常或者是斷電等之類問題,這時消息在mq的隊列中已經出隊列,而正常邏輯沒有執行完就異常終止,這樣就可能會造成數據的缺失和數據的不完整,如何解決這個問題?其實挺簡單的,就是在消息進入消費者消費的同時做一個記錄,再在邏輯執行完成後再刪除這條記錄或者是改變這條記錄的狀態,同時,在項目初始化時或者是執行一個定時任務掃描這個記錄表,如果存在則產生一條相同的記錄發送到activemq中(相同的隊列)。這樣就能解決這個問題。然而,今天在開發中我又思考到一個問題,如圖:
假設消息1沒有消費完成,則消息1要重新進入隊列,此時有一條消息4跟消息1操作的是同一條數據,如果消息1再進入隊列,則4會在1之前消費掉,此時數據就會發生錯亂,如果要保證消息1在消息4之前消費,則就需要對重新入隊列的消息1進行一些操作來使得消息1優先消費。
activemq是提供對隊列設置爲具有優先級消息屬性的功能,那麼下面就要來實現具有優先級屬性的消息隊列:
第一步,進入activemq的conf文件夾下,修改activemq.xml配置文件,在<policyEntries>標籤下插入一個配置:
<policyEntry queue="queue1" strictOrderDispatch="true" useCache="false" queuePrefetch="1" prioritizedMessages="true" />
完成後保存,並重啓mq服務。這條配置就配置了對於queue1這個隊列內的消息是具有優先級的屬性的。
第二步,整合一個springboot+activemq的demo項目用於測試。
第三步,就是具體的代碼實現了,首先我們來關注下springboot整合activemq的源碼:
1.JmsMessagingTemplate這個類是實現發送消息的主要類,正常我們只需要在producer中注入這個類,調用其中發送消息的方法就能夠實現消息的發送。具體代碼如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import javax.jms.Destination;
@Service
public class TestProducer {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
public void sendMsg(Destination destination, String text){
jmsMessagingTemplate.convertAndSend(destination,text);
}
}
但是,此消息是不具有優先級的,接着往下看源碼。
2.在JmsMessagingTemplate類中實際上是封裝了一個JmsTemplate這個類,實際上這個類纔是真正實現消息發送的類,它只是將它進一步封裝而已。
package org.springframework.jms.core;
import java.util.Map;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Session;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jms.InvalidDestinationException;
import org.springframework.jms.JmsException;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessagingMessageConverter;
import org.springframework.jms.support.destination.DestinationResolutionException;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.core.AbstractMessagingTemplate;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.util.Assert;
public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination> implements JmsMessageOperations, InitializingBean {
@Nullable
private JmsTemplate jmsTemplate;
private MessageConverter jmsMessageConverter = new MessagingMessageConverter();
private boolean converterSet;
@Nullable
private String defaultDestinationName;
public JmsMessagingTemplate() {
}
public JmsMessagingTemplate(ConnectionFactory connectionFactory) {
this.jmsTemplate = new JmsTemplate(connectionFactory);
}
public JmsMessagingTemplate(JmsTemplate jmsTemplate) {
Assert.notNull(jmsTemplate, "JmsTemplate must not be null");
this.jmsTemplate = jmsTemplate;
}
public void setConnectionFactory(ConnectionFactory connectionFactory) {
if (this.jmsTemplate != null) {
this.jmsTemplate.setConnectionFactory(connectionFactory);
} else {
this.jmsTemplate = new JmsTemplate(connectionFactory);
}
}
@Nullable
public ConnectionFactory getConnectionFactory() {
return this.jmsTemplate != null ? this.jmsTemplate.getConnectionFactory() : null;
}
public void setJmsTemplate(@Nullable JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
@Nullable
public JmsTemplate getJmsTemplate() {
return this.jmsTemplate;
}
public void setJmsMessageConverter(MessageConverter jmsMessageConverter) {
Assert.notNull(jmsMessageConverter, "MessageConverter must not be null");
this.jmsMessageConverter = jmsMessageConverter;
this.converterSet = true;
}
public MessageConverter getJmsMessageConverter() {
return this.jmsMessageConverter;
}
public void setDefaultDestinationName(@Nullable String defaultDestinationName) {
this.defaultDestinationName = defaultDestinationName;
}
@Nullable
public String getDefaultDestinationName() {
return this.defaultDestinationName;
}
public void afterPropertiesSet() {
Assert.notNull(this.jmsTemplate, "Property 'connectionFactory' or 'jmsTemplate' is required");
if (!this.converterSet && this.jmsTemplate.getMessageConverter() != null) {
((MessagingMessageConverter)this.jmsMessageConverter).setPayloadConverter(this.jmsTemplate.getMessageConverter());
}
}
private JmsTemplate obtainJmsTemplate() {
Assert.state(this.jmsTemplate != null, "No JmsTemplate set");
return this.jmsTemplate;
}
public void send(Message<?> message) {
Destination defaultDestination = (Destination)this.getDefaultDestination();
if (defaultDestination != null) {
this.send(defaultDestination, message);
} else {
this.send(this.getRequiredDefaultDestinationName(), message);
}
}
public void convertAndSend(Object payload) throws MessagingException {
this.convertAndSend((Object)payload, (MessagePostProcessor)null);
}
public void convertAndSend(Object payload, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
Destination defaultDestination = (Destination)this.getDefaultDestination();
if (defaultDestination != null) {
this.convertAndSend((Object)defaultDestination, payload, (MessagePostProcessor)postProcessor);
} else {
this.convertAndSend(this.getRequiredDefaultDestinationName(), payload, postProcessor);
}
}
public void send(String destinationName, Message<?> message) throws MessagingException {
this.doSend(destinationName, message);
}
public void convertAndSend(String destinationName, Object payload) throws MessagingException {
this.convertAndSend(destinationName, payload, (Map)null);
}
public void convertAndSend(String destinationName, Object payload, @Nullable Map<String, Object> headers) throws MessagingException {
this.convertAndSend(destinationName, payload, headers, (MessagePostProcessor)null);
}
public void convertAndSend(String destinationName, Object payload, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
this.convertAndSend(destinationName, payload, (Map)null, postProcessor);
}
public void convertAndSend(String destinationName, Object payload, @Nullable Map<String, Object> headers, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
Message<?> message = this.doConvert(payload, headers, postProcessor);
this.send(destinationName, message);
}
@Nullable
public Message<?> receive() {
Destination defaultDestination = (Destination)this.getDefaultDestination();
return defaultDestination != null ? this.receive(defaultDestination) : this.receive(this.getRequiredDefaultDestinationName());
}
@Nullable
public <T> T receiveAndConvert(Class<T> targetClass) {
Destination defaultDestination = (Destination)this.getDefaultDestination();
return defaultDestination != null ? this.receiveAndConvert(defaultDestination, targetClass) : this.receiveAndConvert(this.getRequiredDefaultDestinationName(), targetClass);
}
@Nullable
public Message<?> receive(String destinationName) throws MessagingException {
return this.doReceive(destinationName);
}
@Nullable
public <T> T receiveAndConvert(String destinationName, Class<T> targetClass) throws MessagingException {
Message<?> message = this.doReceive(destinationName);
return message != null ? this.doConvert(message, targetClass) : null;
}
@Nullable
public Message<?> sendAndReceive(Message<?> requestMessage) {
Destination defaultDestination = (Destination)this.getDefaultDestination();
return defaultDestination != null ? this.sendAndReceive(defaultDestination, requestMessage) : this.sendAndReceive(this.getRequiredDefaultDestinationName(), requestMessage);
}
@Nullable
public Message<?> sendAndReceive(String destinationName, Message<?> requestMessage) throws MessagingException {
return this.doSendAndReceive(destinationName, requestMessage);
}
@Nullable
public <T> T convertSendAndReceive(String destinationName, Object request, Class<T> targetClass) throws MessagingException {
return this.convertSendAndReceive(destinationName, request, (Map)null, (Class)targetClass);
}
@Nullable
public <T> T convertSendAndReceive(Object request, Class<T> targetClass) {
return this.convertSendAndReceive((Object)request, (Class)targetClass, (MessagePostProcessor)null);
}
@Nullable
public <T> T convertSendAndReceive(String destinationName, Object request, @Nullable Map<String, Object> headers, Class<T> targetClass) throws MessagingException {
return this.convertSendAndReceive(destinationName, request, headers, targetClass, (MessagePostProcessor)null);
}
@Nullable
public <T> T convertSendAndReceive(Object request, Class<T> targetClass, @Nullable MessagePostProcessor postProcessor) {
Destination defaultDestination = (Destination)this.getDefaultDestination();
return defaultDestination != null ? this.convertSendAndReceive((Object)defaultDestination, request, (Class)targetClass, (MessagePostProcessor)postProcessor) : this.convertSendAndReceive(this.getRequiredDefaultDestinationName(), request, targetClass, postProcessor);
}
@Nullable
public <T> T convertSendAndReceive(String destinationName, Object request, Class<T> targetClass, @Nullable MessagePostProcessor requestPostProcessor) throws MessagingException {
return this.convertSendAndReceive(destinationName, request, (Map)null, targetClass, requestPostProcessor);
}
@Nullable
public <T> T convertSendAndReceive(String destinationName, Object request, @Nullable Map<String, Object> headers, Class<T> targetClass, @Nullable MessagePostProcessor postProcessor) {
Message<?> requestMessage = this.doConvert(request, headers, postProcessor);
Message<?> replyMessage = this.sendAndReceive(destinationName, requestMessage);
return replyMessage != null ? this.getMessageConverter().fromMessage(replyMessage, targetClass) : null;
}
protected void doSend(Destination destination, Message<?> message) {
try {
this.obtainJmsTemplate().send(destination, this.createMessageCreator(message));
} catch (JmsException var4) {
throw this.convertJmsException(var4);
}
}
protected void doSend(String destinationName, Message<?> message) {
try {
this.obtainJmsTemplate().send(destinationName, this.createMessageCreator(message));
} catch (JmsException var4) {
throw this.convertJmsException(var4);
}
}
@Nullable
protected Message<?> doReceive(Destination destination) {
try {
javax.jms.Message jmsMessage = this.obtainJmsTemplate().receive(destination);
return this.convertJmsMessage(jmsMessage);
} catch (JmsException var3) {
throw this.convertJmsException(var3);
}
}
@Nullable
protected Message<?> doReceive(String destinationName) {
try {
javax.jms.Message jmsMessage = this.obtainJmsTemplate().receive(destinationName);
return this.convertJmsMessage(jmsMessage);
} catch (JmsException var3) {
throw this.convertJmsException(var3);
}
}
@Nullable
protected Message<?> doSendAndReceive(Destination destination, Message<?> requestMessage) {
try {
javax.jms.Message jmsMessage = this.obtainJmsTemplate().sendAndReceive(destination, this.createMessageCreator(requestMessage));
return this.convertJmsMessage(jmsMessage);
} catch (JmsException var4) {
throw this.convertJmsException(var4);
}
}
@Nullable
protected Message<?> doSendAndReceive(String destinationName, Message<?> requestMessage) {
try {
javax.jms.Message jmsMessage = this.obtainJmsTemplate().sendAndReceive(destinationName, this.createMessageCreator(requestMessage));
return this.convertJmsMessage(jmsMessage);
} catch (JmsException var4) {
throw this.convertJmsException(var4);
}
}
private JmsMessagingTemplate.MessagingMessageCreator createMessageCreator(Message<?> message) {
return new JmsMessagingTemplate.MessagingMessageCreator(message, this.getJmsMessageConverter());
}
protected String getRequiredDefaultDestinationName() {
String name = this.getDefaultDestinationName();
if (name == null) {
throw new IllegalStateException("No 'defaultDestination' or 'defaultDestinationName' specified. Check configuration of JmsMessagingTemplate.");
} else {
return name;
}
}
@Nullable
protected Message<?> convertJmsMessage(@Nullable javax.jms.Message message) {
if (message == null) {
return null;
} else {
try {
return (Message)this.getJmsMessageConverter().fromMessage(message);
} catch (Exception var3) {
throw new MessageConversionException("Could not convert '" + message + "'", var3);
}
}
}
protected MessagingException convertJmsException(JmsException ex) {
if (!(ex instanceof DestinationResolutionException) && !(ex instanceof InvalidDestinationException)) {
return (MessagingException)(ex instanceof org.springframework.jms.support.converter.MessageConversionException ? new MessageConversionException(ex.getMessage(), ex) : new MessagingException(ex.getMessage(), ex));
} else {
return new org.springframework.messaging.core.DestinationResolutionException(ex.getMessage(), ex);
}
}
private static class MessagingMessageCreator implements MessageCreator {
private final Message<?> message;
private final MessageConverter messageConverter;
public MessagingMessageCreator(Message<?> message, MessageConverter messageConverter) {
this.message = message;
this.messageConverter = messageConverter;
}
public javax.jms.Message createMessage(Session session) throws JMSException {
try {
return this.messageConverter.toMessage(this.message, session);
} catch (Exception var3) {
throw new MessageConversionException("Could not convert '" + this.message + "'", var3);
}
}
}
}
我們可以跟蹤方法的執行,進入到JmsTemplate這個類中,發現裏面336行封裝着一個方法。
protected void doSend(MessageProducer producer, Message message) throws JMSException {
if (this.deliveryDelay >= 0L) {
producer.setDeliveryDelay(this.deliveryDelay);
}
if (this.isExplicitQosEnabled()) {
producer.send(message, this.getDeliveryMode(), this.getPriority(), this.getTimeToLive());
} else {
producer.send(message);
}
}
這裏就可以看到producer這個對象調用的send方法中有設置幾個參數:
(1)、message不用說了,就是要發送的消息內容;
(2)、Delivery Mode翻譯過來就是發送模式發送模式;
(3)、priority!這就是我們要找的優先級,傳入0-9整數,數字越大優先級越高;
(4)、timeToLive延時發送時間;
Ok,到這裏我們就可以看到,如果要執行這個方法就只需要isExplicitQosEnabled()這個方法返回值爲true。
再看代碼的第175行的方法:
public void setQosSettings(QosSettings settings) {
Assert.notNull(settings, "Settings must not be null");
this.setExplicitQosEnabled(true);
this.setDeliveryMode(settings.getDeliveryMode());
this.setPriority(settings.getPriority());
this.setTimeToLive(settings.getTimeToLive());
}
這個方法就是設置上面幾個參數的設置,只需傳入QosSettings這個對象就行。
來看JmsTemplate這個類的構造方法:
public JmsTemplate() {
this.transactionalResourceFactory = new JmsTemplate.JmsTemplateResourceFactory();
this.messageIdEnabled = true;
this.messageTimestampEnabled = true;
this.pubSubNoLocal = false;
this.receiveTimeout = 0L;
this.deliveryDelay = -1L;
this.explicitQosEnabled = false;
this.deliveryMode = 2;
this.priority = 4;
this.timeToLive = 0L;
this.initDefaultStrategies();
}
可以看到幾個參數的默認值,也就是沒有執行任何操作時如果將explicitQosEnabled設置爲true則該消息發送出去優先級默認爲4。
3.所以我們將生產者代碼進行修改:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.QosSettings;
import org.springframework.stereotype.Service;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
@Service
public class TestProducer {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
public void sendMsg(Destination destination, String text, int priority){
//獲取jmsTemplate對象
JmsTemplate jmsTemplate = jmsMessagingTemplate.getJmsTemplate();
//創建QosSettings對象
QosSettings settings = new QosSettings();
//設置優先級
settings.setPriority(priority);
//設置發送模式
settings.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//設置延時發送時間
settings.setTimeToLive(1000L);
//將設置傳入
jmsTemplate.setQosSettings(settings);
//發送消息
jmsMessagingTemplate.convertAndSend(destination,text);
}
}
這樣生產者就寫完了。
4.測試
首先,先寫一個消費者,監聽隊列queue1:
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class TestConsumer {
@JmsListener(destination = "queue1")
public void receiveTest(String text){
System.out.println("接收到queue1發送的消息:"+text);
}
}
再寫一個controller類可以通過前端調用發送消息的方法:
import com.example.springboot.mq.demo.producer.TestProducer;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.jms.Destination;
import java.util.Random;
@RestController
public class TestController {
@Autowired
private TestProducer producer;
@RequestMapping("/test")
public String test(){
Destination destination = new ActiveMQQueue("queue1");
Random random = new Random(10);
for (int i=0;i < 100;i++){
//隨機產生優先級數字
int priority = random.nextInt(9);
producer.sendMsg(destination,"消息No."+i+"優先級爲:"+priority,priority);
}
return "發送成功";
}
}
然後啓動項目進行測試,測試結果:
發送消息優先級爲:0
發送消息優先級爲:6
發送消息優先級爲:6
發送消息優先級爲:0
發送消息優先級爲:4
發送消息優先級爲:1
發送消息優先級爲:1
發送消息優先級爲:4
發送消息優先級爲:3
發送消息優先級爲:1
發送消息優先級爲:1
發送消息優先級爲:2
發送消息優先級爲:6
發送消息優先級爲:6
發送消息優先級爲:3
發送消息優先級爲:3
發送消息優先級爲:1
發送消息優先級爲:1
發送消息優先級爲:4
發送消息優先級爲:7
發送消息優先級爲:8
發送消息優先級爲:7
發送消息優先級爲:6
發送消息優先級爲:8
發送消息優先級爲:5
發送消息優先級爲:1
發送消息優先級爲:5
發送消息優先級爲:5
發送消息優先級爲:8
發送消息優先級爲:8
發送消息優先級爲:2
發送消息優先級爲:8
發送消息優先級爲:6
發送消息優先級爲:8
發送消息優先級爲:7
發送消息優先級爲:1
發送消息優先級爲:7
發送消息優先級爲:1
發送消息優先級爲:0
發送消息優先級爲:4
發送消息優先級爲:8
發送消息優先級爲:1
發送消息優先級爲:8
發送消息優先級爲:0
發送消息優先級爲:7
發送消息優先級爲:5
發送消息優先級爲:6
發送消息優先級爲:0
發送消息優先級爲:2
發送消息優先級爲:4
接收到的消息:
消息內容:1優先級爲:6
消息內容:20優先級爲:8
消息內容:23優先級爲:8
消息內容:28優先級爲:8
消息內容:29優先級爲:8
消息內容:31優先級爲:8
消息內容:33優先級爲:8
消息內容:40優先級爲:8
消息內容:42優先級爲:8
消息內容:19優先級爲:7
消息內容:21優先級爲:7
消息內容:34優先級爲:7
消息內容:36優先級爲:7
消息內容:44優先級爲:7
消息內容:2優先級爲:6
消息內容:12優先級爲:6
消息內容:13優先級爲:6
消息內容:22優先級爲:6
消息內容:32優先級爲:6
消息內容:46優先級爲:6
消息內容:24優先級爲:5
消息內容:26優先級爲:5
消息內容:27優先級爲:5
消息內容:45優先級爲:5
消息內容:4優先級爲:4
消息內容:7優先級爲:4
消息內容:18優先級爲:4
消息內容:39優先級爲:4
消息內容:49優先級爲:4
消息內容:8優先級爲:3
消息內容:14優先級爲:3
消息內容:15優先級爲:3
消息內容:11優先級爲:2
消息內容:30優先級爲:2
消息內容:48優先級爲:2
消息內容:5優先級爲:1
消息內容:6優先級爲:1
消息內容:9優先級爲:1
消息內容:10優先級爲:1
消息內容:16優先級爲:1
消息內容:17優先級爲:1
消息內容:25優先級爲:1
消息內容:35優先級爲:1
消息內容:37優先級爲:1
消息內容:41優先級爲:1
消息內容:0優先級爲:0
消息內容:3優先級爲:0
消息內容:38優先級爲:0
消息內容:43優先級爲:0
消息內容:47優先級爲:0
結果顯示,除第一條外,其他消息的接收全部是按消息的優先級出隊列。所以大功告成,至於第一個消息爲什麼沒有按優先級出隊列後續我再研究一下。