1.异步投递
1.1是什么
ActiveMQ支持同步、异步两种模式将消息发送到broker,模式的选择对于发送的延时有着巨大的影响,使用异步可以显著的提高发送性能。
ActiveMQ默认使用的是异步发送的模式,除非明确指定使用同步发送的方式或者是在未使用事物的前提下发送持久化的消息。
如果没有使用事务且发送的是持久化的消息,每一次发送都会是同步发送的且会阻塞producer直到broker返回一个确认,表示已经被安全的持久化到硬盘。确认机制提供了消息安全的保障,但是同时会阻塞客户端带来很大的延时。
很多高性能的应用,允许在失败的情况下有少量的数据丢失。
异步发送可以最大化producer端的发送效率,在消息量发送比较密集的时候一般使用异步发送,他可以很大的提升producer性能,但是也带来了额外的问题:
- 需要较多的client端内存同时也会导致broker端性能消耗增加
- 不能确保信息的发送成功。要是想要确保消息发送是否成功,就要采用回调的方式。
1.2异步发送如何确认发送成功
异步发送丢失消息的场景是:生产者设置UseAsySend=true,使用producer.send(msg)持续发送消息。由于消息不阻塞,生产者会认为所有的消息均被成功发送至MQ。
如果MQ忽然间宕机,此时生产者端内存中尚未被方发送到MQ的消息都会丢失。
同步发送等send不阻塞了就表示一定发送成功了。但是异步发送需要接收回执并且由客户端再判断一次是否发送成功。
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
activeMQConnectionFactory.setUseAsyncSend(true);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME);
ActiveMQMessageProducer activeMQMessageProducer = (ActiveMQMessageProducer)session.createProducer(queue);
try {
for (int i = 0; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("tx msg--" + i);
textMessage.setJMSMessageID(UUID.randomUUID().toString()+"orderAtguigu");
final String msgId = textMessage.getJMSMessageID();
activeMQMessageProducer.send(textMessage, new AsyncCallback() {
public void onSuccess() {
System.out.println("成功发送消息Id:"+msgId);
}
public void onException(JMSException e) {
System.out.println("失败发送消息Id:"+msgId);
}
});
}
System.out.println("消息发送完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
activeMQMessageProducer.close();
session.close();
connection.close();
}
}
}
2.延迟投递和定时投递
官网文档:http://activemq.apache.org/delay-and-schedule-message-delivery.html
2.1修改配置文件并重启
在activemq.xml添加如下代码:
</bean>
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true" >
<destinationPolicy>
之后重启activemq
2.2代码实现
以下是生产者代码,消费者代码不变
package com.activemq.demo;
import org.apache.activemq.*;
import javax.jms.*;
import java.util.UUID;
public class Jms_TX_Producer {
private static final String ACTIVEMQ_URL = "tcp://118.24.20.3:61626";
private static final String ACTIVEMQ_QUEUE_NAME = "Schedule01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME);
MessageProducer messageProducer = session.createProducer(queue);
long delay = 10*1000;
long period = 5*1000;
int repeat = 3 ;
try {
for (int i = 0; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("tx msg--" + i);
// 延迟的时间
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);
// 重复投递的时间间隔
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
// 重复投递的次数
textMessage.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
// 此处的意思:该条消息,等待10秒,之后每5秒发送一次,重复发送3次。
messageProducer.send(textMessage);
}
System.out.println("消息发送完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
messageProducer.close();
session.close();
connection.close();
}
}
}
3.消息消费者的重试机制
3.1是什么
官网文档:http://activemq.apache.org/redelivery-policy
消费者收到消息,之后出现异常了,没有告诉broker确认收到该消息,broker会尝试再将该消息发送给消费者。尝试n次,如果消费者还是没有确认收到该消息,那么该消息将被放到死信队列重,之后broker不会再将该消息发送给消费者。
3.2具体哪些情况会引发消息重发
- Client用了transactions且再session中调用了rollback
- Client用了transactions且再调用commit之前关闭或者没有commit
- Client再CLIENT_ACKNOWLEDGE的传递模式下,session中调用了recover
3.3消息重发时间间隔和重发次数
间隔:1
次数:6
每秒发6次
3.4有毒消息Poison ACK
一个消息被redelivedred超过默认的最大重发次数(默认6次)时,消费的回个MQ发一个“poison ack”表示这个消息有毒,告诉broker不要再发了。这个时候broker会把这个消息放到DLQ(私信队列)。
3.5属性说明
3.6代码修改默认参数
修改重试次数为3。更多的设置请参考官网文档。
public class Jms_TX_Consumer {
private static final String ACTIVEMQ_URL = "tcp://118.24.20.3:61626";
private static final String ACTIVEMQ_QUEUE_NAME = "dead01";
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 修改默认参数,设置消息消费重试3次
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(3);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
final Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME);
MessageConsumer messageConsumer = session.createConsumer(queue);
messageConsumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("***消费者接收到的消息: " + textMessage.getText());
//session.commit();
}catch (Exception e){
e.printStackTrace();
}
}
}
});
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
4.死信队列
4.1是什么
官网文档: http://activemq.apache.org/redelivery-policy
死信队列:异常消息规避处理的集合,主要处理失败的消息。
!
4.2死信队列的配置(一般采用默认的)
5.幂等性
幂等性,即为保证消息不被重复消费。
在网络传输延迟中,会造成MQ的重试中,在重试中,可能会造成重复消费。
解决方案:
- 如果这个消息是做数据库的插入操作,给这个消息做一个唯一主键,就算是出现了重复消费和情况,就会导致主键冲突。
- 实在不行,准备一个第三方的服务去做消费记录,以redis为例,给消息分配一个id,只要是消费过该消息,会以<id,message>以k-v形式写入redis,先去redis中查询有无消费记录即可。