Skip to content

Commit

Permalink
4.2.0 (#9)
Browse files Browse the repository at this point in the history
* 4.2.0
  • Loading branch information
aembke committed Sep 28, 2021
1 parent bcb7a9e commit 146be08
Show file tree
Hide file tree
Showing 13 changed files with 1,030 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 4.2.0

* Support Sentinel clients
* Fix broken doc links

## 4.1.0

* Support Redis Sentinel
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fred"
version = "4.1.0"
version = "4.2.0"
authors = ["Alec Embke <[email protected]>"]
edition = "2018"
description = "An async Redis client for Rust built on Futures and Tokio."
Expand Down Expand Up @@ -76,6 +76,7 @@ blocking-encoding = ["tokio/rt-multi-thread"]
network-logs = []
custom-reconnect-errors = []
monitor = ["nom"]
sentinel-client = []
# Testing Features
sentinel-tests = []
# a testing feature to randomly stop, restart, and rebalance the cluster while tests are running
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ When a client is initialized it will generate a unique client name with a prefix
| pool-prefer-active | x | Prefer connected clients over clients in a disconnected state when using the `RedisPool` interface. |
| full-tracing | | Enable full [tracing](./src/trace/README.md) support. This can emit a lot of data so a partial tracing feature is also provided. |
| partial-tracing | | Enable partial [tracing](./src/trace/README.md) support, only emitting traces for top level commands and network latency. Note: this has a non-trivial impact on [performance](./bin/pipeline_test/README.md#Examples). |
| blocking-encoding | | Use a blocking task for encoding or decoding frames over a [certain size](./src/globals.rs). This can be useful for clients that send or receive large payloads, but will only work when used with a multi-thread Tokio runtime. |
| blocking-encoding | | Use a blocking task for encoding or decoding frames over a [certain size](./src/modules/globals.rs). This can be useful for clients that send or receive large payloads, but will only work when used with a multi-thread Tokio runtime. |
| network-logs | | Enable TRACE level logging statements that will print out all data sent to or received from the server. |
| custom-reconnect-errors | | Enable an interface for callers to customize the types of errors that should automatically trigger reconnection logic. |
| monitor | | Enable an interface for running the `MONITOR` command. |
| sentinel-client | | Enable an interface for communicating directly with Sentinel nodes. This is not necessary to use normal Redis clients behind a sentinel layer. |

## Environment Variables

Expand Down Expand Up @@ -137,11 +138,13 @@ The client will automatically update these values in place as sentinel nodes cha

Note: Sentinel connections will use the same authentication and TLS configuration options as the connections to the Redis servers.

Callers can also use the `sentinel-client` feature to communicate directly with Sentinel nodes.

## Customizing Error Handling

The `custom-reconnect-errors` feature enables an interface on the [globals](src/globals.rs) to customize the list of errors that should automatically trigger reconnection logic (if configured).
The `custom-reconnect-errors` feature enables an interface on the [globals](src/modules/globals.rs) to customize the list of errors that should automatically trigger reconnection logic (if configured).

In many cases applications respond to Redis errors by logging the error, maybe waiting and reconnecting, and then trying again. Whether to do this often depends on [the prefix](https://github.com/redis/redis/blob/unstable/src/server.c#L2506-L2538) in the error message, and this interface allows callers to specify which errors should be handled this way.
In many cases applications respond to Redis errors by logging the error, maybe waiting and reconnecting, and then trying again. Whether to do this often depends on [the prefix](https://github.com/redis/redis/blob/66002530466a45bce85e4930364f1b153c44840b/src/server.c#L2998-L3031) in the error message, and this interface allows callers to specify which errors should be handled this way.

Errors that trigger this can be seen with the [on_error](https://docs.rs/fred/*/fred/client/struct.RedisClient.html#method.on_error) function.

Expand Down
8 changes: 8 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,14 @@ impl RedisClient {
/// This metric reflects the total latency experienced by callers, including time spent waiting in memory to be written and network latency.
/// Features such as automatic reconnect, `reconnect-on-auth-error`, and frame serialization time can all affect these values.
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn read_latency_metrics(&self) -> Stats {
self.inner.latency_stats.read().read_metrics()
}

/// Read and consume latency metrics, resetting their values afterwards.
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn take_latency_metrics(&self) -> Stats {
self.inner.latency_stats.write().take_metrics()
}
Expand All @@ -278,36 +280,42 @@ impl RedisClient {
/// This metric only reflects time spent waiting on a response. It will factor in reconnect time if a response doesn't arrive due to a connection
/// closing, but it does not factor in the time a command spends waiting to be written, serialization time, backpressure, etc.
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn read_network_latency_metrics(&self) -> Stats {
self.inner.network_latency_stats.read().read_metrics()
}

/// Read and consume network latency metrics, resetting their values afterwards.
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn take_network_latency_metrics(&self) -> Stats {
self.inner.network_latency_stats.write().take_metrics()
}

/// Read request payload size metrics across all commands.
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn read_req_size_metrics(&self) -> Stats {
self.inner.req_size_stats.read().read_metrics()
}

/// Read and consume request payload size metrics, resetting their values afterwards.
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn take_req_size_metrics(&self) -> Stats {
self.inner.req_size_stats.write().take_metrics()
}

/// Read response payload size metrics across all commands.
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn read_res_size_metrics(&self) -> Stats {
self.inner.res_size_stats.read().read_metrics()
}

/// Read and consume response payload size metrics, resetting their values afterwards.
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn take_res_size_metrics(&self) -> Stats {
self.inner.res_size_stats.write().take_metrics()
}
Expand Down
3 changes: 3 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,6 @@ pub mod slowlog;
pub mod sorted_sets;
pub mod streams;
pub mod strings;

#[cfg(feature = "sentinel-client")]
pub mod sentinel;
230 changes: 230 additions & 0 deletions src/commands/sentinel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use super::*;
use crate::error::RedisError;
use crate::modules::inner::RedisClientInner;
use crate::protocol::types::*;
use crate::protocol::utils as protocol_utils;
use crate::types::*;
use crate::utils;
use std::net::IpAddr;
use std::sync::Arc;

pub async fn config_get<K>(inner: &Arc<RedisClientInner>, name: K) -> Result<RedisValue, RedisError>
where
K: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((
RedisCommandKind::Sentinel,
vec!["CONFIG".into(), "GET".into(), name.into()],
))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn config_set<K>(
inner: &Arc<RedisClientInner>,
name: K,
value: RedisValue,
) -> Result<RedisValue, RedisError>
where
K: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((
RedisCommandKind::Sentinel,
vec!["CONFIG".into(), "SET".into(), name.into(), value],
))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn ckquorum<N>(inner: &Arc<RedisClientInner>, name: N) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((RedisCommandKind::Sentinel, vec!["CKQUORUM".into(), name.into()]))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn flushconfig(inner: &Arc<RedisClientInner>) -> Result<RedisValue, RedisError> {
args_values_cmd(inner, RedisCommandKind::Sentinel, vec!["FLUSHCONFIG".into()]).await
}

pub async fn failover<N>(inner: &Arc<RedisClientInner>, name: N) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((RedisCommandKind::Sentinel, vec!["FAILOVER".into(), name.into()]))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn get_master_addr_by_name<N>(inner: &Arc<RedisClientInner>, name: N) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((
RedisCommandKind::Sentinel,
vec!["GET-MASTER-ADDR-BY-NAME".into(), name.into()],
))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn info_cache(inner: &Arc<RedisClientInner>) -> Result<RedisValue, RedisError> {
args_values_cmd(inner, RedisCommandKind::Sentinel, vec!["INFO-CACHE".into()]).await
}

pub async fn masters(inner: &Arc<RedisClientInner>) -> Result<RedisValue, RedisError> {
args_values_cmd(inner, RedisCommandKind::Sentinel, vec!["MASTERS".into()]).await
}

pub async fn master<N>(inner: &Arc<RedisClientInner>, name: N) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((RedisCommandKind::Sentinel, vec!["MASTER".into(), name.into()]))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn monitor<N>(
inner: &Arc<RedisClientInner>,
name: N,
ip: IpAddr,
port: u16,
quorum: u32,
) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let (name, ip) = (name.into(), ip.to_string());
let frame = utils::request_response(inner, move || {
Ok((
RedisCommandKind::Sentinel,
vec!["MONITOR".into(), name.into(), ip.into(), port.into(), quorum.into()],
))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn myid(inner: &Arc<RedisClientInner>) -> Result<RedisValue, RedisError> {
args_values_cmd(inner, RedisCommandKind::Sentinel, vec!["MYID".into()]).await
}

pub async fn pending_scripts(inner: &Arc<RedisClientInner>) -> Result<RedisValue, RedisError> {
args_values_cmd(inner, RedisCommandKind::Sentinel, vec!["PENDING-SCRIPTS".into()]).await
}

pub async fn remove<N>(inner: &Arc<RedisClientInner>, name: N) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((RedisCommandKind::Sentinel, vec!["REMOVE".into(), name.into()]))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn replicas<N>(inner: &Arc<RedisClientInner>, name: N) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((RedisCommandKind::Sentinel, vec!["REPLICAS".into(), name.into()]))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn sentinels<N>(inner: &Arc<RedisClientInner>, name: N) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
Ok((RedisCommandKind::Sentinel, vec!["SENTINELS".into(), name.into()]))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn set<N>(inner: &Arc<RedisClientInner>, name: N, options: RedisMap) -> Result<RedisValue, RedisError>
where
N: Into<String>,
{
let name = name.into();
let frame = utils::request_response(inner, move || {
let mut args = Vec::with_capacity(2 + options.len());
args.push("SET".into());
args.push(name.into());

for (key, value) in options.inner().into_iter() {
args.push(key.into());
args.push(value);
}
Ok((RedisCommandKind::Sentinel, args))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn simulate_failure(
inner: &Arc<RedisClientInner>,
kind: SentinelFailureKind,
) -> Result<RedisValue, RedisError> {
let frame = utils::request_response(inner, move || {
Ok((
RedisCommandKind::Sentinel,
vec!["SIMULATE-FAILURE".into(), kind.to_str().into()],
))
})
.await?;

protocol_utils::frame_to_results(frame)
}

pub async fn reset<P>(inner: &Arc<RedisClientInner>, pattern: P) -> Result<RedisValue, RedisError>
where
P: Into<String>,
{
let pattern = pattern.into();
let frame = utils::request_response(inner, move || {
Ok((RedisCommandKind::Sentinel, vec!["RESET".into(), pattern.into()]))
})
.await?;

protocol_utils::frame_to_results(frame)
}
1 change: 1 addition & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ impl From<Resp2Frame> for RedisError {
}

#[cfg(feature = "enable-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable-tls")))]
impl From<native_tls::Error> for RedisError {
fn from(e: native_tls::Error) -> Self {
RedisError::new(RedisErrorKind::Tls, format!("{:?}", e))
Expand Down
10 changes: 9 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, allow(unused_attributes))]

//! Fred
//! ====
//!
Expand Down Expand Up @@ -72,9 +76,13 @@ pub mod client;
pub mod error;
/// An interface to run the `MONITOR` command.
#[cfg(feature = "monitor")]
#[cfg_attr(docsrs, doc(cfg(feature = "monitor")))]
pub mod monitor;
/// An interface for interacting directly with sentinel nodes.
#[cfg(feature = "sentinel-client")]
#[cfg_attr(docsrs, doc(cfg(feature = "sentinel-client")))]
pub mod sentinel;

// TODO test cargo doc works
pub use crate::modules::{globals, pool, types};

/// Convenience module to `use` a `RedisClient`, `RedisError`, and any argument types.
Expand Down
Loading

0 comments on commit 146be08

Please sign in to comment.