В этой лекции мы наконец-то подойдем к тому, что делают почти все современные крупные веб-приложения — сохраним пользовательские сессии в Redis. Как вы уже догадались, Redis здесь выступит как быстрый и удобный инструмент для хранения временных данных.
Пользовательская сессия — это как билет в кино: она подтверждает, что пользователь (или система) уже "законно вошел" в наше приложение. Сессия может содержать идентификатор пользователя, токен авторизации, временную метку и другие данные. Зачем их хранить? Чтобы пользователь мог продолжить работу в приложении даже после закрытия браузера или перезагрузки страницы.
Однако если хранить сессии в базе данных (например, PostgreSQL), это может быстро стать бутылочным горлышком, ведь пользователи запрашивают десятки, если не сотни сессий каждую секунду. Redis же, будучи быстрой in-memory базой данных, идеально подходит для этой задачи.
Преимущества кэширования сессий в Redis:
- Мгновенная скорость: Redis хранит данные в оперативной памяти, что делает доступ к сессиям невероятно быстрым.
- Устойчивость: Redis поддерживает репликацию и дампирование данных, минимизируя риск потери сессий.
- Гибкость: вы можете настраивать TTL для сессий, чтобы они автоматически удалялись через определенное время.
- Обновление данных "на лету": хотите отслеживать активность пользователей? Вы легко можете обновлять данные сессий без лишних задержек.
Давайте разберем сессии через Redis как простую последовательность действий:
- Инициализация сессии: когда пользователь логинится, создается новая сессия, и её идентификатор сохраняется в Redis вместе с данными (например, ID пользователя).
- Чтение сессии: при последующих запросах идентификатор сессии используется для извлечения данных из Redis.
- Обновление TTL: TTL (время жизни) можно обновлять каждый раз, когда пользователь выполняет запрос, чтобы его сессия "не истекла" в процессе работы.
- Удаление сессии: как только пользователь разлогинился, сессия удаляется.
Установка и настройка окружения
У вас Redis уже должен быть установлен с предыдущих лекций. Но если вдруг вы пропустили, выполните:
sudo apt update
sudo apt install redis
Или запустите Redis через Docker:
docker run --name redis -p 6379:6379 -d redis
Убедитесь, что redis-py установлена:
pip install redis
Для асинхронного взаимодействия нам также понадобится aioredis:
pip install aioredis
Реализация кэширования сессий в FastAPI
Создадим клиент для работы с Redis. В нашем случае мы будем использовать асинхронный клиент на основе aioredis.
import aioredis
from fastapi import FastAPI
app = FastAPI()
# Подключение к Redis
redis = aioredis.from_url("redis://localhost", decode_responses=True)
Здесь decode_responses=True позволит нам работать с текстовыми данными вместо байтов.
Теперь, когда пользователь логинится, мы будем создавать запись о сессии в Redis.
from fastapi import HTTPException
from uuid import uuid4
@app.post("/login")
async def login(username: str):
# (Пример простой авторизации, без проверки пароля)
if username not in ["user1", "user2"]:
raise HTTPException(status_code=401, detail="Invalid user")
# Генерация уникального идентификатора сессии
session_id = str(uuid4())
# Сохраняем сессию в Redis
await redis.set(f"session:{session_id}", username, ex=3600) # TTL = 1 час
return {"session_id": session_id}
Какие тут магические вещи происходят:
- Уникальный идентификатор сессии генерируется через
uuid4(). Это ваша "голопузая" защита от повторяющихся сессий. - Мы сохраняем сессию с ключом формата
session:{session_id}. - Параметр
ex=3600задаёт время жизни ключа Redis (в нашем случае 1 час).
Проверка сессии (авторизация запросов)
Теперь при каждом запросе мы будем проверять, существует ли сессия в Redis.
@app.get("/protected")
async def protected_route(session_id: str):
# Проверяем, существует ли такая сессия
username = await redis.get(f"session:{session_id}")
if username is None:
raise HTTPException(status_code=403, detail="Session expired or invalid")
return {"message": f"Welcome back, {username}!"}
Здесь происходит следующее:
- Мы получаем
session_idиз запроса. - Пытаемся найти сессию в Redis.
- Если сессия отсутствует (или истек TTL), пользователь получает ошибку 403.
Удаление сессии (Logout)
Теперь реализуем удаление сессии. Оно понадобится, если пользователь выйдет из системы.
@app.post("/logout")
async def logout(session_id: str):
# Удаляем сессию из Redis
await redis.delete(f"session:{session_id}")
return {"message": "Logged out successfully"}
Обновление TTL
Для автоматического обновления TTL при каждом новом запросе, можем добавить такую логику:
@app.get("/keep-alive")
async def keep_alive(session_id: str):
# Проверяем активность сессии
if not await redis.exists(f"session:{session_id}"):
raise HTTPException(status_code=403, detail="Session expired")
# Обновляем TTL
await redis.expire(f"session:{session_id}", 3600) # Обновляем TTL до 1 часа
return {"message": "Session TTL updated"}
Тестирование приложения
Попробуйте запустить:
- /login:
- Отправьте имя пользователя, получите
session_id.
- Отправьте имя пользователя, получите
- /protected:
- Отправьте
session_id. - Убедитесь, что доступ есть, если сессия активна.
- Отправьте
- /logout:
- Удалите сессию и проверьте, что она больше не работает.
Типичные ошибки и их решение
Если вы получаете ошибку подключения к Redis, проверьте:
- Работает ли Redis-сервер (
redis-serverзапущен?). - Правильный ли порт указан в
redis://localhost. - Установлены ли библиотеки
redisиaioredis.
При отсутствии сессии в /protected убедитесь, что:
- Время жизни сессии (TTL) не истекло.
session_idбыл передан в запросе корректно.
Эта реализация подходит для небольших и средних приложений. В крупных проектах вы можете использовать Redis-кластеры для большей отказоустойчивости, а также добавить библиотеку для работы с JWT токенами вместо uuid4.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