← Назад к главному
# redis.rs - Модуль Redis клиента
## 📋 Назначение модуля
`RedisCore` - обёртка вокруг redis-rs для управления подключениями и командами к Redis.
**Основная логика:**
1. **Инициализация** - создание клиента Redis
2. **PING** - проверка подключения к серверу
3. **DELETE BY PREFIX** - удаление ключей по префиксу
4. **GET CONNECTION** - получение мультиплексированного подключения
**Используется в:**
- `main.rs` - проверка Redis PING/PONG, очистка Redis
- `monitor.rs` - запись тиков и мета-данных
- `src/live/meta.rs` - чтение/запись мета-данных
- `src/live/logger.rs` - запись live_log
- `src/live/entry_calculator.rs` - чтение/запись EntryCalculation
---
## 🏗️ Структура модуля
### Структура `RedisCore`
**Строки:** 5-7
```rust
pub struct RedisCore {
pub client: redis::Client,
}
```
**Назначение:** Управление подключением к Redis
**Поля:**
| Поле | Тип | Описание |
|------|-----|----------|
| `client` | `redis::Client` | Клиент Redis (базовое подключение) |
---
## 🔄 Основные функции
### Функция `RedisCore::new()`
**Строки:** 11-15
**Сигнатура:**
```rust
pub fn new(redis_url: &str) -> Result
```
**Параметры:**
- `redis_url` - URL подключения к Redis (например, `redis://127.0.0.1:6379/`)
**Возвращает:**
- `Ok(RedisCore)` - клиент создан успешно
- `Err` - ошибка создания клиента
**Алгоритм:**
```rust
let client = redis::Client::open(redis_url)
.context("Failed to create Redis client")?;
Ok(RedisCore { client })
```
**Что делает:**
- Создаёт клиент Redis по указанному URL
- Возвращает структуру `RedisCore`
**Пример:**
```rust
let redis = RedisCore::new("redis://127.0.0.1:6379/")?;
```
**Пример ошибки:**
```
Failed to create Redis client: Connection refused
```
---
### Функция `RedisCore::ping()`
**Строки:** 18-32
**Сигнатура:**
```rust
pub async fn ping(&self) -> Result<()>
```
**Возвращает:**
- `Ok(())` - Redis отвечает PONG
- `Err` - ошибка подключения или неверный ответ
**Алгоритм:**
---
#### Этап 1: Получение подключения
**Строки:** 19-20
```rust
let mut conn = self.get_conn().await
.context("❌ ОШИБКА: Redis сервер не запущен или недоступен!")?;
```
**Что делает:**
- Получает мультиплексированное подключение к Redis
- При ошибке выводит сообщение "Redis сервер не запущен или недоступен"
**Пример ошибки:**
```
❌ ОШИБКА: Redis сервер не запущен или недоступен!
Connection refused
```
---
#### Этап 2: Отправка PING
**Строки:** 22-25
```rust
let pong: String = redis::cmd("PING")
.query_async(&mut conn)
.await
.context("❌ ОШИБКА: Redis не отвечает на PING!")?;
```
**Что делает:**
- Отправляет команду `PING` в Redis
- Ожидает ответ `PONG`
- При ошибке выводит сообщение "Redis не отвечает на PING"
**Пример ошибки:**
```
❌ ОШИБКА: Redis не отвечает на PING!
Timeout
```
---
#### Этап 3: Проверка ответа
**Строки:** 27-29
```rust
if pong != "PONG" {
anyhow::bail!("❌ ОШИБКА: Redis вернул неверный ответ: {}", pong);
}
```
**Что делает:**
- Проверяет что ответ равен "PONG"
- Если ответ другой → ошибка
**Пример ошибки:**
```
❌ ОШИБКА: Redis вернул неверный ответ: PANG
```
---
#### Этап 4: Возврат успеха
**Строка:** 31
```rust
Ok(())
```
---
**Пример использования:**
```rust
match redis.ping().await {
Ok(_) => println!("✅ Redis PING/PONG - сервер работает!"),
Err(e) => eprintln!("❌ {}", e),
}
```
**Пример успешного ответа:**
```
✅ Redis PING/PONG - сервер работает!
```
---
### Функция `RedisCore::delete_by_prefix()`
**Строки:** 35-73
**Сигнатура:**
```rust
pub async fn delete_by_prefix(&self, prefix: &str) -> Result
```
**Параметры:**
- `prefix` - префикс ключей для удаления (например, `"hb:*"`)
**Возвращает:**
- `Ok(usize)` - количество удалённых ключей
- `Err` - ошибка SCAN или DEL
**Алгоритм:**
---
#### Этап 1: Получение подключения
**Строки:** 36-37
```rust
let mut conn = self.get_conn().await
.context("❌ ОШИБКА: Не удалось подключиться к Redis для удаления!")?;
```
**Что делает:**
- Получает мультиплексированное подключение к Redis
- При ошибке выводит сообщение
---
#### Этап 2: SCAN по ключам
**Строки:** 40-60
```rust
// Используем SCAN для безопасного поиска ключей
let mut keys: Vec = Vec::new();
let mut cursor: usize = 0;
loop {
let (next_cursor, batch): (usize, Vec) = redis::cmd("SCAN")
.arg(cursor)
.arg("MATCH")
.arg(prefix)
.arg("COUNT")
.arg(100)
.query_async(&mut conn)
.await
.context("❌ ОШИБКА: Не удалось выполнить SCAN!")?;
keys.extend(batch);
cursor = next_cursor;
if cursor == 0 {
break;
}
}
```
**Что делает:**
- Использует `SCAN` для итерации по ключам (безопасная альтернатива KEYS)
- `SCAN cursor MATCH prefix COUNT 100` - сканирует по 100 ключей за раз
- Накапливает ключи в векторе
- Повторяет пока `cursor != 0`
**Почему SCAN вместо KEYS:**
- `KEYS` блокирует Redis на всё время выполнения
- `SCAN` неблокирующий и безопасный для продакшена
**Пример команды:**
```
SCAN 0 MATCH hb:* COUNT 100
→ возвращает (cursor, batch)
SCAN next_cursor MATCH hb:* COUNT 100
→ продолжаем пока cursor != 0
```
---
#### Этап 3: Удаление ключей
**Строки:** 62-72
```rust
if !keys.is_empty() {
let deleted: usize = redis::cmd("DEL")
.arg(keys.as_slice())
.query_async(&mut conn)
.await
.context("❌ ОШИБКА: Не удалось выполнить DEL!")?;
Ok(deleted)
} else {
Ok(0)
}
```
**Что делает:**
- Если есть ключи → удаляет их командой `DEL`
- Если ключей нет → возвращает 0
**Пример команды:**
```
DEL hb:ticks:MEMES_USDT hb:last:MEMES_USDT hb:meta:MEMES_USDT
→ возвращает 3 (удалено 3 ключа)
```
---
**Пример использования:**
```rust
match redis.delete_by_prefix("hb:*").await {
Ok(count) => {
if count > 0 {
println!("🧹 Redis очищен: удалено {} старых ключей", count);
} else {
println!("✅ Redis чист - нет старых данных");
}
}
Err(e) => {
eprintln!("⚠️ Очистка Redis не удалась: {:?}", e);
}
}
```
**Пример успешного ответа:**
```
🧹 Redis очищен: удалено 42 старых ключей
```
**Пример когда ключей нет:**
```
✅ Redis чист - нет старых данных
```
---
### Функция `RedisCore::get_conn()`
**Строки:** 76-80
**Сигнатура:**
```rust
pub async fn get_conn(&self) -> Result
```
**Возвращает:**
- `Ok(MultiplexedConnection)` - мультиплексированное подключение
- `Err` - ошибка получения подключения
**Алгоритм:**
```rust
let conn = self.client.get_multiplexed_async_connection().await
.context("Failed to get Redis connection")?;
Ok(conn)
```
**Что делает:**
- Получает мультиплексированное асинхронное подключение
- Мультиплексированное подключение позволяет выполнять несколько команд параллельно
**Почему MultiplexedConnection:**
- Поддерживает несколько команд параллельно
- Пул подключений внутри
- Безопасно использовать в async контексте
---
### Функция `get_redis_conn()`
**Строки:** 84-86
**Сигнатура:**
```rust
pub async fn get_redis_conn(redis: &RedisCore) -> Result
```
**Параметры:**
- `redis` - ссылка на `RedisCore`
**Возвращает:**
- `Ok(MultiplexedConnection)` - мультиплексированное подключение
- `Err` - ошибка получения подключения
**Алгоритм:**
```rust
redis.get_conn().await
```
**Что делает:**
- Helper-функция для вызова `RedisCore::get_conn()`
- Удобна для импорта через `use crate::redis::get_redis_conn`
**Пример использования:**
```rust
use crate::redis::get_redis_conn;
let conn = get_redis_conn(&redis).await?;
```
---
## 📊 Redis ключи
| Ключ | Тип | Модуль | Описание |
|------|-----|---------|----------|
| `hb:meta:{symbol}` | String | `live/meta.rs` | Мета-данные контракта |
| `hb:entry_calc:{symbol}` | String | `live/entry_calculator.rs` | Расчёт входа |
| `hb:live_log:{symbol}` | List | `live/logger.rs` | Лог позиции (максимум 200 записей) |
| `hb:ticks:{symbol}` | List | `monitor.rs` | Список тиков (максимум 1000) |
| `hb:last:{symbol}` | String | `monitor.rs` | Последний тик |
---
## ⚠️ Критические моменты
### 1. SCAN вместо KEYS
**Строки:** 43-60
```rust
let (next_cursor, batch): (usize, Vec) = redis::cmd("SCAN")
.arg(cursor)
.arg("MATCH")
.arg(prefix)
.arg("COUNT")
.arg(100)
.query_async(&mut conn)
.await
.context("❌ ОШИБКА: Не удалось выполнить SCAN!")?;
```
**Почему SCAN вместо KEYS:**
- `KEYS hb:*` - блокирует Redis пока ищет все ключи
- `SCAN 0 MATCH hb:*` - неблокирующий, возвращает по 100 ключей за раз
- Безопасно использовать в продакшене с большим количеством ключей
---
### 2. MultiplexedConnection
**Строка:** 77
```rust
let conn = self.client.get_multiplexed_async_connection().await
```
**Почему MultiplexedConnection:**
- Поддерживает несколько команд параллельно
- Пул подключений внутри
- Оптимизировано для async/await
---
### 3. Проверка PONG
**Строки:** 27-29
```rust
if pong != "PONG" {
anyhow::bail!("❌ ОШИБКА: Redis вернул неверный ответ: {}", pong);
}
```
**Почему это важно:**
- PING должен вернуть именно "PONG"
- Любой другой ответ означает проблему
- Защита от некорректных ответов
---
### 4. batch возвращает Vec
**Строка:** 44
```rust
let (next_cursor, batch): (usize, Vec) = redis::cmd("SCAN")
```
**Что это значит:**
- SCAN возвращает `(cursor, batch)` - курсор и партию ключей
- `batch` имеет тип `Vec` - вектор строк (имена ключей)
- Накапливаем все партии в `keys.extend(batch)`
---
## 📚 Связанные файлы
| Файл | Связь |
|------|-------|
| `src/main.rs` | Использует RedisCore::ping(), RedisCore::delete_by_prefix() |
| `src/monitor.rs` | Использует get_redis_conn() для записи тиков |
| `src/live/meta.rs` | Использует get_redis_conn() для чтения/записи мета-данных |
| `src/live/logger.rs` | Использует get_redis_conn() для записи live_log |
| `src/live/entry_calculator.rs` | Использует get_redis_conn() для чтения/записи EntryCalculation |
---
## 🎯 Резюме
**Что делает redis.rs:**
1. ✅ Создаёт клиент Redis по URL
2. ✅ Проверяет подключение через PING/PONG
3. ✅ Удаляет ключи по префиксу (через SCAN)
4. ✅ Получает мультиплексированное подключение
**RedisCore методы:**
- `new(redis_url)` - создание клиента
- `ping()` - проверка подключения (PING/PONG)
- `delete_by_prefix(prefix)` - удаление ключей по префиксу
- `get_conn()` - получение подключения
**Helper функция:**
- `get_redis_conn(redis)` - удобная обёртка над `redis.get_conn()`
**Redis ключи бота:**
- `hb:*` - префикс всех ключей бота
- `hb:meta:{symbol}` - мета-данные
- `hb:entry_calc:{symbol}` - расчёт входа
- `hb:live_log:{symbol}` - лог позиции
- `hb:ticks:{symbol}` - список тиков
- `hb:last:{symbol}` - последний тик
**Критические моменты:**
- 🔥 SCAN вместо KEYS (неблокирующий)
- 🔥 MultiplexedConnection (мультиплексированное подключение)
- 🔥 Проверка PONG (валидация ответа)
---
**Дата создания:** 2026-02-22
**Автор:** Claude Code Assistant
**Версия:** 1.0