当前位置: 首页 > article >正文

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. 结论

方案优点缺点
顺序重试(队列模式)减少服务器压力、保证请求顺序可能导致某些请求等待较久
并行重试(多定时器)提高吞吐量,适合高并发可能让服务器短时间内收到大量重试请求

 

 

注:本人也在学习中,如果有错误,请指出!!!

原文地址:https://blog.csdn.net/m0_62573048/article/details/146362122
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/596460.html

相关文章:

  • PHP 应用留言板功能超全局变量数据库操作第三方插件引用
  • PRODIGY: “不折腾人”的蛋白-蛋白/蛋白-小分子结合能计算工具
  • 多线程14(哈希表与文件操作IO)
  • 数据结构(排序(上)):冒泡、选择、插入
  • Vue.js 模板语法全解析:从基础到实战应用
  • Java8 流式分组(groupingBy)与分区(partitioningBy)深度解析
  • 复现关于图片重构方向的项目
  • 在线生成自定义二维码
  • 【Linux】Hadoop-3.4.1的伪分布式集群的初步配置
  • mysql——第二课
  • spring MVC 介绍
  • Java实体类(Javabean)-编程规范
  • AI Agent设计模式 四种高层次模式以及更具体的九种模式
  • CSS 文档流:元素排列的底层逻辑与布局控制
  • Android Studio最后一个绑定JDK8的版本,但是官方下载是最新的,怎么下载Android Studio历史版本包,这篇文章帮你解决。
  • 2025年消防设施操作员考试题库及答案
  • centos 7 搭建FTP user-list用户列表
  • Spring AOP实战指南:面向切面编程精髓
  • C语言:循环控制结构习题
  • 从 0 到 1:深度学习模型,重构世界的数字蓝图