您好,欢迎您访问welcome-球速体育,我们将竭诚为您服务!
   

7*24小时求学热线

020-12345678

您现在所在的位置: 首页 > 成人高考 > 成考资讯

Java教程:如何实现异步通知重试机制确保消息成功送达

时间:2026-02-27 来源: 本站 阅读:

你所下下达的相关订单,其支付环节已然成功完成,然而库存数量却并未出现扣减的情况,这般数据呈现出不一致状态的故障现象,其根源常常是源自一次以失败告终的异步通知操作行为。在分布式系统这个范畴当中,异步通知能够实现可靠送达这一目标,相较于你所想象的通常情形而言,实际达成难度是要大得多的。

为什么异步通知容易失败

异步通知实际上就是,一个服务借助HTTP请求,去告知另一个服务“有一件事情发生了”。就好比在电商平台当中,订单服务会向库存服务传达“订单001支付已成功,请进行库存扣减”这一信息。此过程依赖于网络,然而网络恰恰是最为不可靠的环节。

在2023年双十一阶段,某位于前列的电商平台,只因瞬间流量过多,致使千分之一的回调通知超出规定时间,对约5万笔订单的库存扣除造成了影响。这属于典型的重试机制设计欠佳。 网络出现波动,目标服务GC暂停,数据库连接池已满,这些情况都会致使通知失败。

设计靠谱的重试策略

并不是简简单单直接把请求再次发送就叫做重试了,这里面是需要有相应策略的。在这其中,首先一定要明确界定出来哪些失败的情况是值得去进行重试操作的,就如同像是HTTP返回500、502、504这类的服务端错误情况,又或者是出现连接超时这类状况。要是遇到的是400这种属于客户端错误的情况,那么即便进行重试那也是毫无作用的。

重试间隔颇有讲究,固定间隔易引发雪崩,像服务恰好复苏了,倘若你一秒之后便重试,极有可能再度将其压垮,业界常用指数退避策略,首次失败后等待一秒,第二次等待两秒,第三次等待四秒,给予对端喘息之机,还需设定最大重试次数,通常为三至五次。

用Spring Retry实现本地重试

倘若你的项目正运用Spring Boot,最为简便的办法乃是集成Spring Retry。首要在pom.xml之中引入spring-retry以及aspectjweaver依赖。接着于启动类或者配置类之上添加上@EnableRetry注解。

下一步,于你所要进行重试的方法那儿,像是一个发送通知的Service方法,添加上@Retryable注解。能够按这般去配置:@Retryable(value = {RemoteAccessException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 2))。也就是说,当遭遇到RemoteAccessException异常情况时,要重试3次,其中,第一次需等待2秒,而后续的每次等待时长都要在前一次的基础上翻倍。

借助消息队列做异步补偿

本地进行重试存在着一个不足之处,若应用被重新启动了,那么重试的状态便会丢失。更为健壮的一种做法是引入消息队列,像是RabbitMQ或者RocketMQ这类。当首次通知遭遇失败之后,并非立马进行重试,而是将这条消息发送至一个延迟队列当中。

拿RocketMQ来说,它对延迟时间有着18个级别的支持状况 ,你能够依据重试的次数情形 ,将消息投送到不同条件级别的延迟状队列之中 ,像第1次重试选用5秒之后的时间 ,第2次重试选用10秒之后的时间 ,RocketMQ的定时消息的机制情况 能够确保即便你的应用出现挂掉的状况 ,消息也不会出现丢失的状况 ,在重启之后会继续进行消费的行为。

记录重试日志和监控告警

重试机制绝不可为黑盒,定要能够被观察到。每一回重试均应当记录详尽日志,涵盖重试次数、耗时、以及失败原因。在2024年初的时候,某银行系统恰恰由于重试日志未打全,排查问题耗费了整整两天时间。

java 代码解读复制代码// NotificationService.java
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    // 发送通知
    public void sendNotification(String message, String routingKey) {
        try {
            rabbitTemplate.convertAndSend(routingKey, message);
            // 模拟通知成功
            System.out.println("通知发送成功");
        } catch (Exception e) {
            // 模拟通知失败,进入重试队列
            System.out.println("通知发送失败,进入重试队列");
            rabbitTemplate.convertAndSend("retryQueue", message);
        }
    }
}

采用MDC机制于日志当中注入一个全局的traceId,如此一来能够将一次通知的多次重试串联起来。与此同时,针对超过阈值的消息,像是重试了5次依旧失败的情况,要发送告警至钉钉或者电话,以使值班人员人工介入,防止数据长时间维持不一致状态。

结合数据库做最终状态保证

java 代码解读复制代码// RetryService.java
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class RetryService {
    // 监听重试队列
    @RabbitListener(queues = "retryQueue")
    public void retryNotification(String message) {
        // 处理重试逻辑
        System.out.println("处理重试消息:" + message);
        // 此处应包含重试逻辑,如增加重试次数,设置下一次重试的时间等
    }
}

以下句子最为稳妥:配合业务数据库来做,以电商作为例子来看,当订单支付获得成功之后,首先在订单表里对状态进行更新,使其变为“已支付待扣减”,与此同时插入一条通知记录,该记录状态是“初始化”。之后存在一个定时任务,此定时任务每隔一定时间扫描通知记录表里状态为“初始化”或者“失败”,并且未超过重试限度的记录。

此定时任务承担传送通知职责,于获取成功回应过后,将纪录状态更替成‘成功’。倘若持续呈现失败状况,人工参与进来后能够手动激发再次尝试。这一方案虽说较为繁复,但可为数据最终一致性予以保证,诸多金融机构皆在运用它。

言及如此之多说,于实际项目里,你曾遭遇因通知失败引致的数据不一致此种问题与否?那时是怎样予以解决的呢?欢迎于评论区之中分享你的踩坑历程,要是觉得本文具实用价值,切莫忘了点赞之便,抑或是分享给予你的同事之事哦。