【筆記】Node 應用使用 Redis 進行快取管理

前言

當初寫台鐵時刻表的後端時,資料快取這塊我是直接將資料儲存在變數裡,並自行做 TTL(Time To Live,存活時間)的驗證和持久化(Persistence)的機制。但用了一年後,我發現直接使用 redis 還是會比自造輪子來的方便些。本文將簡單記錄我的一些快取心得和 redis 的設定方式。

為何一開始要自造輪子

一開始我只是覺得伺服器記憶體已經很吃緊了,而我只是想要加入簡單的快取功能,心想導入一套完整的快取解決方案,怕是殺雞焉用牛刀;加上我也滿想自己去理解和嘗試快取機制,若只是將資料儲存在變數裡做快取,做個簡單的 TTL 和持久化應該不會花太久時間,所以就決定自造輪子。

自造輪子的困難

快取資料的過期驗證並不會太困難,資料持久化倒是麻煩了一點,但最困難的我覺得還是「驅逐策略」:到底要釋放哪些快取資料?是否得在儲存資料時多加上權重值?JS 有什麼辦法可以即時釋放記憶體?要自己去控管這些是真的挺麻煩的。

這一年來,我的快取策略雖然有些問題但還算勘用;但因為我這樣有缺陷的機制,有時候付費 API 就會超過用量上限,導致我得多付一倍的錢。而且一直看著應用一直因為記憶體不足而崩潰重啟也不是辦法,於是我決定向專業的記憶體快取解決方案 redis 靠攏。

redis 優勢

node.js 的記憶體是使用 V8 引擎管理,並未針對大型資料進行特別優化;redis 將資料儲存在自己的記憶體中,採用針對不同資料類型的壓縮和高效存儲格式。而且 redis 有完整的 TTL、持久化、資料壓縮甚至是分散式資料管理。所以在相同資料量的情況下,redis 是很有可能比 node.js 占用更少的記憶體的。

以下簡單記錄 redis 的安裝與使用方式。

安裝 redis-server

macOS: brew install redis
Ubuntu: sudo apt update && sudo apt install redis-server

啟動 redis-server

redis-server

限制 redis-server 最大記憶體使用以及驅逐策略

這是非常重要的步驟。

可以到 redis 設定檔中,調整 maxmemory 和 maxmemory-policy 這兩個欄位。

sudo vim /etc/redis/redis.conf

# 依你的記憶體大小決定
maxmemory 256mb

# 建議使用 allkeys-lru 策略
maxmemory-policy allkeys-lru

完整趨逐策略如下:

  • noeviction:當記憶體不足時,不再接受寫入請求。這是預設策略
  • allkeys-lru:從所有鍵中驅逐最近最少使用的鍵。建議使用
  • volatile-lru:從設置了 TTL 的鍵中驅逐最近最少使用的鍵。
  • allkeys-random:從所有鍵中隨機驅逐。
  • volatile-random:從設置了 TTL 的鍵中隨機驅逐。
  • volatile-ttl:從設置了 TTL 的鍵中驅逐即將過期的鍵(TTL 最短的優先)。

重啟 redis-server

改完 redis.conf 後務必重新啟動 redis。

sudo systemctl restart redis

確認 redis-server 是否已在運行

sudo systemctl status redis

檢查 redis-server 記憶體使用狀況

redis-cli info memory

查看 redis-server 的快取內容

# 列出所有符合模式的鍵(* 可自行調整)
redis-cli KEYS *

# 列出 100 個符合模式的鍵(推薦使用,* 和 100 可自行調整)
redis-cli SCAN 0 MATCH "*" COUNT 100

# 查看特定鍵
redis-cli GET key_name

在 node.js 應用中使用 redis-client

安裝 node-redis

npm install redis

使用 redis-client

以下使用 promise 示範。

import { createClient } from "redis";

// 建立 redis 物件
const client = createClient();
// 連接 redis
await client.connect();

const getData = async () => {
    const cacheKey = "cacheKey";
    let result;

    // 從 redis 取得快取
    const cachedData = await client.get(cacheKey);

    // 若沒有快取
    if (!cachedData) {
        // 從原先的方法取得資料(API、Database...)
        const response = await getDataFromAPI();

        // 將剛取回來的資料作為結果
        result = response;

        // 並將資料轉成字串後快取
        await client.set(cacheKey, JSON.stringify(response), {
            EX: 60 * 60 * 24, // 設定 EX 過期時間(單位:秒)
        });
    } else {
        // 若有快取,將快取轉回 JSON 後直接作為結果
        result = JSON.parse(cachedData);
    }

    return result;
}

// 在應用程式關閉時關閉與 redis 的連線
process.on("SIGINT", async () => {
    await client.quit();
    process.exit();
});

後記

在自造輪子後,才會知道 redis 是如此簡單好用又強大,前端、後端和快取管理完全分離,非常清爽。



0 0 votes
Article Rating
Subscribe
Notify of
guest
6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
trackback
14 days ago

vardenafil 20mg price

vardenafil 20mg price

trackback
14 days ago

cyprofloksacyna

cyprofloksacyna

trackback

remeron 15 mg oral tablet

remeron 15 mg oral tablet

trackback

antibiotics online prescription

antibiotics online prescription

trackback
8 days ago

vibramycin 100mg

vibramycin 100mg

trackback
2 days ago

furosemide for dogs

furosemide for dogs