Connect to the server
Connect your Rust application to a Redis database
Basic connection
Synchronous connections need only redis = "1.7.1" in your Cargo.toml file. For
asynchronous connections, also enable the tokio-comp feature and add
tokio:
[dependencies]
redis = { version = "1.7.1", features = ["tokio-comp"] }
tokio = { version = "1", features = ["full"] }
The following example opens a synchronous connection, then stores and retrieves a string:
use redis::Commands;
fn main() -> redis::RedisResult<()> {
let client = redis::Client::open("redis://localhost:6379/")?;
let mut con = client.get_connection()?;
let _: () = con.set("foo", "bar")?;
let val: String = con.get("foo")?;
println!("foo: {val}"); // foo: bar
Ok(())
}
The asynchronous version uses get_multiplexed_async_connection():
use redis::AsyncCommands;
#[tokio::main]
async fn main() -> redis::RedisResult<()> {
let client = redis::Client::open("redis://localhost:6379/")?;
let mut con = client.get_multiplexed_async_connection().await?;
let _: () = con.set("foo", "bar").await?;
let val: String = con.get("foo").await?;
println!("foo: {val}"); // foo: bar
Ok(())
}
To connect as an access control list (ACL) user, add the username and password to the URL. The path selects the database number:
let client = redis::Client::open("redis://<username>:<password>@localhost:6379/0")?;
You can also build the connection details in code with ConnectionInfo and
RedisConnectionInfo:
use redis::{ConnectionAddr, IntoConnectionInfo, RedisConnectionInfo};
let info = ConnectionAddr::Tcp("localhost".to_string(), 6379)
.into_connection_info()?
.set_redis_settings(
RedisConnectionInfo::default()
.set_username("default") // use your Redis user
.set_password("secret") // use your Redis password
.set_db(0),
);
let client = redis::Client::open(info)?;
To connect through a Unix socket, use a redis+unix:// URL. A Unix socket URL takes the
username, password, and database number as the user, pass, and db query parameters.
This differs from a redis:// URL, which takes the credentials before the host and the
database number from the path.
let client = redis::Client::open("redis+unix:///tmp/redis.sock?user=<username>&pass=<password>&db=0")?;
To authenticate with Microsoft Entra ID, see Connect to Azure Managed Redis.
Connect to a Redis cluster
To connect to a Redis cluster, enable the cluster feature:
[dependencies]
redis = { version = "1.7.1", features = ["cluster"] }
Then pass one or more cluster nodes to ClusterClient::new(). The client discovers the
rest of the cluster from these nodes:
use redis::cluster::ClusterClient;
use redis::Commands;
fn main() -> redis::RedisResult<()> {
let nodes = vec![
"redis://localhost:16379/",
"redis://localhost:16380/",
"redis://localhost:16381/",
];
let client = ClusterClient::new(nodes)?;
let mut con = client.get_connection()?;
let _: () = con.set("foo", "bar")?;
let val: String = con.get("foo")?;
println!("foo: {val}"); // foo: bar
Ok(())
}
For asynchronous connections, enable the cluster-async and tokio-comp features instead,
and call get_async_connection():
let mut con = client.get_async_connection().await?;
By default, the client sends every command to the primary node. To send read-only
commands to replicas, create the client with ClusterClientBuilder and set a read
routing strategy. RandomReplicaStrategy sends each read to a random replica of the
key's shard, and RoundRobinReplicaStrategy rotates through them. Writes still go to the
primary. Replication is asynchronous, so a replica can return stale data.
use redis::cluster::ClusterClientBuilder;
use redis::cluster_read_routing::RandomReplicaStrategy;
let client = ClusterClientBuilder::new(nodes)
.read_routing_strategy(RandomReplicaStrategy)
.build()?;
let mut con = client.get_connection()?;
let val: String = con.get("foo")?; // Read from a replica
Connect to Redis Sentinel
To connect through Redis Sentinel,
enable the sentinel feature:
[dependencies]
redis = { version = "1.7.1", features = ["sentinel"] }
Then create a client with SentinelClientBuilder. Pass one or more Sentinel addresses,
the name of the primary that the Sentinels monitor, and SentinelServerType::Master.
Each call to get_connection() asks the Sentinels for the current primary and checks
its role before it connects:
use redis::sentinel::{SentinelClientBuilder, SentinelServerType};
use redis::{Commands, ConnectionAddr};
fn main() -> redis::RedisResult<()> {
let sentinels = vec![
ConnectionAddr::Tcp("localhost".to_string(), 26379),
ConnectionAddr::Tcp("localhost".to_string(), 26380),
ConnectionAddr::Tcp("localhost".to_string(), 26381),
];
let mut client =
SentinelClientBuilder::new(sentinels, "mymaster", SentinelServerType::Master)?.build()?;
let mut con = client.get_connection()?;
let _: () = con.set("foo", "bar")?;
let val: String = con.get("foo")?;
println!("foo: {val}"); // foo: bar
Ok(())
}
The Sentinels and the data nodes have separate credentials. Use the
set_client_to_sentinel_* methods for the Sentinels, and the set_client_to_redis_*
methods for the primary and replicas:
let mut client = SentinelClientBuilder::new(sentinels, "mymaster", SentinelServerType::Master)?
.set_client_to_sentinel_password("sentinel-secret") // use your Sentinel password
.set_client_to_redis_username("default") // use your Redis user
.set_client_to_redis_password("secret") // use your Redis password
.build()?;
To connect to a replica instead, pass SentinelServerType::Replica. The client connects
to a replica chosen at random, and writes on that connection fail. Replication is
asynchronous, so a replica can return stale data.
let mut replica =
SentinelClientBuilder::new(sentinels, "mymaster", SentinelServerType::Replica)?.build()?;
let mut con = replica.get_connection()?;
let val: String = con.get("foo")?;
A connection from get_connection() doesn't reconnect, and redis-rs doesn't listen for
failover announcements from the Sentinels. After a failover, the connection keeps using
the old primary until Sentinel reconfigures it as a replica and closes the connection.
Any writes that the old primary accepts during that time are lost. When a command fails
with a connection error, call get_connection() again to connect to the new primary.
Connect to your production Redis with TLS
When you deploy your application, use Transport Layer Security (TLS) and follow the Redis security guidelines.
Custom certificates need the rustls backend, so enable the tls-rustls feature, or
tokio-rustls-comp for asynchronous connections. Also add the rustls crate, which
provides the cryptography:
[dependencies]
redis = { version = "1.7.1", features = ["tls-rustls"] }
rustls = "0.23"
If a certificate authority (CA) in your system's trust store signed the server's
certificate, a rediss:// URL is all you need. To use Mozilla's root certificates
instead of the system trust store, enable the tls-rustls-webpki-roots feature.
let client = redis::Client::open("rediss://<username>:<password>@my-redis.example.com:6379")?;
To use your own CA certificate, or a client certificate and key for mutual TLS, pass the
Privacy-Enhanced Mail (PEM) files to Client::build_with_tls() in a TlsCertificates
object. rustls chooses a crypto provider automatically only when your dependencies enable
exactly one, and the connection panics otherwise, so the following example installs one
before it connects:
use redis::{ClientTlsConfig, Commands, TlsCertificates};
use std::fs;
fn main() -> redis::RedisResult<()> {
// Install a crypto provider for rustls before you connect.
rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.expect("Failed to install the rustls crypto provider");
let client = redis::Client::build_with_tls(
"rediss://<username>:<password>@my-redis.example.com:6379",
TlsCertificates {
client_tls: Some(ClientTlsConfig {
client_cert: fs::read("redis_user.crt")?,
client_key: fs::read("redis_user_private.key")?,
}),
root_cert: Some(fs::read("redis_ca.pem")?),
},
)?;
let mut con = client.get_connection()?;
let _: () = con.set("foo", "bar")?;
let val: String = con.get("foo")?;
println!("foo: {val}"); // foo: bar
Ok(())
}
Connect using client-side caching
Client-side caching is a technique to reduce network traffic between the client and server, resulting in better performance. See Client-side caching introduction for more information about how client-side caching works and how to use it effectively.
Client-side caching is an experimental feature of redis-rs. To maximize compatibility with all Redis products, client-side caching is supported by Redis v7.4 or later.
Client-side caching requires version 3 of the
Redis serialization protocol (RESP3),
and connecting with caching enabled over RESP2 fails with an error. It's
available only for asynchronous connections: MultiplexedConnection,
ConnectionManager, and the asynchronous ClusterConnection.
Enable the cache-aio feature:
[dependencies]
redis = { version = "1.7.1", features = ["tokio-comp", "cache-aio"] }
tokio = { version = "1", features = ["full"] }
Then pass a CacheConfig in an AsyncConnectionConfig when you connect.
CacheConfig::new() enables caching with the default settings. Use
get_cache_statistics() to check that the second get() call is served from the cache:
use redis::caching::CacheConfig;
use redis::{AsyncCommands, AsyncConnectionConfig};
#[tokio::main]
async fn main() -> redis::RedisResult<()> {
let client = redis::Client::open("redis://localhost:6379/?protocol=resp3")?;
let config = AsyncConnectionConfig::new().set_cache_config(CacheConfig::new());
let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
let _: () = con.set("city", "New York").await?;
let _: String = con.get("city").await?; // Retrieved from the server and cached
let _: String = con.get("city").await?; // Retrieved from the cache
let stats = con.get_cache_statistics().unwrap();
println!("hits: {}, misses: {}", stats.hit, stats.miss); // hits: 1, misses: 1
Ok(())
}
Connection management and pooling
A MultiplexedConnection is cheap to clone, and all the clones share one underlying
connection, so you don't need a connection pool for asynchronous connections. Give each task
its own clone:
let con = client.get_multiplexed_async_connection().await?;
let tasks: Vec<_> = (1..=3)
.map(|i| {
let mut con = con.clone(); // Shares the same underlying connection
tokio::spawn(async move { con.incr::<_, _, i64>("counter", i).await })
})
.collect();
for task in tasks {
task.await.unwrap()?;
}
Reconnect automatically
A MultiplexedConnection doesn't reconnect after the connection drops. To reconnect
automatically, enable the connection-manager feature:
[dependencies]
redis = { version = "1.7.1", features = ["tokio-comp", "connection-manager"] }
tokio = { version = "1", features = ["full"] }
Then use a ConnectionManager, which you can clone in the same way. The command that
encounters the disconnection still fails, and the manager reconnects in the background
for the next command, so retry where that matters. To change the retry and timeout
settings, pass a ConnectionManagerConfig to get_connection_manager_with_config().
let client = redis::Client::open("redis://localhost:6379/")?;
let mut con = client.get_connection_manager().await?;
let _: () = con.set("foo", "bar").await?;
Pool synchronous connections
For synchronous connections, use an r2d2 pool to handle
disconnections and share connections between threads. Enable the r2d2 feature and add
the r2d2 crate:
[dependencies]
redis = { version = "1.7.1", features = ["r2d2"] }
r2d2 = "0.8"
The pool opens max_size connections when you build it. The default is 10.
use redis::Commands;
let client = redis::Client::open("redis://localhost:6379/")?;
let pool = r2d2::Pool::builder().max_size(15).build(client).unwrap();
let mut con = pool.get().unwrap();
let _: () = con.set("foo", "bar")?;
Timeouts
Asynchronous connections time out after 1 second when they connect, and after 500
milliseconds when they wait for a response. To change these timeouts, pass an
AsyncConnectionConfig when you connect. Pass None to disable a timeout.
ConnectionManagerConfig has the same defaults and setters.
use redis::AsyncConnectionConfig;
use std::time::Duration;
let config = AsyncConnectionConfig::new()
.set_connection_timeout(Some(Duration::from_secs(5)))
.set_response_timeout(Some(Duration::from_secs(2)));
let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
The response timeout also applies to blocking commands. With the default timeout, a
BLPOP that waits longer than 500 milliseconds fails with a
timeout error. Other commands on the same connection also time out until the server
finishes the blocking command, because the server doesn't process them in the meantime.
Send blocking commands on a separate connection, with a response timeout longer than the
command's own timeout, or none:
let config = AsyncConnectionConfig::new().set_response_timeout(None);
let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
// Waits up to 5 seconds for an item.
let item: Option<(String, String)> = con.blpop("queue", 5.0).await?;
Synchronous connections have no timeouts by default. get_connection_with_timeout()
limits the time to connect, and set_read_timeout() and set_write_timeout() limit each
read and write on the connection:
use std::time::Duration;
let con = client.get_connection_with_timeout(Duration::from_secs(5))?;
con.set_read_timeout(Some(Duration::from_secs(2)))?;
con.set_write_timeout(Some(Duration::from_secs(2)))?;
Protocol
redis-rs uses the RESP2
protocol by default. To use RESP3, add protocol=resp3 to the URL:
let client = redis::Client::open("redis://localhost:6379/?protocol=resp3")?;
You can also set the protocol with RedisConnectionInfo:
use redis::{ConnectionAddr, IntoConnectionInfo, ProtocolVersion, RedisConnectionInfo};
let info = ConnectionAddr::Tcp("localhost".to_string(), 6379)
.into_connection_info()?
.set_redis_settings(RedisConnectionInfo::default().set_protocol(ProtocolVersion::RESP3));
let client = redis::Client::open(info)?;
For cluster and Sentinel clients, use ClusterClientBuilder::use_protocol() and
SentinelClientBuilder::set_client_to_redis_protocol().