解决消息队列中因消费超时导致的重复消费

背景

在很多业务场景中,我们都会使用消息队列。而消息队列的ACK机制,让我们在处理消息时更有保障。

比如,当consumer从队列中获取到消息,进行消费处理时,服务宕机了。

  • ACK机制,就保障这些在预设的超时时间内,未被正常消费的消息,会重新被安排消费。
  • 如果服务还在正常处理,但是超过了超时时间,会带来消息重复消费问题。

本文的目标,通过增加消息的监听器,来解决由于消费超时,导致的重复消费问题

另外,通过组件封装,与大家分享可复用代码的编写思路。进一步理解面向对象设计中的开闭原则。实现的代码片段较为简单,可以随处复制到项目中使用。但是对于逻辑中的超时时间、续期间隔等配置参数,如果复制到新的业务逻辑中,加之代码修改,容易产生BUG。而通过封装,对外提供扩展策略,关闭代码修改,使得复用组件时,可以专注业务逻辑开发。

实现流程

在消息队列中,消息的生命周期包含:

  1. 发送,由生产者将消息写入到队列
  2. 接受,由消费者将消息从队列中取走
  3. 回退,消费异常,重新进行Step.2
  4. ACK,消费完成,通知消息队列清理消息

解决方案:在Step.2和Step.4的操作过程中,将消息状态提供给消息监听器Listener,通过线程遍历,来完成不可见消息时间的自动更新

代码实现

本文的技术实现,是基于aws sqs服务

Listener逻辑

private void listen() {
        new Thread(() -> {
            log.info("start message visibility listener");
            while (true) {
                for (Map.Entry<String, ObjTime> entry : objTimeMap.entrySet()) {
                    ObjTime time = entry.getValue();

                    if ((System.currentTimeMillis()
                            - time.getTime()) / 1000
                            + updateVisibilityInterval > timeout) {
                        // 重新放入obj对象信息
                        time.setTime(System.currentTimeMillis());
                        amazonSQS.changeMessageVisibility(queueUrl,
                                time.getObj().getSqsReceiptHandle(),
                                timeout);
                        messageObjectConverter.log("visibility", time.getObj());
                    }
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
    }

使用手册

Maven依赖

说明:依赖并未发布到公共仓库中,可参考附录中项目仓库。

<dependency>
    <groupId>com.yanmushi</groupId>
    <artifactId>aws-sqs-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

属性配置

核心服务:AwsSqsService

属性 是否必须 说明
AmazonSQS 消息队列客户端
QueueName 消息队列名称
MessageObjectConverter 消息与实体对象之间转换器,需要自定义实现
Timeout 默认:60s。消息不可见时间。
UpdateVisibilityInterval 默认:5s。到期5s前,延长消息不可见时间
IllegalMessageHandler 默认:丢弃策略。可自定义扩展

遇到问题

  1. SQS中消息不可见时间最大为12小时,如果过长会被拒绝
  2. 消息队列中的消息是非期望的

附录

  1. Gitee项目仓库
  2. 不错的代码绘图工具PlantUML
    消息队列的工作模式
@startuml
消息生产者 -> 队列SQS : 1. 发送
队列SQS -> 消息消费者  : 2. 接收
队列SQS <-- 消息消费者 : 3. 回退(消费异常)
队列SQS <- 消息消费者 : 4. ACK
@enduml

引入Listener处理逻辑

@startuml
队列SQS -> 消息消费者  : 2. 接收
消息消费者 -> 消息Listener : 记录消息及时间
消息Listener -> 消息Listener : 遍历消息,更新将到期消息
队列SQS <-- 消息消费者 : 3. 回退(消费异常)
队列SQS <- 消息消费者 : 4. ACK
消息消费者 -> 消息Listener : 移除消息
@enduml
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章