← Назад к вопросам

Как реализовать восстановление ресурсов с сервера?

2.2 Middle🔥 151 комментариев
#API и сетевые протоколы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Восстановление ресурсов с сервера (Resource Recovery)

Что это означает

Восстановление ресурсов — это паттерн обеспечения надежности и отказоустойчивости при работе с сетевыми ресурсами. Когда запрос к серверу не удается (timeout, ошибка подключения, 5xx), нужно автоматически повторить попытку или восстановить соединение.

Основные стратегии

1. Простой Retry (повторная попытка)

Повторяем запрос несколько раз при ошибке:

async function fetchWithRetry(
  url: string,
  maxAttempts: number = 3
): Promise<Response> {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const response = await fetch(url);
      if (response.ok) return response;
      
      // Повторяем только при определённых ошибках
      if (response.status >= 500) {
        throw new Error(`Server error: ${response.status}`);
      }
      return response;
    } catch (error) {
      if (attempt === maxAttempts) throw error;
      console.log(`Attempt ${attempt} failed, retrying...`);
    }
  }
}

const data = await fetchWithRetry('https://api.example.com/users');

2. Exponential Backoff (экспоненциальный отступ)

Делаем между попытками всё больший промежуток времени, чтобы не перегружать сервер:

async function fetchWithExponentialBackoff(
  url: string,
  maxAttempts: number = 5
): Promise<Response> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      const response = await fetch(url);
      if (response.ok) return response;
      
      if (response.status >= 500) {
        throw new Error(`Server error: ${response.status}`);
      }
      return response;
    } catch (error) {
      if (attempt === maxAttempts - 1) throw error;
      
      // Exponential backoff: 100ms, 200ms, 400ms, 800ms
      const delay = Math.pow(2, attempt) * 100;
      console.log(`Retrying after ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

3. Jitter (случайная задержка)

Добавляем случайность, чтобы избежать "thundering herd" (когда все клиенты переподключаются одновременно):

function getBackoffWithJitter(attempt: number): number {
  const baseDelay = Math.pow(2, attempt) * 100;
  const jitter = Math.random() * 0.3 * baseDelay; // случайность до 30%
  return baseDelay + jitter;
}

async function fetchWithJitter(
  url: string,
  maxAttempts: number = 5
): Promise<Response> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      const response = await fetch(url);
      if (response.ok) return response;
      throw new Error(`HTTP ${response.status}`);
    } catch (error) {
      if (attempt === maxAttempts - 1) throw error;
      
      const delay = getBackoffWithJitter(attempt);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

4. Circuit Breaker (защита от каскадного отказа)

Если сервер часто возвращает ошибки, мы останавливаем запросы на время, чтобы дать ему восстановиться:

class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime = 0;
  private isOpen = false;

  constructor(
    private failureThreshold: number = 5,
    private resetTimeoutMs: number = 60000 // 1 минута
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    // Проверяем, нужно ли закрыть circuit
    if (this.isOpen) {
      const timeSinceFailure = Date.now() - this.lastFailureTime;
      if (timeSinceFailure > this.resetTimeoutMs) {
        // Пытаемся восстановиться
        this.isOpen = false;
        this.failureCount = 0;
      } else {
        throw new Error('Circuit breaker is open. Service unavailable.');
      }
    }

    try {
      const result = await fn();
      // Успех - сбрасываем счетчик
      this.failureCount = 0;
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();

      if (this.failureCount >= this.failureThreshold) {
        this.isOpen = true;
      }
      throw error;
    }
  }
}

const breaker = new CircuitBreaker(5, 60000);

try {
  const data = await breaker.execute(() => 
    fetch('https://api.example.com/users')
  );
} catch (error) {
  console.error('Service unavailable:', error);
}

5. Использование готовых библиотек

axios с retry

import axios from 'axios';
import axiosRetry from 'axios-retry';

// Включаем автоматический retry
axiosRetry(axios, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    // Повторяем при сетевых ошибках или 5xx
    return !error.response || error.response.status >= 500;
  }
});

const response = await axios.get('https://api.example.com/users');

node-fetch с retry

import fetch from 'node-fetch';

async function fetchWithRetry(url, options = {}) {
  const maxRetries = 3;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) return response;
      if (response.status >= 500) throw new Error('Server error');
      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
    }
  }
}

6. Graceful Degradation (деградация сервиса)

Если не можем получить свежие данные, используем кэшированные данные:

class CachedFetcher {
  private cache: Map<string, any> = new Map();
  private cacheTTL = 5 * 60 * 1000; // 5 минут

  async fetch(url: string): Promise<any> {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      
      const data = await response.json();
      this.cache.set(url, data);
      return data;
    } catch (error) {
      // Если ошибка, возвращаем кэш если есть
      if (this.cache.has(url)) {
        console.warn(`Using cached data for ${url}`);
        return this.cache.get(url);
      }
      throw error;
    }
  }
}

7. Heartbeat (проверка жизнеспособности)

Периодически проверяем, жив ли сервис:

class HealthCheckManager {
  private isHealthy = true;

  startHeartbeat(url: string, intervalMs: number = 10000) {
    setInterval(async () => {
      try {
        const response = await fetch(url);
        this.isHealthy = response.ok;
      } catch (error) {
        this.isHealthy = false;
        console.error('Health check failed:', error);
      }
    }, intervalMs);
  }

  getStatus(): boolean {
    return this.isHealthy;
  }
}

8. Комплексное решение

Объединяем все стратегии:

class RobustFetcher {
  private circuitBreaker = new CircuitBreaker(5, 60000);
  private cache = new Map();

  async fetch(url: string): Promise<any> {
    try {
      // Используем circuit breaker
      return await this.circuitBreaker.execute(() =>
        this.fetchWithRetry(url)
      );
    } catch (error) {
      // Graceful degradation
      if (this.cache.has(url)) {
        console.warn('Using cache due to error');
        return this.cache.get(url);
      }
      throw error;
    }
  }

  private async fetchWithRetry(url: string): Promise<any> {
    for (let i = 0; i < 3; i++) {
      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        
        const data = await response.json();
        this.cache.set(url, data);
        return data;
      } catch (error) {
        if (i === 2) throw error;
        await new Promise(r => 
          setTimeout(r, Math.pow(2, i) * 100 + Math.random() * 100)
        );
      }
    }
  }
}

Когда использовать каждую стратегию

СтратегияКогда использовать
RetryВременные сетевые сбои
BackoffПерегруженный сервер (нужно дать ему передышку)
JitterМасштабируемые системы с множеством клиентов
Circuit BreakerЗащита от каскадного отказа в микросервисах
CacheДеградация сервиса, когда удаленный сервис недоступен
HeartbeatПостоянный мониторинг здоровья сервиса

Вывод

Восстановление ресурсов — это не один метод, а комбинация стратегий. В production используй:

  1. Exponential Backoff + Jitter для повторных попыток
  2. Circuit Breaker для защиты системы
  3. Кэширование для graceful degradation
  4. Мониторинг/Logging для отслеживания проблем
Как реализовать восстановление ресурсов с сервера? | PrepBro