Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Processes — stateless процессы
processes

Processes — stateless процессы

Выполнение приложения в виде stateless процессов без общего состояния

Processes: Stateless процессы

Выполняйте приложение в виде stateless процессов без общего состояния

#Суть принципа

Processes — шестой фактор 12-Factor App. Принцип гласит:

Приложение должно выполняться в виде одного или более stateless процессов. Процессы не хранят состояние между запросами — данные сохраняются во внешних сервисах (БД, кэш).

# ❌ Неправильно: состояние в памяти процесса logged_in_users = set() # ❌ Потеряется при перезапуске @app.route('/login') def login(): logged_in_users.add(user_id) return 'Logged in' # ✅ Правильно: состояние во внешнем сервисе (Redis) import redis redis_client = redis.from_url(os.environ['REDIS_URL']) @app.route('/login') def login(): redis_client.set(f'session:{user_id}', 'active') return 'Logged in'

#Что такое stateless процесс?

Stateless процесс — это процесс, который:

  1. Не хранит данные между запросами — каждый запрос обрабатывается независимо
  2. Не полагается на память процесса — данные во внешних сервисах
  3. Может быть перезапущен без потери данных — состояние сохраняется внешне

#Stateless vs Stateful

┌─────────────────────────────────────────────────────────┐
│                 STATEFUL ПРОЦЕСС                        │
│                                                         │
│  Запрос 1 → [Обработка] → Сохранить в памяти →         │
│  Запрос 2 → [Чтение из памяти] → Ответ                 │
│                                                         │
│  ❌ При перезапуске всё теряется                       │
│  ❌ Нельзя масштабировать горизонтально                │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                 STATELESS ПРОЦЕСС                       │
│                                                         │
│  Запрос 1 → [Обработка] → Сохранить в Redis/БД →       │
│  Запрос 2 → [Чтение из Redis/БД] → Ответ               │
│                                                         │
│  ✅ Перезапуск не влияет на данные                     │
│  ✅ Можно запустить 100 экземпляров                    │
└─────────────────────────────────────────────────────────┘

#Где хранить состояние?

#Сессии пользователей

# ❌ Неправильно: сессии в памяти процесса sessions = {} # Словарь в памяти @app.route('/login') def login(): session_id = generate_session() sessions[session_id] = {'user_id': user_id, 'expires': ...} return session_id # Проблема: при перезапуске все сессии теряются # Проблема: при масштабировании каждый процесс имеет свои сессии
# ✅ Правильно: сессии в Redis import redis redis_client = redis.from_url(os.environ['REDIS_URL']) @app.route('/login') def login(): session_id = generate_session() redis_client.setex( f'session:{session_id}', 3600, # TTL 1 час json.dumps({'user_id': user_id}) ) return session_id # Преимущество: сессии переживают перезапуск # Преимущество: все процессы видят одни сессии

#Файлы и загруженные данные

# ❌ Неправильно: файлы на локальном диске @app.route('/upload', methods=['POST']) def upload(): file = request.files['file'] file.save(f'/tmp/uploads/{file.filename}') # ❌ return 'Uploaded' # Проблема: файлы теряются при перезапуске контейнера # Проблема: файлы не доступны другим процессам
# ✅ Правильно: объектное хранилище (S3) import boto3 s3 = boto3.client('s3') @app.route('/upload', methods=['POST']) def upload(): file = request.files['file'] s3.upload_fileobj( file, os.environ['S3_BUCKET'], file.filename ) return 'Uploaded' # Преимущество: файлы сохраняются после перезапуска # Преимущество: доступны всем процессам

#Кэширование

# ❌ Неправильно: кэш в памяти процесса cache = {} def get_expensive_data(key): if key in cache: return cache[key] data = compute_expensive() cache[key] = data return data # Проблема: кэш не переживает перезапуск # Проблема: каждый процесс имеет свой кэш (неэффективно)
# ✅ Правильно: кэш в Redis import redis redis_client = redis.from_url(os.environ['REDIS_URL']) def get_expensive_data(key): cached = redis_client.get(f'cache:{key}') if cached: return cached data = compute_expensive() redis_client.setex(f'cache:{key}', 300, data) # TTL 5 минут return data # Преимущество: общий кэш для всех процессов # Преимущество: кэш переживает перезапуск процесса

#Масштабирование stateless процессов

#Горизонтальное масштабирование

# Один процесс обрабатывает 100 запросов в секунду kubectl scale deployment myapp --replicas=1 # Десять процессов обрабатывают 1000 запросов в секунду kubectl scale deployment myapp --replicas=10 # Сто процессов обрабатывают 10000 запросов в секунду kubectl scale deployment myapp --replicas=100

Важно: это работает только если процессы stateless. Если сессии в памяти — каждый процесс будет иметь свои сессии, и пользователи будут терять сессию при переключении между процессами.

#Архитектура с балансировщиком

                    ┌─────────────┐
                    │  Load       │
                    │  Balancer   │
                    └──────┬──────┘
                           │
         ┌─────────────────┼─────────────────┐
         │                 │                 │
         ▼                 ▼                 ▼
   ┌───────────┐     ┌───────────┐     ┌───────────┐
   │ Process 1 │     │ Process 2 │     │ Process 3 │
   │ Stateless │     │ Stateless │     │ Stateless │
   └─────┬─────┘     └─────┬─────┘     └─────┬─────┘
         │                 │                 │
         └─────────────────┼─────────────────┘
                           │
                           ▼
                    ┌─────────────┐
                    │   Redis     │
                    │   (State)   │
                    └─────────────┘

Все процессы обращаются к общему хранилищу состояния (Redis, БД).

#Типы процессов

#Веб-процессы

Обработка HTTP-запросов:

