Overview
Two clients cover Redis (and Valkey) from Rust. redis-rs (the redis crate) is
the long-standing default: sync and async APIs, a typed command layer over a generic
ToRedisArgs/FromRedisValue conversion system, cluster and sentinel support, and a
ConnectionManager for auto-reconnection. fred is the batteries-included
alternative built async-first: richer pooling, RESP3, client-side caching
(invalidation tracking), automatic pipelining and reconnect policies as configuration
rather than code. The patterns built on them — cache-aside, locks, rate limits,
queues — transfer verbatim from Go and
Java.
Key points
- redis-rs basics:
Client::open(url)→get_multiplexed_async_connection(); commands as typed methods (con.set_ex("k", v, 60)) or the genericredis::cmd("SETEX")escape hatch; theFromRedisValuederive/impl system maps replies onto your types. ConnectionManager(or a deadpool-redis pool) is the production form — multiplexed connections reconnect under the hood; bare connections don't.- fred's pitch:
RedisPoolwith configurable reconnection/backoff, RESP3 by default, tracing integration, cluster-aware pipelining, client-side caching — the operational features you'd otherwise hand-roll; the trade is a bigger API surface and its own idioms. - Pub/sub: a dedicated connection in both clients (
get_async_pubsub/ fred's subscriber client) feeding aStreamof messages — pair with abroadcastchannel to fan out in-process, the same shape as the WebSocket patterns. - Streams over lists for real queues — consumer groups, acks,
XAUTOCLAIMrecovery; both clients expose theX*family typed. - Lua/atomicity:
redis::Scriptwraps EVALSHA with automatic loading — the check-and-set building block for locks and rate limiters; single-instance Redlock caveats apply unchanged. - Serialization is explicit: store serde_json/rmp-serde bytes and convert at the boundary — no automatic object mapping layer exists, matching the ecosystem's no-magic posture.
Examples
let client = redis::Client::open("redis://127.0.0.1/")?;
let mut con = client.get_multiplexed_async_connection().await?;
// cache-aside with TTL
let key = format!("user:{id}");
if let Some(bytes) = con.get::<_, Option<Vec<u8>>>(&key).await? {
return Ok(serde_json::from_slice(&bytes)?);
}
let user = load_from_db(id).await?;
let _: () = con.set_ex(&key, serde_json::to_vec(&user)?, 300).await?;
Related
- Storage access from Rust — the map — parent map.
- Cache management — invalidation, TTL, and stampede strategy independent of client.
- Redis from Go and Redis from Java — the same store, the neighbouring client landscapes (go-redis; Jedis/Lettuce).