backon

安装

cargo add backon

cargo add tokio -F full

基本用法

函数使用

backon(以及大多数重试库)的逻辑是:1次初始尝试 + N次重试 = 总执行次数
默认重试3次

use backon::{ExponentialBuilder, Retryable};

async fn fetch_data() -> Result<String, &'static str> {
    println!("run fetch_data");
    Err("request failed")
}

#[tokio::main]
async fn main() {
    let result = fetch_data
        .retry(ExponentialBuilder::default())
        .await;

    println!("{:?}", result);
}
run fetch_data
run fetch_data
run fetch_data
run fetch_data
Err("request failed")

闭包使用

use backon::{ExponentialBuilder, Retryable};

#[tokio::main]
async fn main() {
    let result = (|| async {
        println!("trying...");

        Err::<(), &str>("temporary error")
    })
    .retry(ExponentialBuilder::default())
    .await;

    println!("{:?}", result);
}
trying...
trying...
trying...
trying...
Err("temporary error")

使用函数和闭包区别

retry需要反复调用的
如何函数有参数,那么使用闭包方式调用retry

写法 本质 是否可以 .retry(...) 原因 推荐写法
task 无参数 async 函数本身 可以 task 可以被反复调用,每次调用都会创建新的 Future task.retry(...).await
task() 调用 async 函数后得到的 Future 不适合 Future 通常只能 .await 一次,不能失败后重复执行 task.retry(...).await
fetch_text 有参数 async 函数本身 通常不能直接用 backon 需要无参任务,但 fetch_text 需要 clienturl 参数 用闭包固定参数
fetch_text(&client, url) 调用函数后得到的 Future 不适合 这是一次已经创建好的请求任务,不能反复重试 用闭包包起来

退避策略

ExponentialBuilder 指数退避

根据1 * factor ^ 0计算延迟执行秒数

ExponentialBuilder::default()

Self {
    jitter: false,
    factor: 2.0, // 表示指数退避
    min_delay: Duration::from_secs(1),
    max_delay: Some(Duration::from_secs(60)),
    max_times: Some(3),
    total_delay: None,
    seed: None,
}
use backon::{ExponentialBuilder, Retryable};
use chrono::Local; // 需要安装chrono

async fn task() -> Result<(), &'static str> {
    let now = Local::now().format("%H:%M:%S%.3f");
    println!("[{}] Executing task...", now);
    Err("failed")
}

#[tokio::main]
async fn main() {
    let result = task
        .retry(ExponentialBuilder::default())
        .await;

    println!("{:?}", result);
}
[10:32:19.816] Executing task... # 第一次失败,延迟 1 * 2^0 = 1秒
[10:32:20.817] Executing task... # 第二次失败,延迟 1 * 2^1 = 2秒
[10:32:22.818] Executing task... # 第三次失败,延迟 1 * 2^2 = 4秒
[10:32:26.819] Executing task...
Err("failed")

ConstantBuilder固定间隔重试

with_delay设置间隔时间

use backon::{ConstantBuilder, Retryable};
use chrono::Local;
use std::time::Duration;

async fn task() -> Result<(), &'static str> {
    let now = Local::now().format("%H:%M:%S%.3f");
    println!("[{}] Executing task...", now);
    Err("failed")
}

#[tokio::main]
async fn main() {
    let result = task
        .retry(ConstantBuilder::default().with_delay(Duration::from_secs(1)))
        .await;

    println!("{:?}", result);
}

[10:39:33.726] Executing task... # 间隔1秒
[10:39:34.727] Executing task... # 间隔1秒
[10:39:35.728] Executing task... # 间隔1秒
[10:39:36.729] Executing task...
Err("failed")

FibonacciBuilder 斐波那契退避

等待重试: 1, 1, 2, 3, 5, 8 ...

use backon::{FibonacciBuilder, Retryable};
use chrono::Local;

async fn task() -> Result<(), &'static str> {
    let now = Local::now().format("%H:%M:%S%.3f");
    println!("[{}] Executing task...", now);
    Err("failed")
}

