在Rust?web服務(wù)中使用Redis的方法
Redis一直是網(wǎng)絡(luò)生態(tài)系統(tǒng)的重要組成部分,它經(jīng)常用作緩存、消息代理或簡(jiǎn)單地用作數(shù)據(jù)存儲(chǔ)。
在這篇文章中,我們將演示如何在一個(gè)Rust web應(yīng)用程序中使用Redis。
我們將探索兩種種使用Redis的方法:
使用同步連接池
使用異步連接池
對(duì)于同步池,我們使用基于r2d2庫(kù)的r2d2-redis。我們?cè)诋惒浇鉀Q方案中使用mobc,還有許多其他異步連接池,如deadpool和bb8,它們都以類似的方式工作。
話不多說(shuō),讓我們開始吧!
新建一個(gè)項(xiàng)目:
cargo new rust-redis-web-example
在Cargo.toml中加入依賴:
[dependencies]</code> <code>tokio = { version = "1.19", features = ["full"] }</code> <code>warp = "0.3.2"</code> <code>redis = "0.21"</code> <code>r2d2_redis = "0.14"</code> <code>mobc-redis = "0.7"</code> <code>mobc = "0.7"</code> <code>thiserror = "1.0"
首先,讓我們?cè)O(shè)置一些共享類型,在main.rs中:
type WebResult= std::result::Result;</code> <code>type Result= std::result::Result;</code> <code>const REDIS_CON_STRING: &str = "redis://127.0.0.1/";
定義這兩個(gè)Result類型是為了節(jié)省一些輸入,并表示內(nèi)部Errors (Result)和外部Errors (WebResult)。
接下來(lái),定義這個(gè)內(nèi)部error類型并為其實(shí)現(xiàn)Reject,以便它可以處理從程序返回的HTTP錯(cuò)誤。
#[derive(Error, Debug)]</code> <code>pub enum Error {</code> <code> #[error("mobc error: {0}")]</code> <code> MobcError(#[from] MobcError),</code> <code> #[error("r2d2 error: {0}")]</code> <code> R2D2Error(#[from] R2D2Error),</code> <code>}</code> <code>#[derive(Error, Debug)]</code> <code>pub enum MobcError {</code> <code> #[error("could not get redis connection from pool : {0}")]</code> <code> RedisPoolError(mobc::Error),</code> <code> #[error("error parsing string from redis result: {0}")]</code> <code> RedisTypeError(mobc_redis::redis::RedisError),</code> <code> #[error("error executing redis command: {0}")]</code> <code> RedisCMDError(mobc_redis::redis::RedisError),</code> <code> #[error("error creating Redis client: {0}")]</code> <code> RedisClientError(mobc_redis::redis::RedisError),</code> <code>}</code> <code>#[derive(Error, Debug)]</code> <code>pub enum R2D2Error {</code> <code> #[error("could not get redis connection from pool : {0}")]</code> <code> RedisPoolError(r2d2_redis::r2d2::Error),</code> <code> #[error("error parsing string from redis result: {0}")]</code> <code> RedisTypeError(r2d2_redis::redis::RedisError),</code> <code> #[error("error executing redis command: {0}")]</code> <code> RedisCMDError(r2d2_redis::redis::RedisError),</code> <code> #[error("error creating Redis client: {0}")]</code> <code> RedisClientError(r2d2_redis::redis::RedisError),</code> <code>}</code> <code>impl warp::reject::Reject for Error {}
上面定義了通用的錯(cuò)誤類型和我們將實(shí)現(xiàn)的每一種使用Redis方法的錯(cuò)誤類型。錯(cuò)誤本身只是處理連接、池的創(chuàng)建和命令執(zhí)行等 錯(cuò)誤。
使用r2d2(同步)
r2d2 crate是第一個(gè)被廣泛使用的連接池,它現(xiàn)在仍然被廣泛使用。Redis的連接池是r2d2-redis crate。
在src目錄下創(chuàng)建r2d2_pool.rs文件,因?yàn)槲覀儸F(xiàn)在使用的是連接池,所以這個(gè)池的創(chuàng)建也需要在r2d2模塊中處理。
use crate::{R2D2Error::*, Result, REDIS_CON_STRING};</code> <code>use r2d2_redis::redis::{Commands, FromRedisValue};</code> <code>use r2d2_redis::{r2d2, RedisConnectionManager};</code> <code>use std::time::Duration;</code> <code>pub type R2D2Pool = r2d2::Pool;</code> <code>pub type R2D2Con = r2d2::PooledConnection;</code> <code>const CACHE_POOL_MAX_OPEN: u32 = 16;</code> <code>const CACHE_POOL_MIN_IDLE: u32 = 8;</code> <code>const CACHE_POOL_TIMEOUT_SECONDS: u64 = 1;</code> <code>const CACHE_POOL_EXPIRE_SECONDS: u64 = 60;</code> <code>pub fn connect() -> Result> {</code> <code> let manager = RedisConnectionManager::new(REDIS_CON_STRING).map_err(RedisClientError)?;</code> <code> r2d2::Pool::builder()</code> <code> .max_size(CACHE_POOL_MAX_OPEN)</code> <code> .max_lifetime(Some(Duration::from_secs(CACHE_POOL_EXPIRE_SECONDS)))</code> <code> .min_idle(Some(CACHE_POOL_MIN_IDLE))</code> <code> .build(manager)</code> <code> .map_err(|e| RedisPoolError(e).into())</code> <code>}
定義一些常量來(lái)配置池,如打開和空閑連接,連接超時(shí)和連接的生命周期,池本身是使用RedisConnectionManager創(chuàng)建的,傳遞給它的參數(shù)是redis連接字符串。
不要太擔(dān)心配置值,大多數(shù)連接池都有一些缺省值,這些缺省值將適用于基本應(yīng)用程序。
我們需要一種方法來(lái)獲得連接池,然后向Redis設(shè)置和獲取值。
pub fn get_con(pool: &R2D2Pool) -> Result{</code> <code> pool.get_timeout(Duration::from_secs(CACHE_POOL_TIMEOUT_SECONDS))</code> <code> .map_err(|e| {</code> <code> eprintln!("error connecting to redis: {}", e);</code> <code> RedisPoolError(e).into()</code> <code> })</code> <code>}</code> <code>pub fn set_str(pool: &R2D2Pool, key: &str, value: &str, ttl_seconds: usize) -> Result<()> {</code> <code> let mut con = get_con(&pool)?;</code> <code> con.set(key, value).map_err(RedisCMDError)?;</code> <code> if ttl_seconds > 0 {</code> <code> con.expire(key, ttl_seconds).map_err(RedisCMDError)?;</code> <code> }</code> <code> Ok(())</code> <code>}</code> <code>pub fn get_str(pool: &R2D2Pool, key: &str) -> Result{</code> <code> let mut con = get_con(&pool)?;</code> <code> let value = con.get(key).map_err(RedisCMDError)?;</code> <code> FromRedisValue::from_redis_value(&value).map_err(|e| RedisTypeError(e).into())</code> <code>}
我們嘗試從池中獲取連接,并配置超時(shí)時(shí)間。在set_str和get_str中,每次調(diào)用這些函數(shù)時(shí)都會(huì)調(diào)用get_con。
使用mobc(異步)
在src目錄下創(chuàng)建r2d2_pool.rs文件,讓我們定義配置并創(chuàng)建連接池。
use crate::{MobcError::*, Result, REDIS_CON_STRING};</code> <code>use mobc::{Connection, Pool};</code> <code>use mobc_redis::redis::{AsyncCommands, FromRedisValue};</code> <code>use mobc_redis::{redis, RedisConnectionManager};</code> <code>use std::time::Duration;</code> <code>pub type MobcPool = Pool;</code> <code>pub type MobcCon = Connection;</code> <code>const CACHE_POOL_MAX_OPEN: u64 = 16;</code> <code>const CACHE_POOL_MAX_IDLE: u64 = 8;</code> <code>const CACHE_POOL_TIMEOUT_SECONDS: u64 = 1;</code> <code>const CACHE_POOL_EXPIRE_SECONDS: u64 = 60;</code> <code>pub async fn connect() -> Result{</code> <code> let client = redis::Client::open(REDIS_CON_STRING).map_err(RedisClientError)?;</code> <code> let manager = RedisConnectionManager::new(client);</code> <code> Ok(Pool::builder()</code> <code> .get_timeout(Some(Duration::from_secs(CACHE_POOL_TIMEOUT_SECONDS)))</code> <code> .max_open(CACHE_POOL_MAX_OPEN)</code> <code> .max_idle(CACHE_POOL_MAX_IDLE)</code> <code> .max_lifetime(Some(Duration::from_secs(CACHE_POOL_EXPIRE_SECONDS)))</code> <code> .build(manager))</code> <code>}
這和r2d2非常相似,這不是巧合;許多連接池庫(kù)都從r2d2出色的API中獲得了靈感。
async fn get_con(pool: &MobcPool) -> Result{</code> <code> pool.get().await.map_err(|e| {</code> <code> eprintln!("error connecting to redis: {}", e);</code> <code> RedisPoolError(e).into()</code> <code> })</code> <code>}</code> <code>pub async fn set_str(pool: &MobcPool, key: &str, value: &str, ttl_seconds: usize) -> Result<()> {</code> <code> let mut con = get_con(&pool).await?;</code> <code> con.set(key, value).await.map_err(RedisCMDError)?;</code> <code> if ttl_seconds > 0 {</code> <code> con.expire(key, ttl_seconds).await.map_err(RedisCMDError)?;</code> <code> }</code> <code> Ok(())</code> <code>}</code> <code>pub async fn get_str(pool: &MobcPool, key: &str) -> Result{</code> <code> let mut con = get_con(&pool).await?;</code> <code> let value = con.get(key).await.map_err(RedisCMDError)?;</code> <code> FromRedisValue::from_redis_value(&value).map_err(|e| RedisTypeError(e).into())</code> <code>}
現(xiàn)在看起來(lái)應(yīng)該很熟悉了,傳入池并在開始時(shí)獲取連接,但這一次采用異步方式,使用async和await。
下一步就是把它們結(jié)合到一個(gè)warp web應(yīng)用中,修改main.rs:
use std::convert::Infallible;</code> <code>use mobc_pool::MobcPool;</code> <code>use r2d2_pool::R2D2Pool;</code> <code>use thiserror::Error;</code> <code>use warp::{Rejection, Filter, Reply};</code> <code>mod r2d2_pool;</code> <code>mod mobc_pool;</code> <code>type WebResult= std::result::Result;</code> <code>type Result= std::result::Result;</code> <code>const REDIS_CON_STRING: &str = "redis://127.0.0.1/";</code> <code>#[tokio::main]</code> <code>async fn main() {</code> <code> let mobc_pool = mobc_pool::connect().await.expect("can create mobc pool");</code> <code> let r2d2_pool = r2d2_pool::connect().expect("can create r2d2 pool");</code> <code> let mobc_route = warp::path!("mobc")</code> <code> .and(with_mobc_pool(mobc_pool.clone()))</code> <code> .and_then(mobc_handler);</code> <code> let r2d2_route = warp::path!("r2d2")</code> <code> .and(with_r2d2_pool(r2d2_pool.clone()))</code> <code> .and_then(r2d2_handler);</code> <code> let routes = mobc_route.or(r2d2_route);</code> <code> warp::serve(routes).run(([0, 0, 0, 0], 8080)).await;</code> <code>}</code> <code>fn with_mobc_pool(</code> <code> pool: MobcPool,</code> <code>) -> impl Filter+ Clone {</code> <code> warp::any().map(move || pool.clone())</code> <code>}</code> <code>fn with_r2d2_pool(</code> <code> pool: R2D2Pool,</code> <code>) -> impl Filter+ Clone {</code> <code> warp::any().map(move || pool.clone())</code> <code>}</code> <code>async fn mobc_handler(pool: MobcPool) -> WebResult{</code> <code> mobc_pool::set_str(&pool, "mobc_hello", "mobc_world", 60)</code> <code> .await</code> <code> .map_err(|e| warp::reject::custom(e))?;</code> <code> let value = mobc_pool::get_str(&pool, "mobc_hello")</code> <code> .await</code> <code> .map_err(|e| warp::reject::custom(e))?;</code> <code> Ok(value)</code> <code>}</code> <code>async fn r2d2_handler(pool: R2D2Pool) -> WebResult{</code> <code> r2d2_pool::set_str(&pool, "r2d2_hello", "r2d2_world", 60)</code> <code> .map_err(|e| warp::reject::custom(e))?;</code> <code> let value = r2d2_pool::get_str(&pool, "r2d2_hello").map_err(|e| warp::reject::custom(e))?;</code> <code> Ok(value)</code> <code>}
用Docker啟動(dòng)一個(gè)本地Redis實(shí)例:
docker run -p 6379:6379 redis
接下來(lái),運(yùn)行 cargo run。
使用curl測(cè)試:
curl http://localhost:8080/r2d2</code> <code>curl http://localhost:8080/mobc
到此這篇關(guān)于在Rust web服務(wù)中使用Redis的文章就介紹到這了,更多相關(guān)Rust 使用Redis內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust語(yǔ)言從入門到精通系列之Iterator迭代器深入詳解
這篇文章主要為大家介紹了Rust語(yǔ)言從入門到精通系列之Iterator迭代器深入詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Rust在寫庫(kù)時(shí)實(shí)現(xiàn)緩存的操作方法
Moka是一個(gè)用于Rust的高性能緩存庫(kù),它提供了多種類型的緩存數(shù)據(jù)結(jié)構(gòu),包括哈希表、LRU(最近最少使用)緩存和?支持TTL(生存時(shí)間)緩存,這篇文章給大家介紹Rust在寫庫(kù)時(shí)實(shí)現(xiàn)緩存的相關(guān)知識(shí),感興趣的朋友一起看看吧2024-01-01Rust 連接 SQLite 數(shù)據(jù)庫(kù)的過(guò)程解析
本文通過(guò)一個(gè)例子給大家介紹了Rust 連接 SQLite 數(shù)據(jù)庫(kù)的詳細(xì)過(guò)程,我使用rusqlite這個(gè)crate,對(duì)Rust 連接 SQLite 數(shù)據(jù)庫(kù)相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧2022-01-01Rust個(gè)人學(xué)習(xí)小結(jié)之Rust的循環(huán)
這篇文章主要介紹了Rust個(gè)人學(xué)習(xí)小結(jié)之Rust的循環(huán),今天主要了解了Rust語(yǔ)言的3種循環(huán)方法:?loop、while、for,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01Rust for循環(huán)語(yǔ)法糖背后的API場(chǎng)景分析
for語(yǔ)句是一種能確定循環(huán)次數(shù)的循環(huán),for 語(yǔ)句用于執(zhí)行代碼塊指定的次數(shù),今天通過(guò)本文給大家介紹Rust for循環(huán)語(yǔ)法糖背后的API場(chǎng)景分析,感興趣的朋友跟隨小編一起看看吧2022-11-11Rust 的 into_owned() 方法實(shí)例詳解
into_owned是Rust語(yǔ)言中std::borrow::Cow 枚舉的一個(gè)方法,into_owned確保了調(diào)用者獲得數(shù)據(jù)的獨(dú)立所有權(quán),無(wú)論Cow之前是引用還是已經(jīng)擁有數(shù)據(jù),本文給大家介紹Rust 的 into_owned() 方法,感興趣的的朋友跟隨小編一起看看吧2024-03-03