84 lines
2.6 KiB
Python
84 lines
2.6 KiB
Python
|
|
"""重试策略:指数退避 + ±25% 抖动,最多重试 1 次。
|
|||
|
|
|
|||
|
|
只对 RETRYABLE_CODES 中的错误码重试,避免对永久错误做无意义重试。
|
|||
|
|
退避公式:base_delay * (2 ** attempt) * jitter
|
|||
|
|
- attempt=0: base_delay * 1 * jitter(首次重试前等待)
|
|||
|
|
- attempt=1: base_delay * 2 * jitter
|
|||
|
|
jitter 在 [0.75, 1.25] 区间均匀分布,避免雷同请求同时重试。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
import random
|
|||
|
|
|
|||
|
|
from woc_bridge.ui.errors import RETRYABLE_CODES, is_retryable
|
|||
|
|
|
|||
|
|
logger = logging.getLogger("woc-bridge")
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RetryPolicy:
|
|||
|
|
"""重试策略。
|
|||
|
|
|
|||
|
|
Attributes:
|
|||
|
|
max_attempts: 最大尝试次数(含首次),默认 2(即最多重试 1 次)
|
|||
|
|
base_delay: 基础退避秒数,默认 1.0
|
|||
|
|
max_delay: 最大退避秒数,默认 30.0
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self,
|
|||
|
|
max_attempts: int = 2,
|
|||
|
|
base_delay: float = 1.0,
|
|||
|
|
max_delay: float = 30.0,
|
|||
|
|
) -> None:
|
|||
|
|
"""初始化重试策略。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
max_attempts: 最大尝试次数(含首次),>=1
|
|||
|
|
base_delay: 基础退避秒数
|
|||
|
|
max_delay: 最大退避秒数(退避不会超过此值)
|
|||
|
|
"""
|
|||
|
|
if max_attempts < 1:
|
|||
|
|
raise ValueError("max_attempts must be >= 1")
|
|||
|
|
self.max_attempts = max_attempts
|
|||
|
|
self.base_delay = base_delay
|
|||
|
|
self.max_delay = max_delay
|
|||
|
|
|
|||
|
|
def should_retry(self, error_code: str, attempt: int) -> bool:
|
|||
|
|
"""是否应该重试。
|
|||
|
|
|
|||
|
|
条件:attempt < max_attempts - 1 AND error_code in RETRYABLE_CODES。
|
|||
|
|
|
|||
|
|
attempt 为重试序号(0 表示首次失败后考虑第一次重试);
|
|||
|
|
max_attempts 含首次尝试,故允许的重试次数为 max_attempts - 1。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
error_code: 错误码(如 SEND_TIMEOUT / WINDOW_NOT_FOUND)
|
|||
|
|
attempt: 当前已尝试次数(0 表示首次失败,考虑第一次重试)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
True 表示应重试
|
|||
|
|
"""
|
|||
|
|
if attempt >= self.max_attempts - 1:
|
|||
|
|
return False
|
|||
|
|
return is_retryable(error_code)
|
|||
|
|
|
|||
|
|
def get_delay(self, attempt: int) -> float:
|
|||
|
|
"""计算退避秒数。
|
|||
|
|
|
|||
|
|
指数退避:base_delay * (2 ** attempt)
|
|||
|
|
抖动:× [0.75, 1.25] 均匀分布
|
|||
|
|
上限:max_delay
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
attempt: 当前已尝试次数(0 表示首次失败后的第一次重试前等待)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
退避秒数
|
|||
|
|
"""
|
|||
|
|
exponential = self.base_delay * (2 ** attempt)
|
|||
|
|
jitter = random.uniform(0.75, 1.25)
|
|||
|
|
delay = min(exponential * jitter, self.max_delay)
|
|||
|
|
return delay
|