緩存是優化 Web 應用程序性能的關鍵部分。 緩存涉及將數據臨時存儲在緩存中以減少對數據庫的請求數量並縮短響應時間。 Redis 是一種內存數據結構存儲,廣泛用作緩存解決方案。 本指南探討瞭如何使用 Redis 作為具有 Postgres 數據庫的 NodeJS 應用程序的緩存。 Redis 和 Postgres 數據庫將通過 Vultr 管理的數據庫進行配置。
以前的要求
-
提供服務器Ubuntu20.04
-
創建一個非root用戶
-
安裝 nodejs(在選項 2 中使用 PPA 版本)
-
創建一個由 Vultr 管理的 Postgres 數據庫
-
創建一個由 Vultr 管理的 Redis 數據庫
-
安裝 tmux
環境設置
作為參考,最終項目的文件夾結構如下:
postgres-redis
|__configs
| config.db.postgres.js
| config.db.postgresPooling.js
| config.db.redis.js
| config.secrets.js
|__controllers
| controller.blog.js
|__middlewares
| middleware.timer.js
|__routes
| route.blog.js
| .env
| server.js
| postgres-certificate.crt
| redis-certificate.crt
| package.json
| package-lock.json
創建項目文件夾
$ mkdir postgres-redis
將目錄更改為項目文件夾
$ cd postgres-redis
創造 .env
檔案
$ touch .env
導航到概覽選項卡上的 PostgreSQL 數據庫集群連接詳細信息。 將連接詳細信息添加到 .env
程序。 打開 .env
使用 nano 文件並替換 Postgres 連接詳細信息。
$ nano .env
替換為 Postgres 概覽選項卡中的憑據
PG_USERNAME= paste-username-here
PG_PASSWORD= paste-password-here
PG_DATABASE= paste-databse-here
PG_PORT= paste-port-here
PG_HOST= paste-host-here
進入Redis數據庫集群連接詳情,將Redis連接詳情添加到 .env.
REDIS_USERNAME= paste-username-here
REDIS_PASSWORD= paste-password-here
REDIS_HOST= paste-host-here
REDIS_PORT= paste-port-here
安裝依賴項
該項目將需要以下依賴項:
-
cors
:用於處理跨源資源共享 (CORS) 的中間件允許從不同域訪問資源。 -
express
- 用於構建和管理路由、處理請求、中間件和更多功能的 Web 框架。 -
ioredis
: 訪問 Redis 的包。 -
nodemon
: 更改文件時自動重啟節點應用程序的開發工具。 -
pg
: 用於訪問 PostgreSQL 和執行 SQL 查詢的包。 -
dotenv
: 在 .env 中管理環境變量的包。
可以使用以下命令安裝這些依賴項:
$ npm install cors express ioredis nodemon pg dotenv
上面的命令創建了一個 package.json
文件和一個 package-lock.json
. 在裡面 package.json,
使用 nano 編輯文件,添加 server.js 作為主要入口點並將 nodemon 添加到啟動腳本命令,如下所示。
{
"main": "server.js",
{
"start":"nodemon server.js"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"ioredis": "^5.3.0",
"nodemon": "^2.0.20",
"pg": "^8.9.0"
}
}
配置服務器
正如在 package.json
應用程序的入口點是 server.js
. 創建 server.js
使用納米
$ nano server.js
該文件的內容是:
const express = require('express');
const cors = require('cors');
const responseTime = require('./middlewares/middleware.timer.js')
// import routes
const blogRoutes = require('./routes/route.blog.js');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use(responseTime)
// use routes
app.use("/api/blogs", blogRoutes);
// start server
const PORT = 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
});
該代碼執行以下操作:
-
快遞包和cors都是進口的。
-
中定義的 responseTime 中間件
./middlewares/middleware.timer.js
將計算完成申請所需的時間。 -
blogRoutes 從
./routes/route.blog.js
程序。 -
接下來,我們實例化一個 express 應用程序並傳遞 express.json 和 express.urlencoded 中間件以分別使用 URL 編碼和 JSON 編碼數據解析傳入請求。
-
cors 中間件用於處理 CORS 策略,responseTime 中間件用於記錄請求響應時間。
-
blogRoutes 安裝在
/api/blogs/
endpoint 這意味著發送到的任何請求/api/blogs
將由路由處理程序處理./routes/route.blog.js
.
響應時間中間件
在中創建 responseTime 中間件 ./middlewares/middleware.timer.js
.
$ mkdir middlewares && cd middlewares && nano middleware.timer.js
文件中的代碼如下
const responseTime = ((req, res, next)=>{
const startTime = process.hrtime();
res.on('finish', ()=>{
const totalTime = process.hrtime(startTime);
const totalTimeInMs = totalTime[0] * 1000 + totalTime[1] / 1e6;
console.log(`The request to /api/blogs${req.url} took `,totalTimeInMs , " ms");
});
next();
})
module.exports = responseTime;
該代碼測量處理對“/api/blogs”端點發出的請求所花費的時間。 中間件為每個請求添加一個計時器,並在中間件被調用時立即啟動計時器,完成時間記錄在響應對象的'finish'事件中。 已用時間的計算方法是從結束時間減去開始時間,以毫秒為單位表示。 花費的時間記錄在控制台中。
航線
具體的 GET 和 POST 路由定義在 ./routes/route.blog.js
要創建文件,請導航回項目文件夾。
$ cd ..
使用以下命令創建路由文件。
$ mkdir routes && cd routes && nano route.blog.js
該文件的代碼如下:
const express = require('express');
const router = express.Router();
const { getBlogs ,createBlog} = require('../controllers/controller.blog.js');
router.get('/:id', getBlogs);
router.post("https://www.vultr.com/", createBlog);
module.exports = router;
-
該代碼配置了一個 Express 路由器並從中導入了城市功能
../controllers/controller.blog.js
. 這兩個函數是 getBlogs 和 createBlog。 -
getBlogs 函數掛載到 GET 請求中
/api/blogs/:id
終點:id
將是正在檢索的特定 id 的動態 url 參數。 例如 GET 請求/api/blogs/1
getBlogs 函數將在 req.params.id 等於 1 時執行。 -
createBlog 函數掛載在 POST 請求中
/api/blogs/
端點此函數將處理對端點的所有請求。
配置機密
我們將所有環境變量導入到一個文件中,以便在需要更改時輕鬆管理。
返回項目文件夾。
$ cd ..
使用以下命令創建機密文件。
$ mkdir configs && cd configs && nano config.secrets.js
此文件中的代碼導入了我們之前在 .env
.
const dotenv = require('dotenv');
const path = require('path');
dotenv.config({ path: path.resolve(__dirname, '../.env') });
exports.secrets = {
PG_USERNAME: process.env.PG_USERNAME,
PG_PASSWORD: process.env.PG_PASSWORD,
PG_DATABASE: process.env.PG_DATABASE,
PG_PORT: process.env.PG_PORT,
PG_HOST: process.env.PG_HOST,
REDIS_USERNAME: process.env.REDIS_USERNAME,
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
REDIS_PORT: process.env.REDIS_PORT,
REDIS_HOST: process.env.REDIS_HOST,
}
dotenv 包用於讀取 .env
程序。
保存簽名證書
我們需要保存簽名的證書以便為 Redis 和 Postgres 啟用 SSL。 在 Postgres 概述選項卡上,下載簽名證書
https://cdn.hashnode.com/res/hashnode/image/upload/v1675264831640/671ac5ef-762d-4222-ade8-3ac928259638.png?auto=compress,format&format=webp
在項目文件夾中,創建 postgres-certificate.crt 並將下載的證書內容粘貼到此文件中。 首先導航回項目文件夾
$ cd ..
然後創建 postgres-certificate.crt
文件並使用以下命令粘貼證書詳細信息
$ nano postgres-certificate.crt
對 Redis 簽名證書執行相同操作並將其保存到 redis-certificate.crt
.
$ nano redis-certificate.crt
PostgreSQL 連接
Postgres 連接是使用 pg 包建立的。 在項目文件夾中,使用以下命令創建 Postgres 連接文件:
$ cd configs && nano config.db.postgres.js
文件中的代碼如下:
const { Client } = require('pg');
const { secrets } = require('./config.secrets.js');
const fs = require('fs');
let client;
exports.connectPG = (async () => {
if (client) return client;
try{
console.log('Connecting to Postgres...');
const client = new Client({
user: secrets.PG_USERNAME,
host: secrets.PG_HOST,
database: secrets.PG_DATABASE,
password: secrets.PG_PASSWORD,
port: secrets.PG_PORT,
ssl: {
rejectUnauthorized: false,
cert: fs.readFileSync('postgres-certificate.crt').toString(),
},
});
await client.connect();
console.log('Connected to Postgres!');
return client;
}catch(err){
console.error(err);
}
})();
我們從 pg 庫中導入 Client 類,secrets 文件中的環境變量,以及 fs. fs模塊用於在NodeJS中讀取文件內容。 我們初始化客戶端變量,並在調用 connectPG 函數時檢查客戶端對像是否已經存在。 這允許您重用 Postgres 連接,而不是在每個請求上都建立一個新連接。 當客戶端對像不存在時,將使用環境變量中的連接詳細信息創建客戶端對象。 fs 庫讀取 postgres-certificate.crt 的內容並將其添加到 SSL 屬性中。 最後,我們連接到 Postgres 並返回連接的客戶端對象。 該函數是自調用的。
Redis連接
Redis 連接遵循與 Postgres 連接類似的模式。 使用以下命令從項目文件夾創建 redis 文件
$ cd configs && nano config.db.redis.js
文件中的代碼如下
const Redis = require('ioredis');
const { secrets } = require('./config.secrets.js');
const fs = require('fs');
let client;
exports.redisDB = (async () => {
if (client) return client;
try {
console.log('Connecting to Redis DB...');
const client = new Redis({
username: secrets.REDIS_USERNAME,
host: secrets.REDIS_HOST,
port: secrets.REDIS_PORT,
password: secrets.REDIS_PASSWORD,
tls: {
cert: fs.readFileSync('redis-certificate.crt', 'ascii')
}
});
await client.on('connect', () => {
console.log('Connected to Redis!');
});
await client.on('error', (err) => {
console.error("Redis error ",err);
});
return client;
} catch (err) {
console.error(err);
}
})()
代碼使用 ioredis
數據包和連接詳細信息 .env
通過機密文件歸檔。 我們通過讀取 redis-certificate.crt 的內容來指定 tls 對像上的 cert 參數 fs
模塊。
控制器
控制器將所有東西聯繫在一起。 控制器將具有兩個功能。 一個函數將處理 GET 請求,另一個函數將在與兩個託管數據庫交互時處理 POST 請求。
在項目文件夾中,創建控制器文件:
$ mkdir controllers && cd controllers && nano controller.blog.js
該文件的代碼如下:
const { redisDB } = require('../configs/config.db.redis.js');
const { connectPG } = require('../configs/config.db.postgres.js');
const getBlogs = async (req, res) => {
const { id } = req.params;
try {
const redisClient = await redisDB;
const jsonString = await redisClient.get(id);
if (jsonString !== null) {
console.log('Data from Redis');
const dataFromRedis = JSON.parse(jsonString);
return res.status(200).json(dataFromRedis);
}
const pool = await connectPG;
const { rows } = await pool.query(`SELECT * FROM blogs`);
console.log("Data from postgres");
await redisClient.set(id, JSON.stringify(rows));
res.status(200).json(rows)
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Error while retrieving blog' });
}
} ;
const createBlog = async (req, res) => {
try {
const client = await connectPG;
await client.query(`
CREATE TABLE IF NOT EXISTS blogs (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
)`);
const query = `INSERT INTO blogs (title, content)
VALUES ($1, $2) RETURNING *;`;
const values = [req.body.title, req.body.content];
const blogId = await client.query(query, values);
res.status(200).json(`Blog ${blogId.rows[0].id} saved!`);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Unable to retrieve blogs' });
}
};
module.exports = { getBlogs, createBlog };
該代碼執行以下操作:
-
我們導入 Redis 和 Postgres 連接。
-
getBlogs 函數處理對 /api/blogs/:id 路由發出的 GET 請求。 該函數解構請求參數對象的 id 值。 接下來,建立 Redis 客戶端連接,我們檢查 id 的博客是否在 Redis 數據庫中。 如果數據存在,我們將其返回給客戶端。 如果數據不在 Redis 中,我們連接到 Postgres 並從 id 等於請求參數的 blogs 表中檢索數據。 最後,我們將數據存儲在 Redis 中,以避免在後續請求時必須從 Postgres 檢索數據。
-
createBlog 連接函數處理髮出的 POST 請求。 如果 Postgres 中不存在,此函數將創建一個 blog 表。 該表有五行; ID、標題、內容、創建時間和更新時間。 我們從請求正文中檢索博客標題和內容,並將它們插入到博客表中。 當此操作成功時,我們向客戶端返回一條成功消息。
-
最後,導出驅動程序。
創建博客和測試響應時間。
現在我們擁有了所有必要的代碼,我們可以測試 API。 啟動 tmux 會話並創建兩個窗口。 在第一個窗口中,使用以下命令在項目文件夾中運行 API:
$ npm start
您應該會看到以下結果
https://cdn.hashnode.com/res/hashnode/image/upload/v1675276143139/84210d1b-aee2-4f76-b059-cde85615e9b6.png?auto=compress,format&format=webp
在第二個 tmux 窗口中,使用創建一個博客 curl
使用命令:
$ curl -X POST -H "Content-Type: application/json" -d '{"title":"Vultr blog one","content":" This is blog one"}' https://localhost:3000/api/blogs/
默認情況下,該博客的 id 為 1; 因此,在第二個 tmux 窗口中,我們可以使用此 id 使用 curl 從數據庫中獲取數據。
$ curl https://localhost:3000/api/blogs/1
在窗口二中重複此 GET 請求表明檢索博客 1 所需的時間已減少 496 毫秒。 因此,緩存顯著減少了響應時間。
更多資源:
文章標題 名稱(可選) 電子郵件(可選) 描述
發送建議