WechatOnCloud/bridge/woc_bridge/ui/retry.py

84 lines
2.6 KiB
Python
Raw Permalink Normal View History

"""重试策略:指数退避 + ±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