在Rust web服务中使用Redis的方法

Redis一直是网络生态系统的重要组成部分,它经常用作缓存、消息代理或简单地用作数据存储。

在这篇文章中,我们将演示如何在一个Rust web应用程序中使用Redis。

我们将探索两种种使用Redis的方法:

  • 使用同步连接池

  • 使用异步连接池

对于同步池,我们使用基于r2d2库的r2d2-redis。我们在异步解决方案中使用mobc,还有许多其他异步连接池,如deadpool和bb8,它们都以类似的方式工作。

话不多说,让我们开始吧!

新建一个项目:

cargo new rust-redis-web-example

在Cargo.toml中加入依赖:

[dependencies]
tokio = { version = "1.19", features = ["full"] }
warp = "0.3.2"
redis = "0.21"
r2d2_redis = "0.14"
mobc-redis = "0.7"
mobc = "0.7"
thiserror = "1.0"

首先,让我们设置一些共享类型,在main.rs中:

type WebResult= std::result::Result;
type Result= std::result::Result;
const REDIS_CON_STRING: &str = "redis://127.0.0.1/";

定义这两个Result类型是为了节省一些输入,并表示内部Errors (Result)和外部Errors (WebResult)。

接下来,定义这个内部error类型并为其实现Reject,以便它可以处理从程序返回的HTTP错误。