#[tokio::main]
async fn main() {
    let result = task.retry(FibonacciBuilder::default().with_max_times(5)).await;

    println!("{:?}", result);
}
[10:42:20.635] Executing task... # 间隔1秒
[10:42:21.636] Executing task... # 间隔1秒
[10:42:22.637] Executing task... # 间隔2秒
[10:42:24.638] Executing task... # 间隔3秒
[10:42:27.640] Executing task... # 间隔5秒
[10:42:32.641] Executing task...
Err("failed")

ExponentialBuilder设置

with_max_times设置重试次数

use backon::{ExponentialBuilder, Retryable};
use chrono::Local; // 需要安装chrono

async fn task() -> Result<(), &'static str> {
    let now = Local::now().format("%H:%M:%S%.3f");
    println!("[{}] Executing task...", now);
    Err("failed")
}

#[tokio::main]
async fn main() {
    let result = task
        .retry(ExponentialBuilder::default().with_max_times(5))
        .await;

    println!("{:?}", result);
}
[10:46:32.229] Executing task...
[10:46:33.231] Executing task...
[10:46:35.232] Executing task...
[10:46:39.233] Executing task...
[10:46:47.234] Executing task...
[10:47:03.236] Executing task...
Err("failed")

设置最小和最大等待时间

min_delay * factor ^ n开始计算的, n从0开始
如果只大于max_delay, 按照max_delay进行等待 = min(min_delay * factor^n, max_delay)

  • with_min_delay: 最开始重试间隔不会小于 100ms
  • with_max_delay: 即使指数退避越来越大,等待时间也不会超过 2 秒
use backon::{ExponentialBuilder, Retryable};
use chrono::Local; // 需要安装chrono
use std::time::Duration;

async fn task() -> Result<(), &'static str> {
    let now = Local::now().format("%H:%M:%S%.3f");
    println!("[{}] Executing task...", now);
    Err("failed")
}

#[tokio::main]
async fn main() {
    let result = task
        .retry(ExponentialBuilder::default()
            .with_min_delay(Duration::from_millis(100))
            .with_max_delay(Duration::from_secs(2))
            .with_max_times(10)
        )
        .await;

    println!("{:?}", result);
}
[10:54:11.753] Executing task...
[10:54:11.854] Executing task...
[10:54:12.056] Executing task...
[10:54:12.456] Executing task...
[10:54:13.258] Executing task...
[10:54:14.860] Executing task...
[10:54:16.860] Executing task...
[10:54:18.862] Executing task...
[10:54:20.863] Executing task...
[10:54:22.864] Executing task...
[10:54:24.865] Executing task...
Err("failed")

添加jitter抖动

加随机抖动,避免大量请求同时重试

use backon::{ExponentialBuilder, Retryable};
use chrono::Local; // 需要安装chrono

async fn task() -> Result<(), &'static str> {
    let now = Local::now().format("%H:%M:%S%.3f");
    println!("[{}] Executing task...", now);
    Err("failed")
}

#[tokio::main]
async fn main() {
    let result = task
        .retry(ExponentialBuilder::default()
            .with_jitter()
            .with_max_times(3)
        )
        .await;

    println!("{:?}", result);
}
[10:49:18.534] Executing task... # 间隔时间有抖动
[10:49:19.998] Executing task...
[10:49:22.992] Executing task...
[10:49:30.776] Executing task...
Err("failed")

ConstantBuilder设置

ConstantBuilder::default()
    .with_delay(...) // 固定间隔
    .with_max_times(...) // 最大重试次数
    .with_jitter() // 增加抖动

FibonacciBuilder设置

min_delay * Fibonacci 数列

FibonacciBuilder::default()
    .with_min_delay(...) // 最小等待间隔时间
    .with_max_delay(...) // 最大等待间隔时间
    .with_max_times(...) // 最大重试次数
    .with_jitter() // 增加抖动

只对指定错误重试

使用when获取错误,然后判断

use backon::{ExponentialBuilder, Retryable};

#[derive(Debug)]
enum MyError {
    Timeout,
    Unauthorized,
    BadRequest,
}

async fn task() -> Result<(), MyError> {
    println!("run task");
    Err(MyError::Timeout)
}

#[tokio::main]
async fn main() {
    let result = task
        .retry(
            ExponentialBuilder::default()
                .with_max_times(3),
        )
        .when(|err| {
            // 只有在返回MyError::Unauthorized错误的时候才会重试
            matches!(err, MyError::Unauthorized)
        })
        .await;

    println!("{:?}", result);
}
run task
Err(Timeout)

