缓存:Redis
在 MySQL 中的大查询或 MongoDB 中的大聚合可能需要几秒甚至几分钟才能完成。你绝对不希望频繁地触发这些操作。
将查询或聚合结果缓存到内存中是缓解这个问题的绝佳方法。如果你的 API 服务器在单个机器或节点上运行,只需将这些结果放入内存中的 HashMaps
或 Dictionaries
中即可解决问题。
但是,如果你有多台机器或节点运行 API 服务器并共享其公共内存的话,则 Redis 才是你的最佳选择。
尝试 Redis
-
在你的机器上安装 Redis 并启动它。
-
添加 redis 依赖。
npm i ioredis
- 更新代码。
添加 infrastructure/cache/helper.ts:
export interface CacheHelper {
save(key: string, value: string): Promise<void>;
load(key: string): Promise<string | null>;
}
在 infrastructure/cache/redis.ts 中使用 redis:
import Redis, { RedisOptions } from "ioredis";
import { CacheConfig } from "@/infrastructure/config/config";
import { CacheHelper } from "./helper";
const defaultTTL = 3600; // seconds
export class RedisCache implements CacheHelper {
private client: Redis;
constructor(c: CacheConfig) {
const options: RedisOptions = {
host: c.host,
port: c.port,
password: c.password,
db: c.db,
commandTimeout: c.timeout,
};
this.client = new Redis(options);
console.log("Connected to Redis");
}
async save(key: string, value: string): Promise<void> {
await this.client.set(key, value, "EX", defaultTTL);
}
async load(key: string): Promise<string | null> {
return await this.client.get(key);
}
close(): void {
this.client.disconnect();
}
}
导出如下内容,infrastructure/cache/index.ts:
export { RedisCache } from "./redis";
export { CacheHelper } from "./helper";
添加相关配置项,在 infrastructure/config/config.ts 中:
@@ -11,9 +11,18 @@ interface ApplicationConfig {
port: number;
}
+export interface CacheConfig {
+ host: string;
+ port: number;
+ password: string;
+ db: number;
+ timeout: number; // in milliseconds
+}
+
export interface Config {
app: ApplicationConfig;
db: DBConfig;
+ cache: CacheConfig;
}
export function parseConfig(filename: string): Config {
置入配置值,config.json:
@@ -7,5 +7,12 @@
"dsn": "mysql://test_user:test_pass@127.0.0.1:3306/lr_book?charset=utf8mb4",
"mongo_uri": "mongodb://localhost:27017",
"mongo_db_name": "lr_book"
+ },
+ "cache": {
+ "host": "localhost",
+ "port": 6379,
+ "password": "test_pass",
+ "db": 0,
+ "timeout": 5000
}
}
引入 redis 连接,application/wire_helper.ts:
@@ -1,11 +1,13 @@
-import { MySQLPersistence, MongoPersistence } from "@/infrastructure/database";
import { Config } from "@/infrastructure/config";
import { BookManager, ReviewManager } from "@/domain/gateway";
+import { MySQLPersistence, MongoPersistence } from "@/infrastructure/database";
+import { RedisCache, CacheHelper } from "@/infrastructure/cache";
// WireHelper is the helper for dependency injection
export class WireHelper {
private sql_persistence: MySQLPersistence;
private no_sql_persistence: MongoPersistence;
+ private kv_store: RedisCache;
constructor(c: Config) {
this.sql_persistence = new MySQLPersistence(c.db.dsn);
@@ -13,6 +15,7 @@ export class WireHelper {
c.db.mongo_uri,
c.db.mongo_db_name
);
+ this.kv_store = new RedisCache(c.cache);
}
bookManager(): BookManager {
@@ -22,4 +25,8 @@ export class WireHelper {
reviewManager(): ReviewManager {
return this.no_sql_persistence;
}
+
+ cacheHelper(): CacheHelper {
+ return this.kv_store;
+ }
}
假设列出所有图书操作需要在数据库中执行一个大查询,你需要将查询结果存入 Redis 以便下次可快速访问。
更改 application/executor/book_operator.ts:
@@ -1,11 +1,16 @@
import { BookManager } from "@/domain/gateway";
import { Book } from "@/domain/model";
+import { CacheHelper } from "@/infrastructure/cache";
+
+const booksKey = "lr-books";
export class BookOperator {
private bookManager: BookManager;
+ private cacheHelper: CacheHelper;
- constructor(b: BookManager) {
+ constructor(b: BookManager, c: CacheHelper) {
this.bookManager = b;
+ this.cacheHelper = c;
}
async createBook(b: Book): Promise<Book> {
@@ -19,7 +24,13 @@ export class BookOperator {
}
async getBooks(): Promise<Book[]> {
- return await this.bookManager.getBooks();
+ const cache_value = await this.cacheHelper.load(booksKey);
+ if (cache_value) {
+ return JSON.parse(cache_value);
+ }
+ const books = await this.bookManager.getBooks();
+ await this.cacheHelper.save(booksKey, JSON.stringify(books));
+ return books;
}
async updateBook(id: number, b: Book): Promise<Book> {
微调 adapter/router.ts:
@@ -158,7 +158,7 @@ class RestHandler {
// Create router
function MakeRouter(wireHelper: WireHelper): express.Router {
const restHandler = new RestHandler(
- new BookOperator(wireHelper.bookManager()),
+ new BookOperator(wireHelper.bookManager(), wireHelper.cacheHelper()),
new ReviewOperator(wireHelper.reviewManager())
);
@@ -187,8 +187,12 @@ export function InitApp(wireHelper: WireHelper): express.Express {
// Middleware to parse JSON bodies
app.use(express.json());
- // Use Morgan middleware with predefined 'combined' format
- app.use(morgan("combined"));
+ // Use Morgan middleware with predefined tokens
+ app.use(
+ morgan(
+ ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" - :response-time ms'
+ )
+ );
// Define a health endpoint handler
app.get("/", (req: Request, res: Response) => {
morgan("combined")
日志格式没有响应时间,所以咱们需要自定义格式。
这些就是引入 redis 所需的调整。现在让我们试下缓存驱动的新端点。
用 curl 进行测试
列出所有图书:
curl -X GET http://localhost:3000/books
结果与之前相似,但是性能显著提升。你可以通过 Morgan 中间件的日志看出迹象。
::ffff:127.0.0.1 - - [02/Mar/2024:04:58:20 +0000] "GET /books HTTP/1.1" 200 483 "-" "curl/8.1.2" - 10.861 ms
::ffff:127.0.0.1 - - [02/Mar/2024:04:58:23 +0000] "GET /books HTTP/1.1" 200 483 "-" "curl/8.1.2" - 1.272 ms
::ffff:127.0.0.1 - - [02/Mar/2024:04:58:23 +0000] "GET /books HTTP/1.1" 200 483 "-" "curl/8.1.2" - 1.093 ms
::ffff:127.0.0.1 - - [02/Mar/2024:04:58:30 +0000] "GET /books HTTP/1.1" 200 483 "-" "curl/8.1.2" - 1.145 ms
使用 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\":2,\"title\":\"Sample Book 222\",\"author\":\"John Doe\",\"published_at\":\"2023-01-01\",\"description\":\"A sample book description\",\"isbn\":\"1234567890\",\"total_pages\":200,\"created_at\":\"2024-03-01T04:11:57.000Z\",\"updated_at\":\"2024-03-01T04:11:57.000Z\"},{\"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-01T04:40:16.000Z\",\"updated_at\":\"2024-03-01T04:40:16.000Z\"}]"
127.0.0.1:6379> del lr-books
(integer) 1
赞!Redis 已可以供君驱使了!💐