HTTP 失敗重試(重發)方案

? ?在 Qt 網絡開發中,使用 QNetworkAccessManager 進行 HTTP 請求時,可能會遇到網絡超時、服務器錯誤等情況。為了提高請求的可靠性,可以實現 HTTP 失敗重試(重發) 機制。下面介紹幾種常見的 失敗重發方案

單請求

1. 基本重試策略

適用于: 短暫的網絡抖動、服務器瞬時不可用等情況。

實現思路:

  • 監聽?QNetworkReply?的?finished()?信號,檢查?error()?是否為失敗狀態。
  • 如果失敗,等待一段時間后重新發送請求。
  • 設定?最大重試次數,避免無限循環重試。
  • #include <QCoreApplication>
    #include <QNetworkAccessManager>
    #include <QNetworkRequest>
    #include <QNetworkReply>
    #include <QTimer>
    #include <QDebug>class HttpRetryHandler : public QObject {Q_OBJECT
    public:HttpRetryHandler(QObject *parent = nullptr): QObject(parent), networkManager(new QNetworkAccessManager(this)), retryCount(0) {connect(networkManager, &QNetworkAccessManager::finished, this, &HttpRetryHandler::onReplyFinished);}void sendRequest() {QNetworkRequest request(QUrl("https://example.com/api"));request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");reply = networkManager->get(request);connect(reply, &QNetworkReply::finished, this, &HttpRetryHandler::onReplyFinished);}private slots:void onReplyFinished() {if (reply->error() == QNetworkReply::NoError) {qDebug() << "Request successful:" << reply->readAll();reply->deleteLater();} else {qDebug() << "Request failed:" << reply->errorString();if (retryCount < maxRetries) {retryCount++;qDebug() << "Retrying..." << retryCount;QTimer::singleShot(retryInterval, this, &HttpRetryHandler::sendRequest);} else {qDebug() << "Max retries reached. Giving up.";}}}private:QNetworkAccessManager *networkManager;QNetworkReply *reply;int retryCount;const int maxRetries = 3; // 最大重試次數const int retryInterval = 2000; // 失敗后等待 2 秒再重試
    };int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);HttpRetryHandler handler;handler.sendRequest();return a.exec();
    }#include "main.moc"

    關鍵點

  • 設置最大重試次數 (maxRetries),避免無限重試。
  • 使用?QTimer::singleShot()?延遲重試,避免立即重試導致服務器壓力增大。

2. 指數退避(Exponential Backoff)重試

適用于: API 速率限制、網絡負載較高時的自動恢復。

實現思路:

  • 每次重試時,等待時間?指數增長(如?2^retryCount)。
  • 可以設置一個?最大等待時間,避免等待過長。
  • 適用于?API 限流(如 HTTP 429 Too Many Requests)或?服務器負載高的情況。
void retryWithBackoff() {if (retryCount < maxRetries) {int delay = qMin(initialRetryInterval * (1 << retryCount), maxRetryInterval);qDebug() << "Retrying in" << delay << "ms...";QTimer::singleShot(delay, this, &HttpRetryHandler::sendRequest);retryCount++;} else {qDebug() << "Max retries reached. Stopping.";}
}

關鍵點

  • 指數增長等待時間delay = initialRetryInterval * (1 << retryCount)
  • 避免等待過長:使用?qMin()?限制最大重試間隔。

3. 僅對特定 HTTP 狀態碼重試

適用于:

  • 服務器錯誤(HTTP 5xx,如?500502503)。
  • 速率限制(HTTP?429 Too Many Requests)。
void onReplyFinished() {int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();if (reply->error() == QNetworkReply::NoError) {qDebug() << "Success:" << reply->readAll();} else if (statusCode == 500 || statusCode == 502 || statusCode == 503 || statusCode == 429) {qDebug() << "Server error, retrying...";retryWithBackoff();} else {qDebug() << "Request failed permanently:" << reply->errorString();}
}

關鍵點

  • 避免對所有錯誤重試,只對?5xx/429?進行重試,減少無效請求。

4. 結合?QNetworkReply::redirected()?處理重定向

適用于: 遇到 301/302/307 重定向 時自動跟隨新 URL。

void onReplyFinished() {QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);if (!redirectTarget.isNull()) {QUrl newUrl = redirectTarget.toUrl();qDebug() << "Redirected to:" << newUrl;request.setUrl(newUrl);sendRequest();return;}
}

關鍵點

  • 檢查?RedirectionTargetAttribute?并重新發起請求。

總結

