缓存:Redis
在 MySQL 中的大查询或 MongoDB 中的大聚合可能需要几秒甚至几分钟才能完成。你绝对不希望频繁地触发这些操作。
将查询或聚合结果缓存到内存中是缓解这个问题的绝佳方法。如果你的 API 服务器在单个机器或节点上运行,只需将这些结果放入内存中的 HashMaps
或 Dictionaries
中即可解决问题。
但是,如果你有多台机器或节点运行 API 服务器并共享其公共内存的话,则 Redis 才是你的最佳选择。
尝试 Redis
-
在你的机器上安装 Redis 并启动它。
-
添加 redis 依赖。
cargo add redis
Cargo.toml 中变化:
@@ -10,6 +10,7 @@ chrono = { version = "0.4.35", features = ["serde"] }
lazy_static = "1.4.0"
mongodb = { version = "2.8.2", default-features = false, features = ["sync"] }
mysql = "24.0.0"
+redis = "0.25.2"
rocket = { version = "0.5.0", features = ["json"] }
rusqlite = "0.31.0"
serde = { version = "1.0.197", features = ["derive"] }
- 更新代码。
添加 infrastructure/cache/helper.rs:
use std::error::Error;
pub trait Helper: Send + Sync {
fn save(&self, key: &str, value: &str) -> Result<(), Box<dyn Error>>;
fn load(&self, key: &str) -> Result<Option<String>, Box<dyn Error>>;
}
使用 redis,infrastructure/cache/redis.rs:
use std::error::Error;
use std::sync::RwLock;
use redis::{Client, Commands, Connection};
use crate::infrastructure::cache::Helper;
const DEFAULT_TTL: u64 = 3600; // seconds
pub struct RedisCache {
conn: RwLock<Connection>,
}
impl RedisCache {
pub fn new(redis_uri: &str) -> Result<Self, Box<dyn Error>> {
let client = Client::open(redis_uri)?;
let conn = client.get_connection()?;
Ok(Self {
conn: RwLock::new(conn),
})
}
}
impl Helper for RedisCache {
fn save(&self, key: &str, value: &str) -> Result<(), Box<dyn Error>> {
let mut conn = self.conn.write().unwrap();
conn.set_ex(key, value, DEFAULT_TTL)?;
Ok(())
}
fn load(&self, key: &str) -> Result<Option<String>, Box<dyn Error>> {
// Caution: `conn.read()` doesn't work here
let mut conn = self.conn.write().unwrap();
let result: Option<String> = conn.get(key)?;
Ok(result)
}
}
导出符号,infrastructure/cache/mod.rs:
mod redis;
pub use redis::RedisCache;
mod helper;
pub use helper::Helper;
添加相应配置项,infrastructure/config/mod.rs:
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
pub app: ApplicationConfig,
+ pub cache: CacheConfig,
pub db: DBConfig,
}
@@ -16,6 +17,11 @@ pub struct DBConfig {
pub mongo_db_name: String,
}
+#[derive(Debug, Deserialize, Serialize)]
+pub struct CacheConfig {
+ pub redis_uri: String,
+}
+
#[derive(Debug, Deserialize, Serialize)]
pub struct ApplicationConfig {
pub port: i32,
放入对应配置值,config.toml:
@@ -6,3 +6,6 @@ file_name = "test.db"
dsn = "mysql://test_user:test_pass@127.0.0.1:3306/lr_book"
mongo_uri = "mongodb://localhost:27017"
mongo_db_name = "lr_book"
+
+[cache]
+redis_uri = "redis://:test_pass@localhost:6379/0"
合入 redis 连接,application/wire_helper.rs:
@@ -1,12 +1,14 @@
use std::sync::Arc;
use crate::domain::gateway;
+use crate::infrastructure::cache;
use crate::infrastructure::database;
use crate::infrastructure::Config;
pub struct WireHelper {
sql_persistence: Arc<database::MySQLPersistence>,
no_sql_persistence: Arc<database::MongoPersistence>,
+ kv_store: Arc<cache::RedisCache>,
}
impl WireHelper {
@@ -16,9 +18,11 @@ impl WireHelper {
&c.db.mongo_uri,
&c.db.mongo_db_name,
)?);
+ let kv_store = Arc::new(cache::RedisCache::new(&c.cache.redis_uri)?);
Ok(WireHelper {
sql_persistence,
no_sql_persistence,
+ kv_store,
})
}
@@ -29,4 +33,8 @@ impl WireHelper {
pub fn review_manager(&self) -> Arc<dyn gateway::ReviewManager> {
Arc::clone(&self.no_sql_persistence) as Arc<dyn gateway::ReviewManager>
}
+
+ pub fn cache_helper(&self) -> Arc<dyn cache::Helper> {
+ Arc::clone(&self.kv_store) as Arc<dyn cache::Helper>
+ }
}
假设列出所有图书操作需要在数据库中执行一个大查询,你需要将查询结果存入 Redis 以便下次可快速访问。
更改 application/executor/book_operator.rs:
@@ -2,14 +2,21 @@ use std::sync::Arc;
use crate::domain::gateway;
use crate::domain::model;
+use crate::infrastructure::cache;
+
+const BOOKS_KEY: &str = "lr-books";
pub struct BookOperator {
book_manager: Arc<dyn gateway::BookManager>,
+ cache_helper: Arc<dyn cache::Helper>,
}
impl BookOperator {
- pub fn new(b: Arc<dyn gateway::BookManager>) -> Self {
- BookOperator { book_manager: b }
+ pub fn new(b: Arc<dyn gateway::BookManager>, c: Arc<dyn cache::Helper>) -> Self {
+ BookOperator {
+ book_manager: b,
+ cache_helper: c,
+ }
}
pub fn create_book(&self, b: model::Book) -> Result<model::Book, Box<dyn std::error::Error>> {
@@ -24,7 +31,16 @@ impl BookOperator {
}
pub fn get_books(&self) -> Result<Vec<model::Book>, Box<dyn std::error::Error>> {
- self.book_manager.get_books()
+ let raw_value = self.cache_helper.load(BOOKS_KEY)?;
+ if let Some(v) = raw_value {
+ let cached_books = serde_json::from_str(&v)?;
+ Ok(cached_books)
+ } else {
+ let fetched_books = self.book_manager.get_books()?;
+ let v = serde_json::to_string(&fetched_books)?;
+ self.cache_helper.save(BOOKS_KEY, &v)?;
+ Ok(fetched_books)
+ }
}
pub fn update_book(
微调 adapter/router.rs:
@@ -212,7 +212,10 @@ pub fn delete_review(
pub fn make_router(wire_helper: &application::WireHelper) -> RestHandler {
RestHandler {
- book_operator: executor::BookOperator::new(wire_helper.book_manager()),
+ book_operator: executor::BookOperator::new(
+ wire_helper.book_manager(),
+ wire_helper.cache_helper(),
+ ),
review_operator: executor::ReviewOperator::new(wire_helper.review_manager()),
}
}
这些就是引入 redis 所需的调整。现在让我们试下缓存驱动的新端点。
用 curl 进行测试
列出所有图书:
curl -X GET -w "Total time: %{time_total}s\n" http://localhost:8000/books
结果与之前相似,但是性能显著提升。你可以通过 curl 的日志看出迹象。
# Rocket logs
GET /books:
>> Matched: (get_books) GET /books
>> Outcome: Success(200 OK)
>> Response succeeded.
GET /books:
>> Matched: (get_books) GET /books
>> Outcome: Success(200 OK)
>> Response succeeded.
GET /books:
>> Matched: (get_books) GET /books
>> Outcome: Success(200 OK)
>> Response succeeded.
...
# curl logs
Total time: 0.014904s
Total time: 0.007797s
Total time: 0.009242s
Total time: 0.008859s
Total time: 0.008415s
使用 redis-cli
查看 Redis 中的值:
redis-cli
在 redis 客户端 shell 中调试这些键值:
127.0.0.1:6379> keys *
1) "lr-books"
127.0.0.1:6379> get lr-books
"[{\"id\":3,\"title\":\"Sample Book\",\"author\":\"John Doe\",\"published_at\":\"2023-01-01\",\"description\":\"A sample book description\",\"isbn\":\"1234567890\",\"total_pages\":200,\"created_at\":\"2024-03-01 12:40:16\",\"updated_at\":\"2024-03-01 12:40:16\"},{\"id\":4,\"title\":\"The Great Gatsby\",\"author\":\"F. Scott Fitzgerald\",\"published_at\":\"1925-04-10\",\"description\":\"A novel depicting the opulent lives of wealthy Long Island residents during the Jazz Age.\",\"isbn\":\"9780743273565\",\"total_pages\":218,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":5,\"title\":\"To Kill a Mockingbird\",\"author\":\"Harper Lee\",\"published_at\":\"1960-07-11\",\"description\":\"A novel set in the American South during the 1930s, dealing with themes of racial injustice and moral growth.\",\"isbn\":\"9780061120084\",\"total_pages\":281,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":6,\"title\":\"1984\",\"author\":\"George Orwell\",\"published_at\":\"1949-06-08\",\"description\":\"A dystopian novel depicting a totalitarian regime, surveillance, and propaganda.\",\"isbn\":\"9780451524935\",\"total_pages\":328,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":7,\"title\":\"Pride and Prejudice\",\"author\":\"Jane Austen\",\"published_at\":\"1813-01-28\",\"description\":\"A classic novel exploring the themes of love, reputation, and social class in Georgian England.\",\"isbn\":\"9780486284736\",\"total_pages\":279,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":8,\"title\":\"The Catcher in the Rye\",\"author\":\"J.D. Salinger\",\"published_at\":\"1951-07-16\",\"description\":\"A novel narrated by a disaffected teenager, exploring themes of alienation and identity.\",\"isbn\":\"9780316769488\",\"total_pages\":277,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":9,\"title\":\"The Lord of the Rings\",\"author\":\"J.R.R. Tolkien\",\"published_at\":\"1954-07-29\",\"description\":\"A high fantasy epic following the quest to destroy the One Ring and defeat the Dark Lord Sauron.\",\"isbn\":\"9780544003415\",\"total_pages\":1178,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":10,\"title\":\"Moby-Dick\",\"author\":\"Herman Melville\",\"published_at\":\"1851-10-18\",\"description\":\"A novel exploring themes of obsession, revenge, and the nature of good and evil.\",\"isbn\":\"9780142000083\",\"total_pages\":624,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":11,\"title\":\"The Hobbit\",\"author\":\"J.R.R. Tolkien\",\"published_at\":\"1937-09-21\",\"description\":\"A fantasy novel set in Middle-earth, following the adventure of Bilbo Baggins and the quest for treasure.\",\"isbn\":\"9780345339683\",\"total_pages\":310,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":12,\"title\":\"The Adventures of Huckleberry Finn\",\"author\":\"Mark Twain\",\"published_at\":\"1884-12-10\",\"description\":\"A novel depicting the journey of a young boy and an escaped slave along the Mississippi River.\",\"isbn\":\"9780486280615\",\"total_pages\":366,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":13,\"title\":\"War and Peace\",\"author\":\"Leo Tolstoy\",\"published_at\":\"1869-01-01\",\"description\":\"A novel depicting the Napoleonic era in Russia, exploring themes of love, war, and historical determinism.\",\"isbn\":\"9781400079988\",\"total_pages\":1392,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":14,\"title\":\"Alice\xe2\x80\x99s Adventures in Wonderland\",\"author\":\"Lewis Carroll\",\"published_at\":\"1865-11-26\",\"description\":\"A children\xe2\x80\x99s novel featuring a young girl named Alice who falls into a fantastical world populated by peculiar creatures.\",\"isbn\":\"9780141439761\",\"total_pages\":192,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":15,\"title\":\"The Odyssey\",\"author\":\"Homer\",\"published_at\":\"8th Century BC\",\"description\":\"An ancient Greek epic poem attributed to Homer, detailing the journey of Odysseus after the Trojan War.\",\"isbn\":\"9780140268867\",\"total_pages\":541,\"created_at\":\"2024-03-02 05:48:25\",\"updated_at\":\"2024-03-02 05:48:25\"},{\"id\":16,\"title\":\"Sample Book\",\"author\":\"John Doe\",\"published_at\":\"2023-01-01\",\"description\":\"A sample book description\",\"isbn\":\"1234567890\",\"total_pages\":200,\"created_at\":\"2024-03-07 10:17:59\",\"updated_at\":\"2024-03-07 10:17:59\"},{\"id\":17,\"title\":\"Sample Book\",\"author\":\"John Doe\",\"published_at\":\"2023-01-01\",\"description\":\"A sample book description\",\"isbn\":\"1234567890\",\"total_pages\":200,\"created_at\":\"2024-03-07 10:18:20\",\"updated_at\":\"2024-03-07 10:18:20\"},{\"id\":18,\"title\":\"Test Book\",\"author\":\"John Doe\",\"published_at\":\"2003-01-01\",\"description\":\"A sample book description\",\"isbn\":\"1234567890\",\"total_pages\":100,\"created_at\":\"2024-03-08 13:10:49\",\"updated_at\":\"2024-03-08 13:10:49\"},{\"id\":20,\"title\":\"Updated Book Title\",\"author\":\"Jane Smith\",\"published_at\":\"2023-01-01\",\"description\":\"A sample book description\",\"isbn\":\"1234567890\",\"total_pages\":200,\"created_at\":\"2024-03-16 08:27:41\",\"updated_at\":\"2024-03-16 08:29:37\"}]"
127.0.0.1:6379> del lr-books
(integer) 1
赞!Redis 已可以供君驱使了!💐