Redis 做資料快取的基本使用 (搭配Node.js)
背景
有些需要耗費大量運算的結果
如果能夠在伺服器端做快取
可以對效能提升很有幫助
先看這次的結果
左邊為原始情況,右邊為 Redis 快取後
(極端案例? T__T
安裝
已經有安裝
或是只是想看介紹的可以跳過這部分喔~
Redis Server
建議採用官網這種下載壓縮檔再安裝的方式
不然直接用 apt-get 常常預設的不是最新版本
https://redis.io/download#installation
$ wget http://download.redis.io/releases/redis-6.0.5.tar.gz
$ tar xzf redis-6.0.5.tar.gz
$ cd redis-6.0.5
$ make
在背景啟動 redis-server
$ src/redis-server --daemonize yes
並且測試一下功能運作正常
$ redis> set foo bar
$ OK
$ redis> get foo
$ "bar"
Redis Client
我自己是使用 Node.js
所以先安裝 node-redis 套件
https://github.com/NodeRedis/node-redis
npm install redis
Github 上的範例也很簡潔清楚
<!-- 引入模組 -->
const redis = require("redis");
const client = redis.createClient();
<!-- 連接 redis server -->
client.on("error", function(error) {
console.error(error);
});
<!-- 設置與取得 key / value -->
client.set("key", "value", redis.print);
client.get("key", redis.print);
實際使用
使用快取的邏輯
- 查詢資料時,查看是否存在於快取之中
- 是,則從快取中取得資料
- 直接回傳給前端
- 否,則從資料庫中查詢
- 回傳給前端
- 並且寫入快取之中
- 是,則從快取中取得資料
- 更新資料時,查看是否存在於快取之中
// 取得圖表資料
router.get('/someData', (req, res) => {
// 取得請求參數並傳換成 key
const parm1 = req.query.parm1
const parm2 = req.query.parm2
const parm3 = req.query.parm3
const key = (`someData:${parm1}/${parm2}/${parm3}`).trim()
const expireDay = 60 * 60 * 24;
// 檢查資料是否能夠從快取中取得
redisClient.get(key, (err, rawdata) => {
// 可以做錯誤處理
if (err) {
console.log(err)
}
// 如果快取中沒有這筆資料,會回傳 null
if (!!rawdata) {
// redis 無法儲存 Javascript 的 Object
// 這邊是直接存成 string
// 所以需要轉換
const data = JSON.parse(rawdata);
return res.json(data);
}
// 這邊是呼叫你撈資料庫的 function
yourSQL.queryData(parm1, parm2, parm3)
.then(results => {
// 寫入快取,並且設置有效期限為一天
redisClient.set(key, JSON.stringify(results), 'EX', expireDay, err => {
if (err) {
console.log(err)
}
});
// 回傳資料
res.json(results);
})
.catch(err => {
// 資料庫的查詢的錯誤處理
console.log("not connected due to error: " + err);
res.status(500).send('Can not connect to DB');
});
});
});
值得注意的地方
key 的取名
因為 key 的長度會影響到 Redis 查詢的效能
所以設計好的 key 是需要的
關乎取名的東西要複雜都可以很複雜
但我覺得最大原則就是內部溝通好、好管理就好
可以參考 SatckOverflow 上有人講到常見的命名方式
redis-key-naming-conventions
We use a colon (:) as namespace separator and a hash (#) for id-parts of keys, e.g.:
logistics:building#23
value 的資料型態
Redis 目前有五種資料型態:
- string
- hash
- list
- set
- zset (sorted set)
我這邊原本是想直接把資料庫撈出來的結果 (Object型態) 存入
但是因為 Redis 並沒有這種資料型態
所以就直接粗暴的轉成字串
存入與取出時透過JSON.stringify
, JSON.parse
來處理
應該有更好的做法啦
有效期限的設置
- 如果不是需要常駐的資料
盡量都要設置有效期限
(需要常駐的資料也應該要記得存於實體資料庫中) - 設置合理的有效期限
- 太長,占用記憶體空間
- 太短,容易重覆讀寫浪費效能
- 在範例的 Node.js 中
語法為在 set 方法裡傳入參數'EX', expire
redisClient.set(key, value, 'EX', expire, err => { ... });
超過記憶體上限後的處理、淘汰機制
- noeviction:
即使記憶體上達到上限,也不置換 key-value
也就是記憶體滿了之後,只能讀取資料,不能寫入
若再新增資料會 return error這是預設值,我認為不適用於多數場合,所應該要去設定成下列其他的
- allkeys-lru:
優先刪除掉最近最少使用的key,用以保存新數據LRU = least recently used
適用多數場合 - volatile-lru:
只從有設置有效期限 (expire) 的資料中
選擇最近最少使用的 key-value 進行刪除與 allkeys-lru 的差別在於
一個從全部的 key 中來選擇刪除
一個是只會從有有效期限的 key 來選擇刪除 - allkeys-random:
從全部的 key 中
隨機選擇一些進行刪除 - volatile-random:
只從有設置有效期限 (expire) key 中
隨機選擇一些 key 進行刪除 - volatile-ttl:
只從有設置有效期限 (expire) key 中
選出剩餘存活時間 (TTL) 最短的 key 進行刪除TTL = Time To Live