Как реализовать восстановление ресурсов с сервера?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Восстановление ресурсов с сервера (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 используй:
- Exponential Backoff + Jitter для повторных попыток
- Circuit Breaker для защиты системы
- Кэширование для graceful degradation
- Мониторинг/Logging для отслеживания проблем