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 需要 client、url 参数 用闭包固定参数
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  阅读(5)  评论(0)    收藏  举报