iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
JavaScript

不要再說你不會後端!30 天 Node.js 降維打擊指南 系列 第 27 篇

Day 27:突破效能瓶頸(Redis 快取與微服務架構入門)

  • 分享至 

  • xImage
  •  

恭喜你挺進最後的階段!當你的應用程式使用者從 100 人成長到 10 萬人時,就算你的 Node.js 寫得再完美、SQL 關聯設計得再好,傳統的關聯式資料庫(如 PostgreSQL、MySQL)終究會遇到硬碟讀寫速度的物理極限,成為系統最大的效能瓶頸。

為了解決這個問題,業界最通用的解法就是導入 快取 (Cache),而這個領域無可爭議的王者就是 Redis。


1. 什麼是 Redis?為什麼它這麼快?

Redis 是一個開源的 記憶體內 (In-Memory) 資料結構儲存系統,通常被當作資料庫、快取和訊息代理 (Message Broker) 來使用。

  • 傳統資料庫 (如 SQL, MongoDB):資料存在「硬碟」裡。就算有 SSD,讀寫速度依然有極限。好處是斷電資料不會不見。
  • Redis:資料全部存在「記憶體 (RAM)」裡。速度比硬碟快上成千上萬倍!缺點是記憶體很貴(容量小),且伺服器重啟時,如果沒有做持久化設定,資料就會消失。

因此,我們通常不會把使用者的訂單或密碼單獨存在 Redis,而是把它當作傳統資料庫前面的「擋箭牌 (快取)」。


2. 快取的運作邏輯 (Cache Hit / Cache Miss)

當前端發送 API 請求來查詢「今日熱門商品」時:

  1. Node.js 先去問 Redis:「你有今日熱門商品的資料嗎?」
  2. Cache Hit (快取命中):Redis 說「有!我五分鐘前剛存過,直接給你。」-> 瞬間回傳給前端,傳統資料庫完全沒被動到。
  3. Cache Miss (快取未命中):Redis 說「沒有耶,或是剛過期了。」-> Node.js 轉而去辛苦地查詢傳統資料庫 -> 查到後回傳給前端,並順手把這份資料存一份到 Redis,設定 5 分鐘後自動過期。

3. 實戰演練:在 Express 中加入 Redis 快取

步驟 1:安裝 Redis 客戶端

npm install redis

(備註:實務上你需要有一台 Redis 伺服器。你可以註冊免費的 Redis Cloud,或是明天我們會教你用 Docker 在電腦上一鍵啟動!今天先看懂程式碼邏輯。)

步驟 2:實作快取 API
我們來寫一支查詢使用者資料的 API,體驗加入 Redis 前後的差異:

import express from 'express';
import { createClient } from 'redis';

const app = express();

// 1. 建立並連線到 Redis (請填入你的 Redis 網址)
const redisClient = createClient({
    url: 'redis://default:你的密碼@你的Redis伺服器位址:6379'
});

redisClient.on('error', (err) => console.log('Redis 連線錯誤', err));
await redisClient.connect();
console.log(' 成功連線至 Redis!');

// 模擬一個非常耗時的資料庫查詢 (例如要花 3 秒鐘)
const slowDatabaseQuery = async (userId) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ id: userId, name: 'Bibo', title: '全端工程師' });
        }, 3000);
    });
};

// 2. 撰寫帶有快取機制的 API
app.get('/api/users/:id', async (req, res) => {
    const { id } = req.params;
    const cacheKey = `user_profile_${id}`; // 定義專屬的 Redis Key

    try {
        // 步驟 A:先去 Redis 找找看有沒有快取 (Cache Hit)
        const cachedData = await redisClient.get(cacheKey);

        if (cachedData) {
            console.log(' 從 Redis 快取神速讀取!');
            // 注意:Redis 只能存字串,所以存入時要轉 JSON 字串,拿出來時要轉回 Object
            return res.json(JSON.parse(cachedData)); 
        }

        // 步驟 B:Redis 沒有 (Cache Miss),只好去查真的資料庫
        console.log('🐢 找不到快取,正在進行緩慢的資料庫查詢...');
        const userData = await slowDatabaseQuery(id);

        // 步驟 C:查到資料後,把它存進 Redis,並設定過期時間 (EX: 3600 秒 = 1 小時)
        await redisClient.set(cacheKey, JSON.stringify(userData), {
            EX: 3600 
        });

        res.json(userData);

    } catch (error) {
        res.status(500).json({ error: '伺服器錯誤' });
    }
});

app.listen(3000, () => console.log('伺服器啟動於 PORT 3000'));

測試結果:

  • 第一次呼叫:API 會卡住 3 秒,終端機顯示「 找不到快取...」。
  • 第二次呼叫:API 瞬間(幾毫秒內)回傳,終端機顯示「 從 Redis 快取神速讀取!」。這就是快取的魔力!

4. 微服務 (Microservices) 與 Socket.io 的擴容痛點

當你的流量大到需要開 3 台 Node.js 伺服器來分擔時,這稱為「水平擴展 (Horizontal Scaling)」。
但還記得昨天的 Socket.io 嗎?如果 A 使用者連到 1 號伺服器,B 使用者連到 2 號伺服器,他們即使加入同一個房間,也收不到對方的訊息!因為 Socket.io 的記憶體是互相獨立的。

這時候 Redis 又要出來拯救世界了!透過設定 Redis Adapter,我們可以讓這 3 台 Node.js 伺服器透過 Redis 來互相溝通 (Pub/Sub 機制)。1 號伺服器收到訊息後,會丟給 Redis,Redis 再廣播給 2 號和 3 號伺服器,完美解決多台伺服器之間的 WebSocket 即時通訊問題!這就是微服務架構中不可或缺的底層拼圖。


上一篇
Day 26:Socket.io 的進階玩法(Rooms 房間與 Namespace)
下一篇
Day 28:讓程式隨處可跑 — Docker 容器化部署實戰
系列文
不要再說你不會後端!30 天 Node.js 降維打擊指南 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言