← Назад к вопросам
Как обеспечить бесперебойность сервиса?
2.4 Senior🔥 121 комментариев
#DevOps и инфраструктура#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Обеспечение бесперебойности сервиса (High Availability)
Бесперебойность — способность системы работать без прерываний, минимизируя downtime при сбоях. Это комплекс мер на разных уровнях.
1. Дизайн без единой точки отказа (SPOF)
Проблема:
[Клиенты]
|
v
[Единственный сервер]
|
[ОТКАЗ] → системе конец
Решение:
[Клиенты]
|
v
[Load Balancer]
|
+--- [API Server 1]
+--- [API Server 2]
+--- [API Server N]
# Конфиг nginx для health checks
upstream backend {
server api1.example.com:8000 max_fails=3 fail_timeout=10s;
server api2.example.com:8000 max_fails=3 fail_timeout=10s;
server api3.example.com:8000 max_fails=3 fail_timeout=10s;
}
server {
location / {
proxy_pass http://backend;
}
}
2. Graceful Shutdown
Проблема: при остановке сервера текущие запросы могут потеряться.
Решение:
import signal
import asyncio
from fastapi import FastAPI
from contextlib import asynccontextmanager
active_requests = set()
@asynccontextmanager
async def lifespan(app: FastAPI):
# На старте
yield
# На остановке
print("Gracefully shutting down...")
await wait_for_active_requests(timeout=30)
app = FastAPI(lifespan=lifespan)
@app.get("/api/data")
async def get_data():
request_id = uuid.uuid4()
active_requests.add(request_id)
try:
await asyncio.sleep(10) # долгая операция
return {"data": "value"}
finally:
active_requests.discard(request_id)
async def wait_for_active_requests(timeout=30):
"""Ждём пока все запросы завершатся"""
start = asyncio.get_event_loop().time()
while active_requests:
if asyncio.get_event_loop().time() - start > timeout:
print(f"Force shutdown: {len(active_requests)} requests still active")
break
await asyncio.sleep(0.1)
3. Health Checks и Monitoring
from fastapi import FastAPI
from datetime import datetime
app = FastAPI()
@app.get("/health")
async def health_check():
"""Liveness probe — сервис вообще живой"""
return {"status": "ok", "timestamp": datetime.utcnow()}
@app.get("/ready")
async def readiness_check():
"""Readiness probe — сервис готов принимать запросы"""
try:
# Проверяем БД
db.execute("SELECT 1")
# Проверяем Redis
redis.ping()
# Проверяем зависимости
return {"status": "ready"}
except Exception as e:
return {"status": "not ready", "reason": str(e)}, 503
В Kubernetes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
- name: app
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
4. Резервные копии и восстановление
# Автоматическое резервное копирование БД
import subprocess
import datetime
import os
def backup_database():
"""Ежедневное резервное копирование"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
backup_file = f"/backups/db_backup_{timestamp}.sql"
subprocess.run([
"pg_dump",
"-h", os.getenv("DB_HOST"),
"-U", os.getenv("DB_USER"),
os.getenv("DB_NAME")
], stdout=open(backup_file, 'w'))
# Загружаем в S3
upload_to_s3(backup_file, f"db-backups/{timestamp}.sql")
# Удаляем локальную копию
os.remove(backup_file)
# Восстановление
def restore_database(backup_file):
subprocess.run([
"psql",
"-h", os.getenv("DB_HOST"),
"-U", os.getenv("DB_USER"),
os.getenv("DB_NAME")
], stdin=open(backup_file))
5. Replication и Failover
PostgreSQL с репликацией:
Primary (Москва)
|
v [WAL реplication]
|
Standby (резервный, готов к активации)
|
(синхронная репликация: первичный ждёт подтверждения)
# Python код для failover
import paramiko
def promote_standby():
"""Переводим Standby в Primary"""
ssh = paramiko.SSHClient()
ssh.connect(standby_host)
# Команда для promotion
ssh.exec_command("pg_ctl promote -D /var/lib/postgresql/data")
ssh.close()
# Обновляем конфиг приложения
update_database_url(standby_host)
6. Circuit Breaker паттерн
from pybreaker import CircuitBreaker
import requests
# Защита от cascading failures
api_breaker = CircuitBreaker(
fail_max=5,
reset_timeout=60,
exclude=[requests.Timeout] # не считаем timeout за failure
)
@api_breaker
def call_external_api(url: str):
return requests.get(url, timeout=5)
def get_user_data(user_id: int):
try:
return call_external_api(f"https://external.api/users/{user_id}")
except CircuitBreaker.CircuitBreakerListener:
# Circuit открыт, возвращаем cached данные
return cache.get(f"user:{user_id}") or {"error": "service unavailable"}
7. Retry Logic
from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
retry_if_exception_type
)
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type(Exception)
)
def send_payment(payment_id: int):
# Retries с exponential backoff: 2s, 4s, 8s
return payment_service.process(payment_id)
# Идемпотентность
def process_payment(payment_id: int):
# Проверяем уже ли обработан
if cache.exists(f"payment:{payment_id}:processed"):
return cache.get(f"payment:{payment_id}:result")
result = send_payment(payment_id)
# Сохраняем результат
cache.setex(f"payment:{payment_id}:processed", 86400, True)
cache.setex(f"payment:{payment_id}:result", 86400, result)
return result
8. Rate Limiting и Throttling
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
@app.get("/api/data")
@limiter.limit("100/minute")
async def get_data(request):
return {"data": "value"}
# Custom rate limiting по user_id
from redis import Redis
redis = Redis()
def check_rate_limit(user_id: str, limit: int, window: int) -> bool:
key = f"rate_limit:{user_id}"
current = redis.incr(key)
if current == 1:
redis.expire(key, window)
return current <= limit
@app.post("/api/payment")
async def create_payment(payment: PaymentData, request):
user_id = request.user.id
if not check_rate_limit(user_id, limit=5, window=3600):
return {"error": "rate limit exceeded"}, 429
return process_payment(payment)
9. Мониторинг и алерты
from prometheus_client import Counter, Gauge, Histogram
# Метрики
request_count = Counter('requests_total', 'Total requests')
error_count = Counter('errors_total', 'Total errors')
active_connections = Gauge('active_connections', 'Active connections')
response_time = Histogram('response_time', 'Response time')
@app.middleware("http")
async def metrics_middleware(request, call_next):
active_connections.inc()
try:
with response_time.time():
response = await call_next(request)
request_count.inc()
return response
except Exception as e:
error_count.inc()
raise
finally:
active_connections.dec()
Prometheus alerts:
groups:
- name: alerts
rules:
- alert: HighErrorRate
expr: rate(errors_total[5m]) > 0.05
for: 5m
annotations:
summary: "High error rate ({{ $value }})"
- alert: ServiceDown
expr: up{job="api"} == 0
for: 1m
annotations:
summary: "Service {{ $labels.instance }} is down"
Архитектура HA системы
[Клиенты]
|
v
[CloudFlare / CDN]
|
v
[nginx Load Balancer (Active-Passive)] ← Health checks
|
+--- [API 1] ──┐
+--- [API 2] ──┤
+--- [API 3] ──┘
|
[PostgreSQL Primary] ← WAL Replication → [PostgreSQL Standby]
[Redis Cluster (3+ nodes)]
[Message Queue (2+ brokers)]
[Monitoring]
├── Prometheus (метрики)
├── Grafana (визуализация)
├── AlertManager (алерты)
└── ELK (логирование)
Лучшие практики
Нет SPOF — дублируй всё критичное. Graceful shutdown — корректная остановка. Health checks — liveness + readiness. Resilience patterns — circuit breaker, retry, timeout. Мониторинг — know что происходит. Backup & restore — тестируй восстановление. Chaos engineering — специально ломай систему чтобы найти слабые места.