在AWS SQS的消息消费场景中,默认的重试机制会对所有消费失败的情况进行重试,但实际业务中往往只有部分可恢复的错误需要重试,比如依赖服务临时不可用,而参数错误、权限不足这类明确无法恢复的问题不需要重试。要实现仅对自定义异常触发重试,需要从SQS配置和消费端代码两个层面配合处理。

核心实现思路
整体实现分为两个步骤,首先配置SQS队列的重试相关参数,允许消息在消费失败后进行重试,然后在消费端代码中捕获异常,仅当异常为自定义的可重试异常时,才让消息重新回到队列等待重试,其他异常直接标记为消费失败,不再重试。
SQS队列基础配置
首先需要配置SQS队列的可见性超时和死信队列,避免消息无限重试。相关配置参数说明如下:
| 配置项 | 说明 |
|---|---|
| 可见性超时 | 消息被消费后到重新可见的时间,建议设置为单次消息处理的最大耗时,避免提前重试 |
| 最大接收次数 | 消息最多被接收的次数,超过该次数会发送到死信队列,避免无限重试 |
| 死信队列 | 存储超过最大重试次数的消息,方便后续排查问题 |
自定义可重试异常定义
首先定义自定义异常,区分可重试异常和不可重试异常,示例Java代码如下:
// 自定义可重试异常,标记该异常触发时需要重试消息
public class RetryableException extends RuntimeException {
public RetryableException(String message) {
super(message);
}
public RetryableException(String message, Throwable cause) {
super(message, cause);
}
}
// 自定义不可重试异常,标记该异常触发时不需要重试
public class NonRetryableException extends RuntimeException {
public NonRetryableException(String message) {
super(message);
}
public NonRetryableException(String message, Throwable cause) {
super(message, cause);
}
}
消费端逻辑处理
在消费端处理消息时,捕获异常并判断异常类型,仅当为RetryableException时,才触发消息重试,其他异常直接处理为消费失败。以Java SDK为例,示例代码如下:
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.DeleteMessageRequest;
import com.amazonaws.services.sqs.model.Message;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import java.util.List;
public class SqsConsumer {
private static final String QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/123456789012/my-queue";
private static final AmazonSQS sqs = AmazonSQSClientBuilder.defaultClient();
public static void main(String[] args) {
while (true) {
// 接收消息
ReceiveMessageRequest receiveRequest = new ReceiveMessageRequest()
.withQueueUrl(QUEUE_URL)
.withMaxNumberOfMessages(1)
.withWaitTimeSeconds(10);
List<Message> messages = sqs.receiveMessage(receiveRequest).getMessages();
for (Message message : messages) {
try {
processMessage(message.getBody());
// 处理成功,删除消息
DeleteMessageRequest deleteRequest = new DeleteMessageRequest()
.withQueueUrl(QUEUE_URL)
.withReceiptHandle(message.getReceiptHandle());
sqs.deleteMessage(deleteRequest);
} catch (RetryableException e) {
// 可重试异常,不删除消息,等待可见性超时后重新被消费
System.out.println("捕获可重试异常,消息将重试:" + e.getMessage());
} catch (NonRetryableException e) {
// 不可重试异常,直接删除消息,避免重试
System.out.println("捕获不可重试异常,消息将不再重试:" + e.getMessage());
DeleteMessageRequest deleteRequest = new DeleteMessageRequest()
.withQueueUrl(QUEUE_URL)
.withReceiptHandle(message.getReceiptHandle());
sqs.deleteMessage(deleteRequest);
} catch (Exception e) {
// 其他未知异常,可根据业务需求决定是否重试,这里默认不重试
System.out.println("捕获未知异常,消息不再重试:" + e.getMessage());
DeleteMessageRequest deleteRequest = new DeleteMessageRequest()
.withQueueUrl(QUEUE_URL)
.withReceiptHandle(message.getReceiptHandle());
sqs.deleteMessage(deleteRequest);
}
}
}
}
private static void processMessage(String body) {
// 模拟消息处理逻辑
if (body.contains("retry")) {
throw new RetryableException("依赖服务临时不可用,需要重试");
} else if (body.contains("invalid")) {
throw new NonRetryableException("消息参数校验失败,无需重试");
}
System.out.println("消息处理成功:" + body);
}
}
注意事项
- 可见性超时的设置要合理,不能小于单次消息处理的最大耗时,否则会出现消息还没处理完就被重新消费的情况
- 最大接收次数要根据业务重试需求设置,避免重试次数过多导致消息长时间占用队列资源
- 死信队列需要定期监控,排查那些超过重试次数的消息,定位失败原因
- 自定义异常的定义要清晰,避免混淆可重试和不可重试的场景,导致重试逻辑不符合预期
总结
实现AWS SQS仅对自定义异常触发重试的核心是区分异常类型,通过自定义异常标记可重试场景,在消费端代码中根据异常类型决定是否保留消息等待重试。这种方式可以灵活控制重试逻辑,避免无效重试,提升消息处理的效率。同时配合SQS的可见性超时、最大接收次数和死信队列配置,可以让整个重试机制更加健壮,减少资源浪费和故障排查成本。