方案適用情況優缺點
基本重試網絡波動、短暫的服務器異常簡單有效,但可能導致過多請求
指數退避API 限流、服務器負載高避免頻繁請求,適應性更強
特定狀態碼重試5xx/429 錯誤只在必要時重試,減少無效請求
自動重定向301/302/307 響應處理 URL 變更,防止訪問失敗

在實際開發中,可以 結合多種策略

  • 對?5xx/429?進行指數退避重試
  • 對?301/302?自動重定向
  • 對不可恢復錯誤(如?403/404)直接放棄

多請求

如果有多個失敗請求需要重試,可以使用 隊列管理 機制來處理所有失敗的請求,而不是單獨重試每個請求。這可以確保 多個請求按順序重試,并且不會讓服務器負擔過重。或者可以讓請求并行。

方案 1:使用隊列逐個重試

  • 適用于:?不希望所有請求同時重試,逐個處理失敗請求,降低服務器壓力。

實現思路:

  1. 維護一個?QQueue<QNetworkRequest>?隊列,存儲失敗的請求
  2. 失敗請求會進入隊列,并按照 FIFO(先進先出)順序依次重試。
  3. 使用?QTimer?控制指數退避重試時間依次出隊并重試
  4. 如果重試成功,從隊列中移除請求,否則增加重試次數,重新排隊。
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QTimer>
#include <QQueue>
#include <QDebug>
#include <QRandomGenerator>class HttpRetryHandler : public QObject {Q_OBJECT
public:HttpRetryHandler(QObject *parent = nullptr): QObject(parent), networkManager(new QNetworkAccessManager(this)) {connect(networkManager, &QNetworkAccessManager::finished, this, &HttpRetryHandler::onReplyFinished);}void sendRequest(const QUrl &url) {QNetworkRequest request(url);request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");QNetworkReply *reply = networkManager->get(request);requestMap.insert(reply, {request, 0}); // 記錄請求和當前重試次數connect(reply, &QNetworkReply::finished, this, &HttpRetryHandler::onReplyFinished);}private slots:void onReplyFinished() {QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());if (!reply) return;int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();auto it = requestMap.find(reply);if (reply->error() == QNetworkReply::NoError) {qDebug() << "Request successful: " << reply->readAll();requestMap.remove(reply);} else if (statusCode == 500 || statusCode == 502 || statusCode == 503 || statusCode == 429) {// 服務器錯誤或限流,加入重試隊列qDebug() << "Server error" << statusCode << ", adding request to retry queue...";retryQueue.enqueue(it.value());processRetryQueue();  // 處理隊列} else {qDebug() << "Request failed permanently: " << reply->errorString();}reply->deleteLater();}void processRetryQueue() {if (retryQueue.isEmpty() || retrying) return;retrying = true;RequestData requestData = retryQueue.dequeue();if (requestData.retryCount < maxRetries) {int delay = qMin(initialRetryInterval * (1 << requestData.retryCount) + getJitter(), maxRetryInterval);qDebug() << "Retrying request in" << delay << "ms...";QTimer::singleShot(delay, this, [=]() {sendRequest(requestData.request.url());retrying = false;processRetryQueue();  // 繼續處理下一個請求});requestData.retryCount++;} else {qDebug() << "Max retries reached for request: " << requestData.request.url();retrying = false;}}private:struct RequestData {QNetworkRequest request;int retryCount;};QNetworkAccessManager *networkManager;QMap<QNetworkReply *, RequestData> requestMap;QQueue<RequestData> retryQueue;bool retrying = false;const int maxRetries = 5; // 最大重試次數const int initialRetryInterval = 1000; // 初始重試間隔 1 秒const int maxRetryInterval = 16000; // 最大重試間隔 16 秒int getJitter() { return QRandomGenerator::global()->bounded(500); } // 額外隨機延遲 0~500ms
};int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);HttpRetryHandler handler;// 發送多個請求handler.sendRequest(QUrl("https://example.com/api1"));handler.sendRequest(QUrl("https://example.com/api2"));handler.sendRequest(QUrl("https://example.com/api3"));return a.exec();
}#include "main.moc"

? 關鍵點

(1) 多個請求失敗后的處理
  • 使用?QMap<QNetworkReply *, RequestData>?記錄請求信息
  • QQueue<RequestData>?存儲失敗的請求
  • processRetryQueue()?依次從隊列取出請求并重試
(2) 防止所有請求同時重試
  • 使用?bool retrying?確保一次只處理一個重試請求
  • 指數退避 (2^retryCount) 并增加隨機抖動
**(3)?重試失敗的請求
  • 如果達到?maxRetries,則放棄重試
  • 否則等待?delay?時間后重試

