ActiveMQ认识和深入(七),高级特性
ActiveMQ,异步投递
官网地址:http://activemq.apache.org/async-sends
ACtiveMQ 支持同步、异步两种发送的模式将消息发送到broker,模式的选择对发送延时有巨大的影响。 producer 能达到怎样的产出率(产出率=发送数据总量/时间)主要受发送延时的影响,使用异步发送可以显著的提高发送的性能。
ACtiveMQ 默认使用异步发送的模式:除非明确指定使用同步发送的方式或者在未使用事务的前提下发送持久化的消息,这两种情况都是被同步发送的。
如果你没用使用事务且发送的是持久化的消息,每一次发送都是同步发送的且会阻塞 Producer 直到 broker 返回一个确认,表示消息已经被安全的持久化到磁盘, 确认机制提供了消息安全的保障,但同时会阻塞客户端带来很大的延时。
很多高性能的应用,允许在失败的情况下有少量的数据丢失。如果你的应用满足这个特点,你可以使用发送的是持久化的消息。
异步发送
它可以最大化 produer 端的发送效率。我们通常在发送消息量比较密集的情况下使用异步发送,它可以很大的提升p roducer 性能;
就是需要消耗较多的 Client 端内存同时也会导致 broker 端性能消耗增加;
此外它不能有效的确保消息的发送成功。在 useAsyncSend=true 的情况下客户端需要容忍消息丢失的可能。
官网三种开启异步投递方法;
JAVA代码实现;
package com.activemq.demo;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class Jms_TX_Producer {
// 方式1。3种方式任选一种
private static final String ACTIVEMQ_URL = "tcp://localhost:61626?jms.useAsyncSend=true";
private static final String ACTIVEMQ_QUEUE_NAME = "Async";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 方式2
activeMQConnectionFactory.setUseAsyncSend(true);
Connection connection = activeMQConnectionFactory.createConnection();
// 方式3
((ActiveMQConnection)connection).setUseAsyncSend(true);
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME);
MessageProducer producer = session.createProducer(queue);
try {
for (int i = 0; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("tx msg--" + i);
producer.send(textMessage);
}
System.out.println("消息发送完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
producer.close();
session.close();
connection.close();
}
}
}
异步发送如何确认发送成功;
异步发送丢失消息的场景是:生产者设置
UseAsyncSend=true
,使用producer.send(msg)
持续发送消息。由于消息不阻塞,生产者会认为所有send的消息均被成功发送至MQ。如果MQ突然宕机,此时生产者端内存中尚未被发送至MQ的消息都会丢失。所以,正确的异步发送方法是需要接收回调的。同步发送和异步发送的区别就在此,同步发送等send不阻塞了就表示一定发送成功了,异步发送需要接收回执并由客户端再判断一次是否发送成功。
代码实现;
package com.activemq.demo;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQMessageProducer;
import org.apache.activemq.AsyncCallback;
import javax.jms.*;
import java.util.UUID;
public class Jms_TX_Producer {
private static final String ACTIVEMQ_URL = "tcp://localhost:61626";
private static final String ACTIVEMQ_QUEUE_NAME = "Async";
public static void main(String[] args) throws JMSException {
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();
}
}
}
ActiveMQ,延迟投递和定时投递
官网地址:http://activemq.apache.org/delay-and-schedule-message-delivery.html
activemq.xml 配置 schedulerSupport="true"
;
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true" >
代码实现;
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://localhost: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();
}
}
}
ActiveMQ,消费重试机制
官网地址:http://activemq.apache.org/message-redelivery-and-dlq-handling
具体哪些情况会引起消息重发
-
Client用了transactions且在session中调用了rollback()
-
Client用了transactions且在调用commit()之前关闭或者没有commit
-
Client在CLIENT_ACKNOWLEDGE的传递模式下,在session中调用了recover()
消息重发时间间隔和重发次数吗
间隔:1
次数:6
有毒消息Poison ACK的理解
一个消息被redelivedred超过默认的最大重发次数(默认6次)时,消费端会给MQ发送一个”poison ack”表示这个消息有毒,告诉broker不要再发了。这个时候broker会把这个消息放到DLQ(死信队列)。
JAVA修改消费重试机制配置
RedeliveryPolicy queuePolicy = new RedeliveryPolicy();
queuePolicy.setInitialRedeliveryDelay(0);
queuePolicy.setRedeliveryDelay(1000);
queuePolicy.setUseExponentialBackOff(false);
queuePolicy.setMaximumRedeliveries(2);
Spring配置
<!-- 定义ReDeLivery(重发机制) 机制-->
<bean id="activeMQRedeliveryPolicy" c1ass="org.apache.activemq.RedeliveryPolicy">
<!--否在每次尝试重新发送先败后,增长这个等待时间-->
<property name="useExponentia1Back0ff" valuer="true"> </property>
<!--否在每次尝试重新发送先败后,增长这个等待时间-->
<property name="maximumRedeliveries" valuer="3"></property>
<!--重发时间间隔,默认为1秒-->
<property name="initialRedeliveryDelay" valuer="1000"></property>
<!--第一次失败后重新发送之前等待500毫形,第二次先败再等待500 * 3毫秒,这里的3就是value -->
<property name="backOffMultiplier" valuer="3" ></property>
<!--最大传送延迟,只在useExponentialBackoff为true时有效(V5.5)
假设首次重连间隔为10ms倍数为2,那么第二次重连时间间隔为20ms,第三次重连时间间隔为40ms,
当重连时间间隔大的最大重连时间间隔时,以后每次重连时间间隔都为最大重连时间间隔。-->
<property name="maximumRedeliveryDelay" valuer="1000" ></property>
</bean>
<!-- 创建速接工厂-->
<bean id="connectionFactory" class="org.apache. activemq.ActiveMQConnectionFactory">
<property name="brokerURL" valuer="tcp://localhost:61616"> </property>
<!--否在每次尝试重新发送先败后,增长这个等待时间-->
<property name="redeliveryPolicy" ref="activeMQRedeliveryPolicy"></property>
</bean>
ActiveMQ,死信队列
官网地址:http://activemq.apache.org/redelivery-policy
activeMq 中引入了 死信队列 概念,即一条消息再被重发了多次后,(默认为重发6次redeliveryCounter ==6),将被ActiveMQ移入死信队列,我们可以在队列里面查看处理出错的消息。进行人工处理。
sharedDeadLetterStrategy
将所有的死信保存在一个共享的队列中,这是ActiveMQ broker端默认的策略。共享队列默认为“ActiveMQ.DLQ”,可以通过“deadLetterQueue” 属性来设定。
<deadLetterStrategy>
<sharedDeadLetterStrategy deadLetterQueue="DLQ-QUEUE"/>
</deadLetterStrategy>
individualDeadLetterStrategy
把DeadLetter放入各自的死信通道中,
对于 Queue而言,死信通道的前缀默认为,“ActiveMQ.DLQ.Queue”
对于 Topic而言,死信通道的前缀默认为"ActiveMQ.DLQ.Topic"
比如队列Order,那么它对于的死信通道为 “ActiveMQ.DLQ.Queue.Order”
我们使用queuePrefix topicPrefix 来指定上述前缀
默认情况下,无论是Topic还是Queue,broker将使用 Queue来保存DeadLeader,即死信通道通常为Queue,不过开发者也可以指定为 Topic
<destinationPolicy>
<policyMap>
<policyEntries>
<!-- 使用“>”通配符在所有队列上设置以下策略 -->
<policyEntry queue=">">
<deadLetterStrategy>
<!-- 使用前缀“DLQ.”作为目标名称,并使DLQ是一个队列,而不是一个主题-->
<individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="true"/>
</deadLetterStrategy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
自动删除过期消息,而不发送到死队列中。
<destinationPolicy>
<policyMap>
<policyEntries>
<!-- 使用“>”通配符在所有队列上设置以下策略 -->
<policyEntry queue=">">
<deadLetterStrategy>
<!-- 告诉死信策略不要处理过期消息,这样它们就会被删除,而不是被送到死信(DLQ) -->
<sharedDeadLetterStrategy processExpired="false" />
</deadLetterStrategy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
存放非持久消息到死信队列中
<destinationPolicy>
<policyMap>
<policyEntries>
<!-- 使用“>”通配符在所有队列上设置以下策略 -->
<policyEntry queue=">">
<!-- 告诉死信策略也放置非持久化消息,如果无法送达,则发送到死信队列。 -->
<deadLetterStrategy>
<sharedDeadLetterStrategy processNonPersistent="true" />
</deadLetterStrategy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
ActiveMQ,如何保证消息不被重复消费,幂等性的问题
-
如果消息是做数据库的插入操作,给这个消息一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据 。
-
如果不是,可以用redis 等的第三方服务,给消息一个全局 id ,只要消费过的消息,将 id ,message 以 K-V 形式写入 redis ,那消费者开始消费前,先去 redis 中查询有没消费的记录即可。