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)

浙公网安备 33010602011771号