# Gunicorn для Python gunicorn app:app -w 4 -b 0.0.0.0:5000 # Node.js node server.js # Go ./myapp

#Воркеры (фоновые процессы)

Обработка фоновых задач:

# worker.py from celery import Celery app = Celery('worker', broker=os.environ['REDIS_URL']) @app.task def send_email(to, subject, body): # Отправка email pass # Запуск воркера celery -A worker worker --loglevel=info

#Периодические задачи

# scheduler.py from apscheduler.schedulers.blocking import BlockingScheduler scheduler = BlockingScheduler() @scheduler.scheduled_job('cron', hour=0) def daily_cleanup(): # Ежедневная очистка pass scheduler.start()

#Запуск разных типов процессов

# docker-compose.yml version: '3.8' services: web: build: . command: gunicorn app:app -b 0.0.0.0:5000 replicas: 4 # 4 веб-процесса worker: build: . command: celery -A tasks worker replicas: 2 # 2 воркера scheduler: build: . command: python scheduler.py replicas: 1 # 1 планировщик

#Антипаттерны

#❌ Состояние в памяти

# ❌ Неправильно class ShoppingCart: _carts = {} # Класс-уровень, память процесса def add_item(self, user_id, item): if user_id not in self._carts: self._carts[user_id] = [] self._carts[user_id].append(item)

Проблема: при перезапуске все корзины теряются.

Правильно:

# ✅ Правильно: состояние в Redis import redis class ShoppingCart: def __init__(self): self.redis = redis.from_url(os.environ['REDIS_URL']) def add_item(self, user_id, item): self.redis.rpush(f'cart:{user_id}', json.dumps(item))

#❌ Файлы на локальном диске

# ❌ Неправильно @app.route('/export') def export(): filename = f'/tmp/export_{user_id}.csv' # Генерация файла return send_file(filename)

Проблема: файл не доступен другим процессам, теряется при перезапуске.

Правильно:

# ✅ Правильно: временные файлы в S3 @app.route('/export') def export(): filename = f'exports/export_{user_id}.csv' # Генерация и загрузка в S3 s3.upload_fileobj(buffer, os.environ['S3_BUCKET'], filename) return redirect(f'https://s3.amazonaws.com/{filename}')

#❌ Sticky sessions как костыль

# ❌ Неправильно: привязка сессии к процессу upstream backend { ip_hash; # Sticky sessions server backend1:5000; server backend2:5000; }

Проблема: sticky sessions маскируют проблему stateful процессов, но не решают её. При падении процесса сессия всё равно теряется.

Правильно: вынести сессии во внешний Redis.

#Graceful shutdown

#Проблема: резкое завершение процессов

# ❌ Неправильно: нет обработки SIGTERM while True: process_request()

При получении SIGTERM процесс завершается немедленно, текущие запросы обрываются.

#Решение: корректное завершение

# ✅ Правильно: graceful shutdown import signal import sys import time shutdown = False def handle_sigterm(signum, frame): global shutdown print('Received SIGTERM, shutting down gracefully...') shutdown = True signal.signal(signal.SIGTERM, handle_sigterm) # Обработка текущих запросов while not shutdown: process_request() # Завершение текущих операций cleanup() sys.exit(0)

#Django/Gunicorn пример

# gunicorn.conf.py def child_exit(server, worker): # Очистка ресурсов при завершении воркера pass def worker_abort(worker): # Обработка прерывания pass

#Kubernetes: preStop hook

# deployment.yaml spec: containers: - name: web image: myapp:latest lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 10"] # Даёт время завершить текущие запросы

#Проверка соответствия принципу

Задайте себе вопросы:

  • Хранятся ли сессии во внешнем сервисе (Redis, БД)?
  • Сохраняются ли файлы в объектном хранилище (S3)?
  • Используется ли общий кэш для всех процессов?
  • Можно ли запустить 10 экземпляров приложения без проблем?
  • Переживёт ли перезапуск процесса данные пользователей?
  • Обрабатывается ли SIGTERM для graceful shutdown?

#Связь с другими факторами

  • Backing Services — состояние хранится во внешних сервисах
  • Concurrency — масштабирование через запуск дополнительных процессов
  • Disposability — процессы могут быть быстро перезапущены без потери данных
  • Logs — логи процессов выводятся в stdout

#Пример из практики

#До: stateful приложение

# ❌ Неправильно class Application: def __init__(self): self.users = {} # Пользователи в памяти self.cache = {} # Кэш в памяти self.uploads = '/tmp/uploads' # Локальные файлы def login(self, user_id): self.users[user_id] = {'logged_in': True} def get_cached(self, key): return self.cache.get(key)

Проблемы:

  • При перезапуске все данные теряются
  • Нельзя масштабировать горизонтально
  • Файлы не доступны другим процессам

#После: stateless приложение

# ✅ Правильно import redis import boto3 class Application: def __init__(self): self.redis = redis.from_url(os.environ['REDIS_URL']) self.s3 = boto3.client('s3') def login(self, user_id): self.redis.set(f'user:{user_id}:logged_in', 'true', ex=3600) def get_cached(self, key): return self.redis.get(f'cache:{key}') def save_upload(self, user_id, file): self.s3.upload_fileobj( file, os.environ['S3_BUCKET'], f'uploads/{user_id}/{file.filename}' )

Результат:

  • Данные переживают перезапуск
  • Можно запустить 100 процессов
  • Все процессы видят одни данные

Ключевой вывод: Процессы должны быть stateless — не хранить данные в памяти. Состояние (сессии, файлы, кэш) должно храниться во внешних сервисах (БД, Redis, S3). Это позволяет масштабировать приложение горизонтально и перезапускать процессы без потери данных.

Далее: Port Binding — привязка портов