ActiveMQ学习笔记(十一)—— 重试机制与死信队列

消费重试机制

在消息的消费过程中,如果消息未被签收或者签收失败,是会导致消息重复消费的,但如果消息一直签收失败,那是不是就会被无限次的消费呢?答案是否定的。一条消息签收不成功,消息服务器就会认为该消费者没有消费过这条消息,就会再次将这条消息传送给该消费者供它消费。至于会传送几次取决于我们定义的消费重试机制。很显然消费重试机制是针对消费者端的。

当发生下列任一情况时,消息将重新传递给客户端(也就是消费者)
1. 开启事务,调用了session.rollback()
2. 开启事务,session.commit()之前关闭了会话或没有commit或commit失败
3. 在Session.CLIENT_ACKNOWLEDGE签收模式下,调用了session.recover() 或没有签收
4. 客户端连接超时(可能正在执行的代码比配置的超时时间长

在默认情况下,当消息签收失败时ActiveMQ消息服务器会继续每隔1秒钟向消费者端发送一次这个签收失败的消息,默认会尝试6次(加上正常的1次共7次),如果这7次消费者端全部签收失败,则会给ActiveMQ服务器发送一个“poison ack”,表示这个消息不正常(“有毒”),这时消息服务器不会继续传送这个消息给这个消费者,而是将这个消息放入死信队列(DLQ,即Dead Letter Queue)。官网http://activemq.apache.org/redelivery-policy

属性 默认值 描述
backOffMultiplier 5 重连时间间隔的递增倍数,只有值大于1和启用useExponentialBackOff参数时生效,默认为5。自定义消费重试示例:只需要在消费者端设置重试策略
collisionAvoidanceFactor 0.15 设置防止冲突范围的正负百分比,只有启用useCollisionAvoidance参数时才生效,也就是在延迟时间上再加一个时间波动范围
initialRedeliveryDelay 1000L 初始的重发时间间隔,即正常发送签收失败后间隔多长时间进行重发(以毫秒为单位)。
maximumRedeliveries 6 最大重传次数,达到最大重传次数后抛出异常。值为-1时不限制次数,为0时不重传
maximumRedeliveryDelay -1 最大重连时间间隔,只在useExponentialBackOff为true是有效。假设首次重连间隔为10ms,倍数为2,那么第2次重连的时间间隔为20ms,第3次重连的时间间隔为40ms,当重连时间间隔大于最大重连时间间隔时,以后每次重连的时间间隔都是设置的最大重连时间间隔。默认值为-1,表示没有最大重连时间间隔
redeliveryDelay 1000L 重发延迟时间,当initialRedeliveryDelay=0是有效
useCollisionAvoidance false 启用防止冲突功能,默认false
useExponentialBackOff false 使用倍数递增的方式增加重发延迟时间,默认false
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://192.168.1.3:61618");
//自定义消费重试机制
RedeliveryPolicy policy = new RedeliveryPolicy();
policy.setMaximumRedeliveries(3);
factory.setRedeliveryPolicy(policy);
Connection connection = factory.createConnection();
connection.start();
...

在spring中配置RedeliveryPolicy

<!-- 重试策略 -->
<bean id="activeMQRedeliveryPolicy" class="org.apache.activemq.RedeliveryPolicy">
		<property name="useExponentialBackOff" value="true" />
		<property name="maximumRedeliveries" value="3" />
		<property name="initialRedeliveryDelay" value="1000" />
		<property name="backOffMultiplier" value="2" />
		<property name="maximumRedeliveryDelay" value="1000" />
	</bean>
    
    <!-- activemq 连接池 -->
    <bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
    	<property name="connectionFactory">
    		<bean class="org.apache.activemq.ActiveMQConnectionFactory">
        		<property name="brokerURL" value="tcp://192.168.1.3:61616" />
        		<property name="redeliveryPolicy" ref="activeMQRedeliveryPolicy"/> <-- 注入策略 -->
    		</bean>
    	</property>
    </bean>

消费重试失败的消息会被放入到死信队列中:

死信队列

ActiveMQ中引入了“死信队列”(Dead Letter Queue)的概念,在一条消息被重复发送给消息消费者端多次(默认为6次)后,若一直签收不成功,则ActiveMQ会将这条消息移入到“死信队列”。开发时可以开启一个后台线程监听这个队列(默认死信队列的名称为ActiveMQ.DLQ)中的消息,进行人工干预,也就是说死信队列的作用主要是处理签收失败的消息。官网http://activemq.apache.org/message-redelivery-and-dlq-handling

关于死信队列的配置主要有两种:SharedDeadLetterStrategy和IndividualDeadLetterStrategy

SharedDeadLetterStrategy:共享的死信队列配置策略,将所有的DeadLetter保存在一个共享的队列中,这是ActiveMQ Broker端的默认策略。共享队列的名称默认为“ActiveMQ.DLQ”。

IndividualDeadLetterStrategy:单独的死信队列配置策略,把DeadLetter放入各自的死信通道中。对于Queue而言,死信通道的前缀默认为“ActiveMQ.DLQ.Queue”;对于Topic而言,死信通道的前缀默认为“ActiveMQ.DLQ.Topic”。比如队列Order,那么它对应的死信通道为“ActiveMQ.DLQ.Queue.Order”。我们可以使用queuePrefix和topicPrefix来指定上述前缀

<broker>

	<destinationPolicy>
		<policyMap>
		  <policyEntries>
			<!-- 仅对与order队列起作用 -->
			<policyEntry queue="order">
				<deadLetterStrategy>
					<!-- useQueueForQueueMessage属性的作用:是否将名为order的Topic中的DeadLetter也保存在该队列中,默认为true -->
					<individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="false" />
				</deadLetterStrategy>
			</policyEntry>

			<!-- > 符号表示对所有队列起作用 -->
			<policyEntry queue=">">
				<deadLetterStrategy>
					<!--
						processExpired:过期消息,即setJMSExpiration(1000),是否放入死信队列,默认true 
						processNonPersistent:非持久化消息,即setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT),是否放入死心队列,默认false
					-->
					<sharedDeadLetterStrategy processExpired="false" processNonPersistent="true"  /> 
				</deadLetterStrategy>
			</policyEntry>

		</policyMap>
	</destinationPolicy>

</broker>

防止重复调用

ActiveMQ中的消息有时是会被重复消费的,而我们消费消息时大都会在拿到消息后去调用其他的方法,比如说将消息的内容解析为一个对象保存到数据库中。一旦发生消息的重复消费时就会重复保存,这是有问题的,因此我们需要考虑如何防止重复调用。其实我们是没有办法防止重复调用的,只能在重复调用时进行消息是否重复消费的校验,当然对于幂等性接口也可以不进行校验。那如何进行校验呢?有很多种方式,比如说我们将消费过的消息的messageId保存到数据库,每次消费消息前先到数据库中查一下该消息是否已被消费。在分布式系统中,也可以将消费过的消息放入redis中,以messageId作为key,message对象作为value(其实value不重要,当然也要看需求本身),在消费消息时先从redis中查找该消息是否已被消费。
 

发布了64 篇原创文章 · 获赞 0 · 访问量 3198

猜你喜欢

转载自blog.csdn.net/q42368773/article/details/103484884