引入消息队列之后该如何保证其高可用性
zookeeper+replicated-levelDB-Store的主从集群
异步投递 Async Sends
对于一个Slow Consumer,使用同步发送消息可能会出现Producer堵塞等情况,慢消费者适合使用异步发送--------activeMQConnectionFactory.setUseAsyncSend(true)
ActiveMQ默认使用异步发送的模式:除非明确指定使用同步发送的方式或者在未使用事务的前提下发送持久化的消息,这两种情况都是同步发送的。
如果没有使用事务且发送的是持久化的消息,每一次发送都是同步发送的且会阻塞producer知道borker返回一个确认,表示消息已经被安全的持久化到磁盘。确认机制提供消费安全的保证,但同时会阻塞客户端带来很大的延时,很多高性能的应用,允许在失败的情况下有少量数据的丢失。如果满足这个特点,可以使用异步发送来提高生产率,即使发送的是持久化的消息。
异步发送
它可以最大化producer端的发送效率。我们通常在发送消息量比较密集的情况下使用异步发送,它可以很大的提升Producer性能;不过也带来了额外的问题,就是需要消耗较多的Client端内存同时也会导致broker端性能消耗增加;此外它不能有效的确保消息发送成功,在useAsyncSend=true的情况下客户端需要容忍消息丢失的可能。
如何确认发送成功?
生产者设置UseAsyncSend=true,使用producer.send(msg)持续发送消息。
由于消息不阻塞,生产者会认为所有send的消息均被成功发送至MQ。
如果MQ突然宕机,此时生产者端内存中尚未被发送至MQ的消息都会丢失。
所以,正确的异步发送方法是需要接受回调的。
同步发送和异步发送的区别就在于此,
同步发送等send不阻塞了就表示一定发送成功了,
异步发送需要接受回执并由客户端在判断一次是否发送成功。
public class jmsProduce_AsyncSend {
public static final String ACTIVEMQ_URL="nio://192.168.10.100:61608";
public static final String QUEUE_NAME="jdbc01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,采用默认的用户名密码,连接给定的url
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
connectionFactory.setSendAcksAsync(true);
//2.通过连接工厂,获得Connection并启动
Connection connection = connectionFactory.createConnection();
connection.start();
//3创建session
//两个参数,第一个:事务,第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(队列还是主题)
Queue queue = session.createQueue(QUEUE_NAME);
//5创建消息的生产者
ActiveMQMessageProducer producer =(ActiveMQMessageProducer) session.createProducer(queue);
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
//6.通过使用MessageProducers生产3条消息发送到mq的队列里
for (int i = 1; i <=3; i++) {
TextMessage textMessage= session.createTextMessage("jdbc msg---" + i);//理解为一个字符串
//发送
textMessage.setJMSMessageID(UUID.randomUUID().toString()+"------order");
String msgID=textMessage.getJMSMessageID();
producer.send(textMessage, new AsyncCallback() {
@Override
public void onSuccess() {
System.out.println(msgID+"send ok");
}
@Override
public void onException(JMSException e) {
System.out.println(msgID+"send false");
}
});
}
//关闭资源
producer.close();
session.close();
connection.close();
System.out.println("******消息发布完成");
}
}
延时投递和定时投递
四大属性
修改activemq.xml配置文件
生产者
public class jmsProduce_DelayAndSchedule {
public static final String ACTIVEMQ_URL="nio://192.168.10.100:61608";
public static final String QUEUE_NAME="jdbc01-Delay";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageProducer producer = session.createProducer(queue);
long delay=3*1000; //延迟三秒
long period=4*1000; //每四秒一次
int repeat=5; //一共五次
for (int i = 1; i <=3; i++) {
TextMessage textMessage= session.createTextMessage("delay msg---" + i);//理解为一个字符串
//发送
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,period);
textMessage.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT,repeat);
producer.send(textMessage);
}
//关闭资源
producer.close();
session.close();
connection.close();
System.out.println("******消息发布完成");
}
}
ActiveMQ消费重试机制
具体哪些情况会引起消息重发
1.Client用了transactions且在session中调用了rollback()
2.Client用了transactions且在调用commit()之前关闭或者没有commit
3.Client在CLIENT_ACKNOWLEDGE的传递模式下,在session中调用了recover()
消息重发时间间隔和重发次数
间隔:1s
次数:6
有毒消息Poison ACK的理解
一个消息被redelivedred超过默认的最大重发次数时,消费端会给MQ发送一个“poison ack”表示这个消息有毒,告诉broker不要再发了,这个时候broker会把这个消息放到DLQ(死信队列)。
修改重发次数
RedeliveryPolicy redeliveryPolicy=new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(3);//修改重发次数
connectionFactory.setRedeliveryPolicy(redeliveryPolicy);
死信队列
一条消息被重发了多次后(默认6次),将会被ActiveMQ移入“死信队列”,可以在这个队列中查看出错的消息,进行人工干预。
SharedDeadLetterStrategy
将所有的DeadLetter保存在一个功效的队列中,这是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”
<policyEntry queue="order">
<deadLetterStrategy>
<individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="false"/>
</deadLetterStrategy>
</policyEntry>
自动删除过期消息
有时需要直接删除过期消息而不需要发送到死队列中,“processExpired” 默认为true
<policyEntry queue=">">
<deadLetterStrategy>
<sharedDeadLetterStrategy processExpired="false" />
</deadLetterStrategy>
</policyEntry>
存放非持久消息到死队列中
默认情况下,ActiveMQ不会把非持久的死消息发送到死信队列中,“processNonPersistent”表示是否将“非持久化”消息放入死信队列,默认为false
<policyEntry queue=">">
<deadLetterStrategy>
<sharedDeadLetterStrategy processNonPersistent="true" />
</deadLetterStrategy>
</policyEntry>
防止重复调用
网络延迟传输中,会造成进行MQ重试中,在重试过程中,可能会造成重复消费。
如果消息是做数据库的插入操作,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据
或者准备一个第三方服务来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该信息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询中有没消费记录即可