#[derive(Error, Debug)]
pub enum Error {
    #[error("mobc error: {0}")]
    MobcError(#[from] MobcError),
    #[error("r2d2 error: {0}")]
    R2D2Error(#[from] R2D2Error),
}
#[derive(Error, Debug)]
pub enum MobcError {
    #[error("could not get redis connection from pool : {0}")]
    RedisPoolError(mobc::Error),
    #[error("error parsing string from redis result: {0}")]
    RedisTypeError(mobc_redis::redis::RedisError),
    #[error("error executing redis command: {0}")]
    RedisCMDError(mobc_redis::redis::RedisError),
    #[error("error creating Redis client: {0}")]
    RedisClientError(mobc_redis::redis::RedisError),
}
#[derive(Error, Debug)]
pub enum R2D2Error {
    #[error("could not get redis connection from pool : {0}")]
    RedisPoolError(r2d2_redis::r2d2::Error),
    #[error("error parsing string from redis result: {0}")]
    RedisTypeError(r2d2_redis::redis::RedisError),
    #[error("error executing redis command: {0}")]
    RedisCMDError(r2d2_redis::redis::RedisError),
    #[error("error creating Redis client: {0}")]
    RedisClientError(r2d2_redis::redis::RedisError),
}
impl warp::reject::Reject for Error {}

上面定义了通用的错误类型和我们将实现的每一种使用Redis方法的错误类型。错误本身只是处理连接、池的创建和命令执行等 错误。

使用r2d2(同步)

r2d2 crate是第一个被广泛使用的连接池,它现在仍然被广泛使用。Redis的连接池是r2d2-redis crate。

在src目录下创建r2d2_pool.rs文件,因为我们现在使用的是连接池,所以这个池的创建也需要在r2d2模块中处理。

use crate::{R2D2Error::*, Result, REDIS_CON_STRING};
use r2d2_redis::redis::{Commands, FromRedisValue};
use r2d2_redis::{r2d2, RedisConnectionManager};
use std::time::Duration;
pub type R2D2Pool = r2d2::Pool;
pub type R2D2Con = r2d2::PooledConnection;
const CACHE_POOL_MAX_OPEN: u32 = 16;
const CACHE_POOL_MIN_IDLE: u32 = 8;
const CACHE_POOL_TIMEOUT_SECONDS: u64 = 1;
const CACHE_POOL_EXPIRE_SECONDS: u64 = 60;
pub fn connect() -> Result> {
    let manager = RedisConnectionManager::new(REDIS_CON_STRING).map_err(RedisClientError)?;
    r2d2::Pool::builder()
        .max_size(CACHE_POOL_MAX_OPEN)
        .max_lifetime(Some(Duration::from_secs(CACHE_POOL_EXPIRE_SECONDS)))
        .min_idle(Some(CACHE_POOL_MIN_IDLE))
        .build(manager)
        .map_err(|e| RedisPoolError(e).into())
}

定义一些常量来配置池,如打开和空闲连接,连接超时和连接的生命周期,池本身是使用RedisConnectionManager创建的,传递给它的参数是redis连接字符串。

不要太担心配置值,大多数连接池都有一些缺省值,这些缺省值将适用于基本应用程序。

我们需要一种方法来获得连接池,然后向Redis设置和获取值。

pub fn get_con(pool: &R2D2Pool) -> Result{
    pool.get_timeout(Duration::from_secs(CACHE_POOL_TIMEOUT_SECONDS))
        .map_err(|e| {
            eprintln!("error connecting to redis: {}", e);
            RedisPoolError(e).into()
        })
}
pub fn set_str(pool: &R2D2Pool, key: &str, value: &str, ttl_seconds: usize) -> Result<()> {
    let mut con = get_con(&pool)?;
    con.set(key, value).map_err(RedisCMDError)?;
    if ttl_seconds > 0 {
        con.expire(key, ttl_seconds).map_err(RedisCMDError)?;
    }
    Ok(())
}
pub fn get_str(pool: &R2D2Pool, key: &str) -> Result{
    let mut con = get_con(&pool)?;
    let value = con.get(key).map_err(RedisCMDError)?;
    FromRedisValue::from_redis_value(&value).map_err(|e| RedisTypeError(e).into())
}

我们尝试从池中获取连接,并配置超时时间。在set_str和get_str中,每次调用这些函数时都会调用get_con。

使用mobc(异步)

在src目录下创建r2d2_pool.rs文件,让我们定义配置并创建连接池。

use crate::{MobcError::*, Result, REDIS_CON_STRING};
use mobc::{Connection, Pool};
use mobc_redis::redis::{AsyncCommands, FromRedisValue};
use mobc_redis::{redis, RedisConnectionManager};
use std::time::Duration;
pub type MobcPool = Pool;
pub type MobcCon = Connection;
const CACHE_POOL_MAX_OPEN: u64 = 16;
const CACHE_POOL_MAX_IDLE: u64 = 8;
const CACHE_POOL_TIMEOUT_SECONDS: u64 = 1;
const CACHE_POOL_EXPIRE_SECONDS: u64 = 60;
pub async fn connect() -> Result{
    let client = redis::Client::open(REDIS_CON_STRING).map_err(RedisClientError)?;
    let manager = RedisConnectionManager::new(client);
    Ok(Pool::builder()
        .get_timeout(Some(Duration::from_secs(CACHE_POOL_TIMEOUT_SECONDS)))
        .max_open(CACHE_POOL_MAX_OPEN)
        .max_idle(CACHE_POOL_MAX_IDLE)
        .max_lifetime(Some(Duration::from_secs(CACHE_POOL_EXPIRE_SECONDS)))
        .build(manager))
}

这和r2d2非常相似,这不是巧合;许多连接池库都从r2d2出色的API中获得了灵感。

async fn get_con(pool: &MobcPool) -> Result{
    pool.get().await.map_err(|e| {
        eprintln!("error connecting to redis: {}", e);
        RedisPoolError(e).into()
    })
}
pub async fn set_str(pool: &MobcPool, key: &str, value: &str, ttl_seconds: usize) -> Result<()> {
    let mut con = get_con(&pool).await?;
    con.set(key, value).await.map_err(RedisCMDError)?;
    if ttl_seconds > 0 {
        con.expire(key, ttl_seconds).await.map_err(RedisCMDError)?;
    }
    Ok(())
}
pub async fn get_str(pool: &MobcPool, key: &str) -> Result{
    let mut con = get_con(&pool).await?;
    let value = con.get(key).await.map_err(RedisCMDError)?;
    FromRedisValue::from_redis_value(&value).map_err(|e| RedisTypeError(e).into())
}

现在看起来应该很熟悉了,传入池并在开始时获取连接,但这一次采用异步方式,使用async和await。

下一步就是把它们结合到一个warp web应用中,修改main.rs:

use std::convert::Infallible;
use mobc_pool::MobcPool;
use r2d2_pool::R2D2Pool;
use thiserror::Error;
use warp::{Rejection, Filter, Reply};
mod r2d2_pool;
mod mobc_pool;
type WebResult= std::result::Result;
type Result= std::result::Result;
const REDIS_CON_STRING: &str = "redis://127.0.0.1/";
#[tokio::main]
async fn main() {
    let mobc_pool = mobc_pool::connect().await.expect("can create mobc pool");
    let r2d2_pool = r2d2_pool::connect().expect("can create r2d2 pool");
    let mobc_route = warp::path!("mobc")
        .and(with_mobc_pool(mobc_pool.clone()))
        .and_then(mobc_handler);
    let r2d2_route = warp::path!("r2d2")
        .and(with_r2d2_pool(r2d2_pool.clone()))
        .and_then(r2d2_handler);
    let routes = mobc_route.or(r2d2_route);
    warp::serve(routes).run(([0, 0, 0, 0], 8080)).await;
}
fn with_mobc_pool(
    pool: MobcPool,
) -> impl Filter+ Clone {
    warp::any().map(move || pool.clone())
}
fn with_r2d2_pool(
    pool: R2D2Pool,
) -> impl Filter+ Clone {
    warp::any().map(move || pool.clone())
}
async fn mobc_handler(pool: MobcPool) -> WebResult{
    mobc_pool::set_str(&pool, "mobc_hello", "mobc_world", 60)
        .await
        .map_err(|e| warp::reject::custom(e))?;
    let value = mobc_pool::get_str(&pool, "mobc_hello")
        .await
        .map_err(|e| warp::reject::custom(e))?;
    Ok(value)
}
async fn r2d2_handler(pool: R2D2Pool) -> WebResult{
    r2d2_pool::set_str(&pool, "r2d2_hello", "r2d2_world", 60)
        .map_err(|e| warp::reject::custom(e))?;
    let value = r2d2_pool::get_str(&pool, "r2d2_hello").map_err(|e| warp::reject::custom(e))?;
    Ok(value)
}

用Docker启动一个本地Redis实例:

docker run -p 6379:6379 redis

接下来,运行 cargo run。

使用curl测试:

curl http://localhost:8080/r2d2
curl http://localhost:8080/mobc

到此这篇关于在Rust web服务中使用Redis的文章就介绍到这了,更多相关Rust 使用Redis内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

你可能感兴趣的:(在Rust web服务中使用Redis的方法)