М
Максим Иглин | Backend
@maximiglindgtl1.3K подп.
1.1Kпросмотров
88.8%от подписчиков
20 сентября 2025 г.
📷 ФотоScore: 1.3K
Jitter в ретраях: как не убить зависимый сервис при сбоях Представьте, у вас есть система с сотнями микросервисов или клиентов, которые делают синхронные HTTP-запросы, например, в сервис уведомлений. Однажды этот сервис начинает отдавать 503 Service Unavailable. И все клиенты одновременно получают ошибку и запускают повторную попытку через фиксированное время (например, через 1 секунду). - Через секунду все они шлют повторный запрос одновременно. - Сервис, который и так упал, снова перегружается. - Ретраи срабатывают синхронно → создаётся шквал запросов → сервис окончательно ложится. Это называется thundering herd problem — эффект "стада", когда множество клиентов одновременно "набегают" на ресурс. backoff и jitter Чтобы смягчить нагрузку и дать сервису шанс на восстановление, применяются два подхода: 1. Exponential backoff — каждая новая попытка делает паузу длиннее (1s → 2s → 4s → 8s) (о нем мы уже говорили в постах выше) 2. Jitter — добавление случайной задержки в ожидание между попытками Jitter размазывает повторные запросы по времени, и сервис получает не лавину, а дождь. Принцип работы jitter: Без jitter: все клиенты делают await sleep(2) → просыпаются одновременно → шлют запрос одновременно. С jitter (вариант full jitter): вместо 2s задержки делается random.uniform(0, 2) → кто-то проснётся через 0.7s, кто-то через 1.5s, кто-то через 1.9s → запросы равномерно распределяются по времени Пример на Python: retry-декоратор с jitter import asyncio import random import logging from functools import wraps from httpx import RequestError, HTTPStatusError logger = logging.getLogger(name) def retry_on_error_with_jitter( retries: int = 3, base_delay: float = 0.5, max_delay: float = 10.0, jitter: bool = True, ): """ Повторяет вызов async-функции с экспоненциальным backoff и jitter. """ def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): attempt = 0 while True: try: return await func(*args, **kwargs) except (RequestError, HTTPStatusError) as exc: status = getattr(exc.response, "status_code", None) if status and 400 <= status < 500: logger.error(f"Ошибка клиента ({status}), не ретраим") raise attempt += 1 if attempt > retries: logger.error(f"Попытки исчерпаны. Ошибка: {exc}") raise delay = base_delay * (2 ** (attempt - 1)) if jitter: delay = random.uniform(0, min(delay, max_delay)) else: delay = min(delay, max_delay) logger.warning( f"Ошибка (попытка {attempt}/{retries}), повтор через {delay:.2f}с" ) await asyncio.sleep(delay) return wrapper return decorator Пример использования с httpx @retry_on_error_with_jitter(retries=3, base_delay=1.0) async def send_email(): response = await httpx.post("http://notifications/api/send", timeout=5.0) response.raise_for_status() return response.json() Что мы получили Повторные попытки при временных ошибках Распределение повторных запросов по времени Защиту зависимого сервиса от спама Jitter – простое решение, которое помогает сервисам выживать под нагрузкой. Особенно важно его использовать в случаях, когда: у вас сотни/тысячи клиентов или микросервисов есть ретраи после сбоев сервисы могут одновременно упасть или стать недоступными Без jitter: все клиенты ретраят через одно и то же время → лавина С jitter: задержки у всех разные → нагрузка размазана → шанс на восстановление
1.1K
просмотров
3831
символов
Нет
эмодзи
Да
медиа

Другие посты @maximiglindgtl

Все посты канала →
Jitter в ретраях: как не убить зависимый сервис при сбоях Пр — @maximiglindgtl | PostSniper