Введение в кэширование в Rust
Кэширование — один из ключевых инструментов для повышения производительности приложений на Rust. Независимо от того, создаёте ли вы высоконагруженный веб-сервер, базу данных или десктопное приложение, грамотное кэширование позволяет сократить время ответа, снизить нагрузку на внешние ресурсы и оптимизировать использование памяти. В этой статье мы разберём три основных подхода к кэшированию в экосистеме Rust: использование внешних систем (Redis и Memcached) и встроенные решения, такие как lru-rs и moka. Вы узнаете, когда какой инструмент выбрать, и получите готовые примеры кода.
Внешние системы кэширования: Redis и Memcached
Кэширование с Redis в Rust
Redis — это популярное in-memory хранилище данных, поддерживающее различные структуры данных (строки, списки, множества, хеш-таблицы). Для работы с Redis в Rust используется крейт redis (или redis-rs).
Установим зависимость в Cargo.toml:
[dependencies]
redis = { version = "0.25", features = ["tokio-comp"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Пример подключения и базового кэширования с Redis:
use redis::AsyncCommands;
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: u32,
name: String,
email: String,
}
#[tokio::main]
async fn main() -> redis::RedisResult<()> {
let client = redis::Client::open("redis://127.0.0.1:6379/")?;
let mut conn = client.get_multiplexed_async_connection().await?;
// Сохраняем объект в кэш (сериализуем в JSON)
let user = User {
id: 1,
name: "Алексей".into(),
email: "alex@example.com".into(),
};
let json = serde_json::to_string(&user).unwrap();
conn.set_ex("user:1", json, 3600).await?; // TTL = 1 час
// Читаем из кэша
let cached: String = conn.get("user:1").await?;
let user_cached: User = serde_json::from_str(&cached).unwrap();
println!("Получен пользователь: {:?}", user_cached);
Ok(())
}
Плюсы Redis: поддержка сложных структур данных, TTL, pub/sub, репликация, кластеризация. Минусы: требует отдельного сервера, сетевые задержки.
Кэширование с Memcached в Rust
Memcached — более лёгкое и простое in-memory хранилище, ориентированное на хранение пар "ключ-значение". Для Rust используется крейт memcache.
Добавим в Cargo.toml:
[dependencies]
memcache = "0.19"
tokio = { version = "1", features = ["full"] }
Пример работы с Memcached:
use memcache::Client;
#[tokio::main]
async fn main() -> memcache::MemcacheResult<()> {
let client = Client::connect("memcache://127.0.0.1:11211")?;
// Запись с TTL (в секундах)
client.set("greeting", "Привет, мир!", 300)?;
// Чтение
let value: Option<String> = client.get("greeting")?;
if let Some(msg) = value {
println!("Из кэша: {}", msg);
}
// Инкремент счётчика
client.set("counter", "10", 0)?;
client.increment("counter", 5)?;
let counter: Option<u64> = client.get("counter")?;
println!("Счётчик: {:?}", counter);
Ok(())
}
Плюсы Memcached: простота, высокая скорость, минимальное потребление памяти. Минусы: нет сложных структур данных, только хранение строк/байтов.
Встроенные решения для кэширования в Rust
Библиотека lru-rs (LRU-кэш)
Крейт lru реализует классический алгоритм вытеснения Least Recently Used (LRU). Идеально подходит для кэширования в памяти одного процесса без внешних зависимостей.
[dependencies]
lru = "0.12"
use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
// Создаём кэш на 100 элементов
let mut cache: LruCache<String, Vec<u8>> =
LruCache::new(NonZeroUsize::new(100).unwrap());
// Добавляем данные
cache.put("key1".to_string(), vec![1, 2, 3]);
cache.put("key2".to_string(), vec![4, 5, 6]);
// Читаем (обновляет порядок LRU)
if let Some(data) = cache.get(&"key1".to_string()) {
println!("Данные: {:?}", data);
}
// Принудительное удаление
cache.pop(&"key2".to_string());
println!("Размер кэша: {}", cache.len()); // 1
}
Когда использовать: однопоточные приложения, небольшие объёмы данных, простая логика вытеснения.
Библиотека moka (высокопроизводительный конкуретный кэш)
moka — это современная, высокопроизводительная библиотека для кэширования с поддержкой конкурентного доступа, TTL и различных политик вытеснения (LRU, LFU, TinyLFU). Рекомендуется для многопоточных приложений.
[dependencies]
moka = { version = "0.12", features = ["future"] }
tokio = { version = "1", features = ["full"] }
use moka::future::Cache;
#[tokio::main]
async fn main() {
// Создаём кэш с максимальным размером 10 000 элементов
let cache: Cache<String, String> = Cache::builder()
.max_capacity(10_000)
.time_to_live(std::time::Duration::from_secs(3600)) // TTL 1 час
.build();
// Вставляем