16. Redis: Структуры данных
Redis (Remote Dictionary Server) — это in-memory хранилище данных с поддержкой различных структур данных. Используется как кэш, брокер сообщений, база данных и для real-time аналитики.
Основные возможности
Заголовок раздела «Основные возможности»graph TD A[Redis] --> B[In-Memory - молниеносная скорость] A --> C[Структуры данных - String, Hash, List, Set, ZSet] A --> D[Persistence - RDB + AOF] A --> E[Pub/Sub - messaging] A --> F[Expires - автоудаление]Преимущества:
- Экстремальная скорость (>100K ops/sec)
- Богатые структуры данных
- Атомарные операции
- Pub/Sub для real-time
- TTL для автоудаления
Установка и запуск
Заголовок раздела «Установка и запуск»# macOSbrew install redisbrew services start redis
# Ubuntusudo apt install redis-serversudo systemctl start redis
# Dockerdocker run -d --name redis -p 6379:6379 redis
# Проверкаredis-cli ping# PONGБазовые команды
Заголовок раздела «Базовые команды»# StringSET user:1:name "John Doe"GET user:1:name # "John Doe"INCR counter # 1INCRBY counter 5 # 6APPEND user:1:name " Jr." # "John Doe Jr."
# Expiration (TTL)SET session:abc123 "data" EX 3600 # expires in 1 hourSETEX temp:key 60 "value" # тоже expires in 60 secTTL session:abc123 # 3599, 3598, ...EXPIRE mykey 10 # set TTL на существующий ключ
# DeleteDEL user:1:nameEXISTS user:1:name # 0 (deleted)Типы данных
Заголовок раздела «Типы данных»Hash - для объектов
Заголовок раздела «Hash - для объектов»HGET user:1 name # "John"HGETALL user:1 # все поля и значенияHINCRBY user:1 age 1 # 31HDEL user:1 emailList - для очередей
Заголовок раздела «List - для очередей»LPUSH queue:tasks "task1" "task2" # добавить слеваRPUSH queue:tasks "task3" # добавить справаLPOP queue:tasks # извлечь слева (task2)RPOP queue:tasks # извлечь справа (task3)LRANGE queue:tasks 0 -1 # все элементыLLEN queue:tasks # длина спискаSet - для уникальных значений
Заголовок раздела «Set - для уникальных значений»SADD tags:post1 "redis" "database" "nosql"SMEMBERS tags:post1 # все элементыSISMEMBER tags:post1 "redis" # 1 (exists)SCARD tags:post1 # 3 (count)SINTER tags:post1 tags:post2 # пересечение множествSUNION tags:post1 tags:post2 # объединениеSorted Set - для рейтингов
Заголовок раздела «Sorted Set - для рейтингов»ZADD leaderboard 100 "Alice" 85 "Bob" 120 "Charlie"ZRANGE leaderboard 0 -1 WITHSCORES # сортировка по scoreZREVRANGE leaderboard 0 2 # топ 3 (по убыванию)ZINCRBY leaderboard 10 "Bob" # 95ZRANK leaderboard "Bob" # позиция (0-indexed)ZREM leaderboard "Alice"Node.js/TypeScript с Redis
Заголовок раздела «Node.js/TypeScript с Redis»import { createClient } from 'redis';
const client = createClient({ url: 'redis://localhost:6379'});
await client.connect();
// String operationsawait client.set('user:1:name', 'John Doe');await client.setEx('session:abc', 3600, 'session-data');const name = await client.get('user:1:name');
// Hash operationsawait client.hSet('user:1', { name: 'John', age: 30});const user = await client.hGetAll('user:1');await client.hIncrBy('user:1', 'age', 1);
// List operationsawait client.lPush('queue:tasks', ['task1', 'task2']);const task = await client.rPop('queue:tasks');
// Set operationsawait client.sAdd('tags:post1', ['redis', 'database']);const tags = await client.sMembers('tags:post1');
// Sorted Setawait client.zAdd('leaderboard', [ { score: 100, value: 'Alice' }, { score: 85, value: 'Bob' }]);const top3 = await client.zRange('leaderboard', 0, 2, { REV: true });
await client.disconnect();Кэширование
Заголовок раздела «Кэширование»// Простой кэш с TTLasync function getCachedUser(userId: string) { const cacheKey = `user:${userId}`;
// Проверяем кэш const cached = await client.get(cacheKey); if (cached) { return JSON.parse(cached); }
// Если нет - берём из БД const user = await db.users.findById(userId);
// Сохраняем в кэш на 1 час await client.setEx(cacheKey, 3600, JSON.stringify(user));
return user;}
// Инвалидация кэша при обновленииasync function updateUser(userId: string, data: any) { await db.users.update(userId, data);
// Удаляем из кэша await client.del(`user:${userId}`);}Rate Limiting
Заголовок раздела «Rate Limiting»async function rateLimit(userId: string, maxRequests = 100, windowSec = 60) { const key = `rate:${userId}`;
const count = await client.incr(key);
if (count === 1) { await client.expire(key, windowSec); }
if (count > maxRequests) { throw new Error('Rate limit exceeded'); }
return { remaining: maxRequests - count };}
// Использованиеtry { await rateLimit('user123', 10, 60); // 10 req/min // ... handle request} catch (error) { // 429 Too Many Requests}Pub/Sub
Заголовок раздела «Pub/Sub»// Publisherawait client.publish('notifications', JSON.stringify({ type: 'new_message', userId: 123, text: 'Hello!'}));
// Subscriberconst subscriber = client.duplicate();await subscriber.connect();
await subscriber.subscribe('notifications', (message) => { const data = JSON.parse(message); console.log('Notification:', data); // Отправить push, email, etc.});Паттерны ключей
Заголовок раздела «Паттерны ключей»// ✅ Хорошие паттерныuser:123:profileuser:123:sessions:abccache:posts:recentleaderboard:weekly:2024-01analytics:pageviews:2024-01-15
// ❌ Плохие паттерныuser123 // непонятноtemp_data // нет структурыx // слишком короткийПравила:
- Используйте
:как разделитель - Начинайте с типа данных (user, cache, session)
- ID/ключи в конце
- Понятные имена
Persistence
Заголовок раздела «Persistence»Redis может сохранять данные на диск:
RDB (Snapshotting)
Заголовок раздела «RDB (Snapshotting)»# redis.confsave 900 1 # snapshot если 1+ изменение за 15 минsave 300 10 # snapshot если 10+ изменений за 5 минsave 60 10000 # snapshot если 10000+ изменений за 1 мин
# Ручной snapshotSAVE # блокирующийBGSAVE # асинхронный (рекомендуется)AOF (Append-Only File)
Заголовок раздела «AOF (Append-Only File)»# redis.confappendonly yesappendfsync everysec # everysec | always | noРекомендация: Используйте оба (RDB + AOF) для максимальной durability.
Redis vs Memcached
Заголовок раздела «Redis vs Memcached»| Характеристика | Redis | Memcached |
|---|---|---|
| Типы данных | Много (String, Hash, List, Set, ZSet) | Только String |
| Persistence | Да (RDB + AOF) | Нет |
| Pub/Sub | Да | Нет |
| Clustering | Да (Redis Cluster) | Да |
| Lua scripting | Да | Нет |
Вывод: Redis более функциональный, Memcached проще и быстрее для простого кэша.
💡 Best Practices
Заголовок раздела «💡 Best Practices»- Используйте connection pooling (1 client на приложение)
- Установите maxmemory и eviction policy
- Мониторьте memory usage (INFO memory)
- Pipeline для batch операций
- Не храните большие значения (>1MB)
⚠️ Частые ошибки
Заголовок раздела «⚠️ Частые ошибки»- Забывают установить TTL (memory leak!)
- Слишком много мелких ключей (overhead)
- Не используют pipelining для batch операций
- Хранят всё в Redis (используйте как кэш, не как primary DB)
Следующий урок: Redis Cache Strategies →