传入自定义判断函数

非异步函数

use backon::{ExponentialBuilder, Retryable};

#[derive(Debug)]
enum MyError {
    Timeout,
    Unauthorized,
    BadRequest,
}

async fn task() -> Result<(), MyError> {
    println!("run task");
    Err(MyError::Timeout)
}

// 创建错误判断函数
fn should_retry(err: &MyError) -> bool {
    match err {
        MyError::Timeout | MyError::Unauthorized => {
            true
        }
        _ => {
            false
        }
    }
}

#[tokio::main]
async fn main() {
    let result = task
        .retry(
            ExponentialBuilder::default()
                .with_max_times(3),
        )
        .when(should_retry)
        .await;

    println!("{:?}", result);
}

监听重试

notify也可放入自定义输出函数(非异步函数)

use backon::{ExponentialBuilder, Retryable};
use std::time::Duration;

async fn task() -> Result<(), &'static str> {
    Err("failed")
}

#[tokio::main]
async fn main() {
    let result = task
        .retry(
            ExponentialBuilder::default()
                .with_min_delay(Duration::from_millis(100))
                .with_max_delay(Duration::from_secs(2))
                .with_max_times(3),
        )
        .notify(|err, duration| {
            eprintln!("error: {}, retry after {:?}", err, duration);
        })
        .await;

    println!("{:?}", result);
}
error: failed, retry after 100ms
error: failed, retry after 200.000003ms
error: failed, retry after 400.000006ms
Err("failed")

结合reqwest案例

  • reqwest 发 HTTP 请求
  • backon 自动重试
  • 指数退避
    • 只重试超时、连接失败、HTTP 5xx
    • 不重试 400、401、403、404 这类客户端错误
  • notify 打印每次重试日志
use backon::{ExponentialBuilder, Retryable};
use reqwest::Client;
use std::time::Duration;

async fn fetch_text(client: &Client, url: &str) -> Result<String, reqwest::Error> {
    let response = client
        .get(url)
        .send()
        .await?
        // 让 4xx / 5xx 状态码变成 reqwest::Error
        .error_for_status()?;

    let text = response.text().await?;

    Ok(text)
}

fn should_retry(err: &reqwest::Error) -> bool {
    // 超时重试
    if err.is_timeout() {
        return true;
    }

    // 连接失败重试
    if err.is_connect() {
        return true;
    }

    // HTTP 5xx 重试
    if let Some(status) = err.status() {
        return status.is_server_error();
    }

    false
}

#[tokio::main]
async fn main() {
    let client = Client::builder()
        // 单次请求超时时间,不是 backon 的重试等待时间
        .timeout(Duration::from_secs(5))
        .build()
        .unwrap();

    let url = "http://127.0.0.1:8080/status/500";

    let retry_policy = ExponentialBuilder::default()
        .with_min_delay(Duration::from_millis(200))
        .with_max_delay(Duration::from_secs(5))
        .with_factor(2.0)
        .with_max_times(3)
        .with_jitter();

    let result = (|| async {
        println!("sending request...");
        fetch_text(&client, url).await
    })
    .retry(retry_policy)
    .when(should_retry)
    .notify(|err, duration| {
        eprintln!("request failed: {err}, retry after {duration:?}");
    })
    .await;

    match result {
        Ok(text) => {
            println!("success:");
            println!("{text}");
        }
        Err(err) => {
            eprintln!("final error: {err}");
        }
    }
}
sending request...
request failed: HTTP status server error (500 INTERNAL SERVER ERROR) for url (http://127.0.0.1:8080/status/500), retry after 262.724642ms
sending request...
request failed: HTTP status server error (500 INTERNAL SERVER ERROR) for url (http://127.0.0.1:8080/status/500), retry after 570.641369ms
sending request...
request failed: HTTP status server error (500 INTERNAL SERVER ERROR) for url (http://127.0.0.1:8080/status/500), retry after 1.57267952s
sending request...
final error: HTTP status server error (500 INTERNAL SERVER ERROR) for url (http://127.0.0.1:8080/status/500)
posted @ 2026-08-11 14:31  lxd670  阅读(2)  评论(0)    收藏  举报