From 104ff427f95d4aae7886c8a118a1190b949812b3 Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 18 Jun 2026 11:05:22 +0800 Subject: [PATCH] perf(db): add exponential backoff retry helper with tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 引入 src/db/retry.rs,提供 base * 2^attempt + jitter 的退避序列, 取代 pool.rs 中固定 2s 重试。MAX_RETRIES 将在下一个提交中被 get_conn 使用。 --- src/db/mod.rs | 4 ++++ src/db/retry.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/db/retry.rs diff --git a/src/db/mod.rs b/src/db/mod.rs index fe935ab..01ff798 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -12,6 +12,10 @@ #[cfg(feature = "server")] pub mod pool; +/// 连接获取的指数退避重试策略,仅在启用 server feature 时编译。 +#[cfg(feature = "server")] +pub mod retry; + /// 占位连接池实现,仅在不启用 server feature 时编译。 /// /// `DummyPool` 是一个最小 stub:它提供与真实连接池相同的公开接口形状 diff --git a/src/db/retry.rs b/src/db/retry.rs new file mode 100644 index 0000000..3cfdc31 --- /dev/null +++ b/src/db/retry.rs @@ -0,0 +1,57 @@ +//! 数据库连接获取的指数退避重试策略。 +//! +//! 取代 pool.rs 中固定 2s 间隔的重试:每次重试间隔 = base * 2^attempt, +//! 再叠加 [0, base) 的随机 jitter,避免多请求同步重试形成惊群。 +//! 仅在 `feature = "server"` 时编译。 + +#[cfg(feature = "server")] +use std::time::Duration; + +/// 退避基准间隔(首次重试前的等待约为 base,随后翻倍)。 +#[cfg(feature = "server")] +const BASE_BACKOFF: Duration = Duration::from_millis(200); + +/// 最大重试次数(不含首次尝试)。 +#[cfg(feature = "server")] +pub const MAX_RETRIES: u32 = 3; + +/// 计算第 `attempt` 次重试(attempt 从 0 开始)前的等待时长。 +/// +/// 公式:base * 2^attempt,再叠加 [0, base) 的 jitter。 +/// jitter 由调用方传入的随机比例 [0.0, 1.0) 决定,便于测试时锁定为 0。 +#[cfg(feature = "server")] +pub fn backoff_for(attempt: u32, jitter_ratio: f64) -> Duration { + debug_assert!((0.0..=1.0).contains(&jitter_ratio)); + let exp = u32::checked_shl(1, attempt).unwrap_or(1 << 30); + let base_ms = BASE_BACKOFF.as_millis() as u64; + let core = base_ms.saturating_mul(exp as u64); + let jitter = (base_ms as f64 * jitter_ratio) as u64; + Duration::from_millis(core.saturating_add(jitter)) +} + +#[cfg(all(test, feature = "server"))] +mod tests { + use super::*; + + #[test] + fn backoff_grows_exponentially_without_jitter() { + // jitter=0 时序列应严格翻倍:200, 400, 800 ms。 + assert_eq!(backoff_for(0, 0.0), Duration::from_millis(200)); + assert_eq!(backoff_for(1, 0.0), Duration::from_millis(400)); + assert_eq!(backoff_for(2, 0.0), Duration::from_millis(800)); + } + + #[test] + fn backoff_includes_jitter_within_base_range() { + // jitter_ratio=0.5 时在 core 上叠加 base*0.5 = 100ms。 + assert_eq!(backoff_for(0, 0.5), Duration::from_millis(300)); + assert_eq!(backoff_for(1, 0.5), Duration::from_millis(500)); + } + + #[test] + fn backoff_clamps_large_attempt() { + // 超大 attempt 不应 panic,应靠 saturating 保护返回一个大但有界的值。 + let d = backoff_for(40, 0.0); + assert!(d.as_millis() > 0); + } +}