方案 2:并行重試多個請求

如果你不想讓請求 順序重試,而是 并行重試多個請求,可以 使用多個 QTimer 并行調度,同時讓多個請求進行指數退避重試。

void processRetryQueue() {while (!retryQueue.isEmpty()) {RequestData requestData = retryQueue.dequeue();if (requestData.retryCount < maxRetries) {int delay = qMin(initialRetryInterval * (1 << requestData.retryCount) + getJitter(), maxRetryInterval);qDebug() << "Retrying request to " << requestData.request.url() << " in " << delay << "ms...";QTimer::singleShot(delay, this, [=]() {sendRequest(requestData.request.url());});requestData.retryCount++;} else {qDebug() << "Max retries reached for request: " << requestData.request.url();}}
}

區別:

  • 多個請求可以同時重試(不會等待上一個重試完成)。
  • 所有請求仍然使用指數退避時間控制頻率

5. 結論

方案優點缺點
順序重試(隊列模式)減少服務器壓力、保證請求順序可能導致某些請求等待較久
并行重試(多定時器)提高吞吐量,適合高并發可能讓服務器短時間內收到大量重試請求

?

?

注:本人也在學習中,如果有錯誤,請指出!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/72830.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/72830.shtml
英文地址,請注明出處:http://en.pswp.cn/web/72830.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大白話詳細解讀React框架的diffing算法

1. Diffing 算法是什么&#xff1f; Diffing 算法是 React 用來比較虛擬 DOM&#xff08;Virtual DOM&#xff09;樹的一種算法。它的作用是找出前后兩次渲染之間的差異&#xff08;diff&#xff09;&#xff0c;然后只更新這些差異部分&#xff0c;而不是重新渲染整個頁面。 …

【Linux內核系列】:動靜態庫詳解

&#x1f525; 本文專欄&#xff1a;Linux &#x1f338;作者主頁&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客勵志語錄&#xff1a; 有些鳥兒是注定是關不住的&#xff0c;因為它們的每一片羽翼都沾滿了自由的光輝 ★★★ 本文前置知識&#xff1a; 編譯與鏈接的過程…

深度解讀DeepSeek部署使用安全(48頁PPT)(文末有下載方式)

深度解讀DeepSeek&#xff1a;部署、使用與安全 詳細資料請看本解讀文章的最后內容。 引言 DeepSeek作為一款先進的人工智能模型&#xff0c;其部署、使用與安全性是用戶最為關注的三大核心問題。本文將從本地化部署、使用方法與技巧、以及安全性三個方面&#xff0c;對Deep…

【詳細解決】pycharm 終端出現報錯:“Failed : 無法將“Failed”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱。

昨天在終端一頓操作后突然打開pycharm時就開始報錯&#xff1a; 無法將“Failed”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱。請檢查名稱的拼寫&#xff0c;如果包括路徑&#xff0c;請確保路徑正確&#xff0c;然后再試一次。 所在位置 行:1 字符: 1 Failed to act…

【電路筆記】-D型觸發器

D型觸發器 文章目錄 D型觸發器1、概述2、主從D觸發器3、使用D型觸發器進行分頻4、D觸發器作為數據鎖存器5、透明數據鎖存器6、總結D型觸發器是一種改進的置位-復位觸發器,通過增加一個反相器來防止S和R輸入處于相同的邏輯電平。 1、概述 D型觸發器克服了基本SR NAND門雙穩態電…

智慧共享桿:城市智能化管理的 “多面手”

智慧共享桿&#xff1a;城市智能化管理的 “多面手” 在智慧城市建設的進程中&#xff0c;智慧共享桿憑借其多功能與集約化的特性&#xff0c;逐漸成為城市基礎設施建設領域的重點關注對象。它不僅革新了傳統路燈桿的固有模式&#xff0c;更為城市的高效管理與便捷服務開創了全…

【Tips】pip臨時換源

pip換源網站 用法&#xff1a; pip install xxx庫 -i https://pypi.tuna.tsinghua.edu.cn/simple https://pypi.tuna.tsinghua.edu.cn/simplehttps://mirrors.aliyun.com/pypi/simplehttps://pypi.douban.com/simplehttps://pypi.mirrors.ustc.edu.cn/simplehttps://mirrors.…

AcWing 838:堆排序 ← 數組模擬

【題目來源】 https://www.acwing.com/problem/content/840/ 【題目描述】 輸入一個長度為 n 的整數數列&#xff0c;從小到大輸出前 m 小的數。 【輸入格式】 第一行包含整數 n 和 m。 第二行包含 n 個整數&#xff0c;表示整數數列。 【輸出格式】 共一行&#xff0c;包含…

