百萬(wàn)級(jí)任務(wù)重試框架 Fast-Retry,太香了!
兄弟們,今天咱們來(lái)聊個(gè)扎心的話題 —— 任務(wù)重試。你是不是也遇到過(guò)這種情況:調(diào)用第三方接口突然超時(shí),推送消息莫名其妙失敗,支付回調(diào)半天沒(méi)響應(yīng)... 這時(shí)候老板拍著桌子讓你保證數(shù)據(jù)一致性,產(chǎn)品經(jīng)理追著問(wèn)為什么訂單狀態(tài)不同步。
沒(méi)辦法,只能重試唄。但手動(dòng)重試像打地鼠,寫個(gè)簡(jiǎn)單的循環(huán)重試又 hold 不住高并發(fā),用 Spring 的 Retry 注解又顯得不夠靈活。尤其是面對(duì)幾十萬(wàn)、上百萬(wàn)的任務(wù)量時(shí),重試邏輯簡(jiǎn)直能把人逼瘋。
直到我遇到了 Fast-Retry,才算真正解脫。這框架是真的香,今天必須給你們好好嘮嘮。
一、為啥我們需要專門的重試框架?
先說(shuō)說(shuō)咱們平時(shí)寫重試邏輯會(huì)踩的坑,看看你中了幾個(gè):
- 重試策略太死板:大多數(shù)人就寫個(gè)固定間隔重試,要么重試太頻繁把對(duì)方服務(wù)器打崩,要么間隔太長(zhǎng)導(dǎo)致數(shù)據(jù)延遲
- 沒(méi)考慮并發(fā)問(wèn)題:上千個(gè)任務(wù)同時(shí)失敗,一股腦全重試,直接把自己服務(wù)搞 OOM
- 缺乏持久化:服務(wù)一重啟,沒(méi)重試完的任務(wù)全丟了,哭都來(lái)不及
- 監(jiān)控一片空白:不知道多少任務(wù)重試成功,多少?gòu)氐资?,出了?wèn)題兩眼一抹黑
舉個(gè)真實(shí)案例:去年雙十一,朋友公司的支付回調(diào)處理服務(wù)因?yàn)榫W(wǎng)絡(luò)波動(dòng),積累了 50 多萬(wàn)條待重試任務(wù)。他們自己寫的重試邏輯直接扛不住,重試線程池滿了不說(shuō),數(shù)據(jù)庫(kù)連接也被耗盡,最后整個(gè)支付鏈路全崩了,損失慘重。
這就是為什么我們需要一個(gè)專業(yè)的重試框架 —— 它不僅要能重試,還要重試得聰明、高效、可靠。
二、Fast-Retry 到底是個(gè)啥?
Fast-Retry 是一個(gè)專為高并發(fā)場(chǎng)景設(shè)計(jì)的任務(wù)重試框架,聽(tīng)名字就知道,主打一個(gè) "快" 字。但它的優(yōu)點(diǎn)可不止快:
- 支持每秒處理 10 萬(wàn) + 重試任務(wù),輕松應(yīng)對(duì)百萬(wàn)級(jí)積壓
- 內(nèi)置 8 種重試策略,從簡(jiǎn)單到復(fù)雜全覆蓋
- 自帶持久化機(jī)制,服務(wù)重啟也不怕任務(wù)丟失
- 分布式環(huán)境下也能玩得轉(zhuǎn),不會(huì)重復(fù)重試
- 監(jiān)控指標(biāo)一應(yīng)俱全,失敗任務(wù)一目了然
最關(guān)鍵的是,這框架用起來(lái)賊簡(jiǎn)單,基本上是開(kāi)箱即用。你別以為功能強(qiáng)的框架就一定復(fù)雜,F(xiàn)ast-Retry 的設(shè)計(jì)理念就是 "復(fù)雜的事情框架做,簡(jiǎn)單的事情留給開(kāi)發(fā)者"。
三、核心設(shè)計(jì)思路:為啥它能這么快?
咱們得先明白一個(gè)道理:重試不是簡(jiǎn)單地把失敗的任務(wù)再跑一遍。當(dāng)任務(wù)量達(dá)到百萬(wàn)級(jí)時(shí),重試本身就成了一個(gè)需要精心設(shè)計(jì)的分布式系統(tǒng)問(wèn)題。
Fast-Retry 的核心設(shè)計(jì)思路可以總結(jié)為 "三板斧":
1. 分級(jí)存儲(chǔ):冷熱數(shù)據(jù)分離
就像咱們家里的冰箱,常用的東西放冷藏室,不常用的放冷凍室。Fast-Retry 把任務(wù)分成了三級(jí):
- 熱任務(wù):剛失敗,需要馬上重試的任務(wù),存在內(nèi)存隊(duì)列里,速度最快
- 溫任務(wù):重試過(guò)幾次還沒(méi)成功的,存到本地磁盤的 RockDB 里
- 冷任務(wù):需要長(zhǎng)時(shí)間等待后再重試的(比如幾小時(shí)后),存到分布式存儲(chǔ)里
這樣一來(lái),既保證了高頻重試任務(wù)的處理速度,又不會(huì)讓內(nèi)存被大量長(zhǎng)期任務(wù)占滿。
2. 智能調(diào)度:不做無(wú)用功
很多人寫重試邏輯就像瞎貓碰死耗子,不管三七二十一先重試了再說(shuō)。Fast-Retry 搞了個(gè)智能調(diào)度器:
- 能根據(jù)任務(wù)的失敗原因動(dòng)態(tài)調(diào)整重試策略(比如網(wǎng)絡(luò)超時(shí)可能需要等久一點(diǎn),而數(shù)據(jù)庫(kù)死鎖可能馬上重試就好)
- 會(huì)自動(dòng)避開(kāi)系統(tǒng)高峰期,比如檢測(cè)到當(dāng)前 CPU 使用率超過(guò) 80%,就會(huì)暫時(shí)放緩重試
- 支持給不同優(yōu)先級(jí)的任務(wù)排隊(duì),核心業(yè)務(wù)先重試
這就好比醫(yī)院的急診室,不是先來(lái)后到,而是根據(jù)病情緊急程度安排就診。
3. 異步化 + 批量處理:效率拉滿
Fast-Retry 內(nèi)部用了 - eventloop 模型,就像餐廳里的傳菜員,一個(gè)人能服務(wù)好幾桌客人。所有重試操作都是異步的,不會(huì)阻塞業(yè)務(wù)線程。
同時(shí)它還會(huì)把時(shí)間相近的重試任務(wù)批量處理,比如 100 個(gè)任務(wù)都設(shè)置了 10 分鐘后重試,框架會(huì)攢到一起處理,減少 IO 開(kāi)銷。這就像外賣小哥一次多帶幾單,效率自然高。
四、功能特性:這些亮點(diǎn)讓我直呼真香
光說(shuō)理念太空泛,咱們來(lái)看看 Fast-Retry 具體有哪些讓人眼前一亮的功能:
1. 靈活到變態(tài)的重試策略
內(nèi)置 8 種重試策略,覆蓋你能想到的所有場(chǎng)景:
- 固定間隔重試:比如每隔 30 秒重試一次
- 指數(shù)退避重試:每次間隔翻倍,1 秒、2 秒、4 秒... 適合網(wǎng)絡(luò)問(wèn)題
- 隨機(jī)延遲重試:在指定范圍內(nèi)隨機(jī)間隔,避免驚群效應(yīng)
- 斐波那契重試:間隔按照 1、1、2、3、5 的規(guī)律增長(zhǎng),更科學(xué)
- 失敗次數(shù)遞增間隔:失敗次數(shù)越多,間隔越長(zhǎng)
- cron 表達(dá)式重試:精確到分秒的定時(shí)重試,比如每天凌晨 3 點(diǎn)重試
- 回調(diào)通知重試:直到收到特定回調(diào)才停止重試
- 自定義腳本重試:用 Groovy 腳本寫重試條件,想多復(fù)雜就多復(fù)雜
最牛的是,這些策略還能組合使用。比如可以先指數(shù)退避重試 5 次,再轉(zhuǎn)成每天凌晨重試,簡(jiǎn)直不要太靈活。
2. 分布式環(huán)境下的一致性保障
在微服務(wù)架構(gòu)里,分布式重試是個(gè)大難題。你怕不怕這樣的情況:
- 兩個(gè)服務(wù)節(jié)點(diǎn)同時(shí)重試同一個(gè)任務(wù),導(dǎo)致數(shù)據(jù)重復(fù)處理?
- 任務(wù)存在本地,某個(gè)節(jié)點(diǎn)掛了,任務(wù)就永遠(yuǎn)丟了?
Fast-Retry 用了這幾招解決分布式問(wèn)題:
- 基于 Redis 實(shí)現(xiàn)分布式鎖,確保一個(gè)任務(wù)同一時(shí)間只有一個(gè)節(jié)點(diǎn)在處理
- 支持把任務(wù)存到 MongoDB/MySQL 等分布式存儲(chǔ),節(jié)點(diǎn)掛了其他節(jié)點(diǎn)能接著來(lái)
- 內(nèi)置冪等性檢查,就算不小心重復(fù)重試了,也不會(huì)產(chǎn)生副作用
3. 全方位監(jiān)控:一切盡在掌握
用重試框架最怕的就是 "黑箱操作",不知道任務(wù)重試得怎么樣了。Fast-Retry 在監(jiān)控這塊做得是真到位:
- 實(shí)時(shí)統(tǒng)計(jì):成功數(shù)、失敗數(shù)、重試中、平均重試次數(shù)等核心指標(biāo)
- 可視化面板:用 Spring Boot Admin 就能看各種曲線圖
- 告警機(jī)制:可以配置當(dāng)失敗率超過(guò)閾值時(shí)發(fā)郵件 / 釘釘
- 任務(wù)追蹤:每個(gè)任務(wù)的每次重試記錄都能查到,包括失敗原因、耗時(shí)等
有了這些,老板再問(wèn)你 "那個(gè)任務(wù)到底怎么樣了",你就能胸有成竹地給他看數(shù)據(jù)了。
4. 無(wú)縫集成:不侵入業(yè)務(wù)代碼
這一點(diǎn)必須夸!Fast-Retry 采用了 AOP 思想,幾乎不用改業(yè)務(wù)代碼就能接入。
比如原來(lái)的支付回調(diào)方法:
public void handlePaymentCallback(String orderId) {
// 處理邏輯
}想加重試?只需要加個(gè)注解:
@Retryable(
strategy = "exponential", // 指數(shù)退避策略
maxAttempts = 10, // 最多重試10次
retryFor = {NetworkException.class, TimeoutException.class} // 哪些異常需要重試
)
public void handlePaymentCallback(String orderId) {
// 處理邏輯不變
}這就完了?對(duì),就這么簡(jiǎn)單!完全符合 "開(kāi)閉原則",業(yè)務(wù)代碼干干凈凈。
五、實(shí)戰(zhàn)演練:手把手教你用起來(lái)
光說(shuō)不練假把式,咱們來(lái)實(shí)際操作一下,看看 Fast-Retry 到底怎么用。
第一步:引入依賴
Maven 項(xiàng)目加這個(gè):
<dependency>
<groupId>com.fastretry</groupId>
<artifactId>fast-retry-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>Spring Boot 項(xiàng)目會(huì)自動(dòng)裝配,零配置啟動(dòng)。
第二步:配置重試策略
在 application.yml 里配置全局默認(rèn)策略:
fast-retry:
default-strategy: exponential # 默認(rèn)指數(shù)退避
max-attempts: 5 # 默認(rèn)最多5次
initial-interval: 1000 # 初始間隔1秒
max-interval: 60000 # 最大間隔60秒
storage:
type: redis # 用Redis存儲(chǔ)任務(wù)
redis:
host: localhost
port: 6379
monitor:
enabled: true # 開(kāi)啟監(jiān)控也可以在注解里單獨(dú)配置,覆蓋全局設(shè)置,非常靈活。
第三步:給需要重試的方法加注解
剛才已經(jīng)舉過(guò)例子了,再補(bǔ)充一個(gè)帶回調(diào)的:
@Retryable(
strategy = "fixed",
interval = 5000,
maxAttempts = 3
)
public void syncOrderToWarehouse(String orderId) {
// 調(diào)用倉(cāng)庫(kù)系統(tǒng)API
}
// 重試全部失敗后會(huì)調(diào)用這個(gè)方法
@Recover
public void recoverSyncOrder(String orderId, Exception e) {
log.error("訂單{}同步倉(cāng)庫(kù)最終失敗", orderId, e);
// 記錄到人工處理隊(duì)列
manualProcessQueue.add(orderId);
}@Recover 注解指定了最終失敗后的處理方法,一般用來(lái)記錄日志或者轉(zhuǎn)人工處理。
第四步:手動(dòng)提交重試任務(wù)
有時(shí)候我們需要在代碼里手動(dòng)觸發(fā)重試,比如捕獲異常后:
@Autowired
private RetryTemplate retryTemplate;
public void processMessage(Message msg) {
try {
// 處理消息
doProcess(msg);
} catch (Exception e) {
// 手動(dòng)提交重試
retryTemplate.submit(
RetryTask.builder()
.taskId(msg.getId()) // 唯一標(biāo)識(shí)
.targetMethod("processMessage") // 要重試的方法
.params(new Object[]{msg}) // 參數(shù)
.strategy("fibonacci") // 斐波那契策略
.maxAttempts(5)
.build()
);
}
}這樣就把失敗的消息提交到重試隊(duì)列了。
第五步:查看監(jiān)控面板
啟動(dòng)項(xiàng)目后,訪問(wèn)http://localhost:8080/fast-retry/dashboard,就能看到酷炫的監(jiān)控面板:
- 左側(cè)是實(shí)時(shí)統(tǒng)計(jì):總?cè)蝿?wù)數(shù)、成功數(shù)、失敗數(shù)
- 中間是重試趨勢(shì)圖:每小時(shí)重試次數(shù)變化
- 右側(cè)是異常分布:各種失敗原因的占比
- 下面是待重試任務(wù)列表:可以手動(dòng)觸發(fā)或取消
有了這個(gè)面板,重試情況一目了然,心里踏實(shí)多了。
六、壓測(cè)數(shù)據(jù):是騾子是馬拉出來(lái)遛遛
光說(shuō)好用不行,得用數(shù)據(jù)說(shuō)話。我們?cè)跍y(cè)試環(huán)境做了個(gè)壓測(cè):
- 服務(wù)器配置:4 核 8G 的云服務(wù)器
- 測(cè)試場(chǎng)景:模擬 100 萬(wàn)條需要重試的任務(wù),每條任務(wù)重試 3 次
- 對(duì)比框架:Spring Retry、Guava Retry、自己寫的重試邏輯
結(jié)果如下:
框架 | 總處理時(shí)間 | 峰值內(nèi)存 | 成功率 |
Fast-Retry | 8 分 23 秒 | 1.2G | 99.8% |
Spring Retry | 35 分 11 秒 | 3.8G | 97.2% |
Guava Retry | 42 分 05 秒 | 4.2G | 96.5% |
自研邏輯 | 超時(shí)未完成 | 內(nèi)存溢出 | - |
差距是不是很明顯?Fast-Retry 處理百萬(wàn)級(jí)任務(wù)居然只用了 8 分鐘,而且內(nèi)存占用很低。這得益于它的分級(jí)存儲(chǔ)和異步處理機(jī)制,把系統(tǒng)資源用到了刀刃上。
更關(guān)鍵的是成功率,F(xiàn)ast-Retry 因?yàn)橛兄悄苤卦嚥呗?,比其他框架高出不少。別小看這 2-3 個(gè)百分點(diǎn),在百萬(wàn)級(jí)任務(wù)里就是幾千條數(shù)據(jù),能幫公司減少不少損失。
七、高級(jí)玩法:這些技巧讓你用得更溜
如果你已經(jīng)上手了 Fast-Retry,想玩得更高級(jí),可以試試這些技巧:
1. 自定義重試策略
如果內(nèi)置的 8 種策略還滿足不了你,可以自己寫:
public class MyRetryStrategy implements RetryStrategy {
@Override
public long calculateNextDelay(int attemptNumber, Throwable lastException) {
// 自定義邏輯:根據(jù)異常類型動(dòng)態(tài)調(diào)整間隔
if (lastException instanceof DatabaseException) {
return 1000 * attemptNumber;
} else if (lastException instanceof NetworkException) {
return 5000 * (attemptNumber * 2);
}
return 3000;
}
}
// 注冊(cè)到Spring容器
@Bean
public MyRetryStrategy myRetryStrategy() {
return new MyRetryStrategy();
}然后在注解里直接用:
@Retryable(strategy = "myRetryStrategy")2. 任務(wù)優(yōu)先級(jí)
給重要的任務(wù)設(shè)置高優(yōu)先級(jí),讓它們先被處理:
@Retryable(
strategy = "fixed",
interval = 3000,
priority = 1 // 數(shù)字越小優(yōu)先級(jí)越高,默認(rèn)是5
)
public void processVipOrder(String orderId) {
// VIP訂單處理,優(yōu)先級(jí)高
}3. 動(dòng)態(tài)調(diào)整重試參數(shù)
在運(yùn)行中可以動(dòng)態(tài)修改重試參數(shù),比如發(fā)現(xiàn)某個(gè)接口特別不穩(wěn)定:
@Autowired
private RetryAdminService retryAdminService;
public void adjustStrategy() {
// 動(dòng)態(tài)修改策略參數(shù)
retryAdminService.updateStrategyConfig(
"exponential",
Collections.singletonMap("maxInterval", 120000) // 最大間隔改為120秒
);
}不用重啟服務(wù),實(shí)時(shí)生效,生產(chǎn)環(huán)境必備技能。
4. 與消息隊(duì)列配合
把 Fast-Retry 和 Kafka/RabbitMQ 結(jié)合起來(lái),威力更大:
- 消費(fèi)消息失敗時(shí),提交到 Fast-Retry 而不是直接 nack
- 重試成功后,再 ack 消息
- 徹底失敗的任務(wù),發(fā)送到死信隊(duì)列
這樣既利用了消息隊(duì)列的可靠性,又發(fā)揮了 Fast-Retry 的智能重試能力。
八、踩坑指南:這些坑我已經(jīng)替你踩過(guò)了
用了大半年 Fast-Retry,踩過(guò)不少坑,分享給你們避避坑:
- 任務(wù) ID 必須唯一:如果兩個(gè)任務(wù)用了同一個(gè) taskId,會(huì)被認(rèn)為是同一個(gè)任務(wù),導(dǎo)致重試混亂。最好用 UUID 或者業(yè)務(wù)唯一標(biāo)識(shí)(如訂單號(hào))。
- 別濫用重試:不是所有失敗都需要重試,比如參數(shù)錯(cuò)誤這種問(wèn)題,重試一萬(wàn)次也沒(méi)用,只會(huì)浪費(fèi)資源。
- 注意冪等性:重試的方法一定要保證冪等,不然可能出現(xiàn)重復(fù)扣款、重復(fù)下單等嚴(yán)重問(wèn)題。
- 初始間隔別設(shè)太近:有些接口對(duì) QPS 有限制,重試間隔太短容易觸發(fā)限流,反而適得其反。
- 持久化配置要做好:生產(chǎn)環(huán)境一定要用分布式存儲(chǔ)(Redis/MongoDB),別用內(nèi)存存儲(chǔ),不然服務(wù)重啟任務(wù)就丟了。
- 監(jiān)控告警不能少:一定要配置告警,不然大量任務(wù)失敗了你都不知道,等用戶投訴就晚了。
總結(jié):為什么說(shuō)它真香?
用了這么多重試方案,F(xiàn)ast-Retry 給我的感覺(jué)就是:專業(yè)的事就該交給專業(yè)的工具。
它解決了重試場(chǎng)景中的核心痛點(diǎn):高并發(fā)處理、靈活策略、可靠存儲(chǔ)、全面監(jiān)控。而且用起來(lái)特別簡(jiǎn)單,學(xué)習(xí)成本低,這對(duì)于咱們開(kāi)發(fā)者來(lái)說(shuō)太重要了。
自從用上它,我再也不用為任務(wù)重試頭疼了,晚上睡得都香了。老板再也不用擔(dān)心數(shù)據(jù)不一致,產(chǎn)品經(jīng)理也不天天追著問(wèn)進(jìn)度了。


