Microchip AN1477中關于LLC數字補償器的疑問

最近在學習Microchip的AN1477關于LLC的功率級傳遞函數推導及數字補償器設計&#xff0c;對其中的2P2Z數字補償器的系數有一些困惑。我在MATLAB中運行了源程序提供的VMC_LLC.m文件&#xff0c;發現有些地方和AN1477中的結果不一致。現在把相關有疑問的地方列舉出來&#xff0c;也…

【原創】使用ElasticSearch存儲向量實現大模型RAG

一、概述 檢索增強生成&#xff08;Retrieval-Augmented Generation&#xff0c;RAG&#xff09;已成為大型語言模型&#xff08;LLM&#xff09;應用的重要架構&#xff0c;通過結合外部知識庫來增強模型的回答能力&#xff0c;特別是在處理專業領域知識、最新信息或企業私有數…

分享下web3j 常見用法

轉賬 fun sendEthTransaction(privateKey: String,toAddress: String,amount: BigDecimal) {//chainIdval chainId:Long 1//url 可以從https://chainlist.org/里面獲取可用節點//eth轉賬&#xff0c;bnb同理&#xff0c;但需發送到bnb對應節點val url "https://xxx"…

《真·滕王閣序》

《滕工閣序》 西二旗故地&#xff0c;后廠新府。 星分百度網易&#xff0c;地接騰訊阿里。 襟PRD而帶OKR&#xff0c;控需求以引撕逼。 物華天寶&#xff0c;龍光射工卡芯片&#xff1b;人杰地靈&#xff0c;徐孺坐產品經理之榻。 工位霧列&#xff0c;碼農星馳。 臺積電…

云盤搭建筆記

報錯問題&#xff1a; No input file specified. 偽靜態 location / {if (!-e $request_filename) { rewrite ^(.*)$ /index.php/$1 last;break;} } location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php/$1 last; break; } } 設…

如何打造安全穩定的亞馬遜采購測評自養號下單系統?

在當今的電商領域&#xff0c;亞馬遜作為全球領先的在線購物平臺&#xff0c;其商品種類繁多&#xff0c;用戶基數龐大&#xff0c;成為了眾多商家和消費者的首選。而對于一些需要進行商品測評或市場調研的用戶來說&#xff0c;擁有一個穩定、安全的亞馬遜賬號體系顯得尤為重要…

c語言數據結構 單循環鏈表設計(完整代碼)

單鏈表的增刪查改代碼&#xff1a; 1.創建結構體 // 結構體類型的創建 struct node {int data; // 數據域struct node *next; // 指針域 };2.創建節點&#xff0c;節點的存儲在malloc申請的空間內&#xff0c;也就是堆空間。 // 創建節點 struct node *create_node…

筆記本電腦關不了機是怎么回事 這有解決方法

在快節奏的現代生活中&#xff0c;筆記本電腦已成為我們工作、學習和娛樂的得力助手。在使用電腦的過程中&#xff0c;筆記本電腦突然關不了機了&#xff0c;怎么回事&#xff1f;下面驅動人生就來講一講筆記本電腦不能正常關機的解決方法&#xff0c;有需要的可以來看看。 一、…

Pytest基礎使用

概述 Pytest是Python里的一個強大的測試框架,靈活易用,可以進行功能,自動化測試使用,可以與Requests,Selenium等進行結合使用,同時可以生成Html的報告。 一、Pytest的基本使用 在未指定Pytest的配置文件時,會對以下文件進行執行: test_*.py,如:test_1.py*_test.py…

服務的拆分數據的遷移

參考&#xff1a; 數據遷移調研

【動態規劃篇】91. 解碼方法

91. 解碼方法 題目鏈接&#xff1a; 91. 解碼方法 題目敘述&#xff1a; 一條包含字母 A-Z 的消息通過以下映射進行了 編碼 &#xff1a; “1” -> ‘A’ “2” -> ‘B’ … “25” -> ‘Y’ “26” -> ‘Z’ 然而&#xff0c;在解碼已編碼的消息時&#xff0c;你…

使用【docker】+【shell】腳本半自動化部署微服務項目

一.前言 以下是一個基于 ?Docker Shell腳本? 的半自動化部署方案&#xff0c;包含鏡像構建、容器管理、網絡配置和日志監控等核心功能&#xff0c;適用于大多數Web應用或微服務項目。 二?.目錄結構 三.腳本代碼實現 1.?Shell腳本實現 (deploy.sh) #!/bin/bash# 設置顏…