當系統只有一台 Backend 時,很多資料暫時存在這台 Server 自己的記憶體裡,通常不會有太大問題。
例如:
Backend 1
├── Login Session
├── Cache
├── Temporary Data
└── Application Memory
因為所有 Request 都一定會進到同一台 Backend。
但是加入 Load Balancer 之後,架構變成:
Load Balancer
│
┌──────────┼──────────┐
▼ ▼ ▼
Backend 1 Backend 2 Backend 3
此時每一個 Request 都可能被分配到不同的 Backend。
這時就會出現一個重要問題:
Backend 1 的 Memory,Backend 2 是看不到的。
例如:
Backend 1 Memory
user_123 = logged_in
Backend 2 Memory
沒有 user_123
這表示只要系統把需要共享的資料存在單一 Backend 的記憶體中,就有可能產生不一致。
因此系統會需要一個所有 Backend 都可以共同存取的地方。
Redis 就經常扮演這個角色。
Redis 可以簡單理解成一個速度非常快的 Key-Value Database。
例如可以存:
"user:123:name" → "Tom"
或者:
"session:abc123" → user_id=123
Redis 最重要的特點之一,就是資料主要放在 Memory 中,因此讀寫速度非常快。
架構可以變成:
Load Balancer
│
┌──────────┼──────────┐
▼ ▼ ▼
Backend 1 Backend 2 Backend 3
│ │ │
└──────────┼──────────┘
│
▼
Redis
這樣三台 Backend 都可以讀取相同的資料。
假設使用者登入網站。
第一次 Request:
User
│
▼
Load Balancer
│
▼
Backend 1
Backend 1 驗證帳號密碼成功後,如果把登入狀態直接存在 Backend 1 的 Memory:
Backend 1
session_abc = {
user_id: 123,
logged_in: true
}
接著 Browser 帶著:
session_id = abc
發出下一個 Request。
但是這一次 Load Balancer 把 Request 分配給:
Backend 2
Backend 2 查自己的 Memory:
session_abc ?
結果發現:
不存在
於是 Backend 2 可能認為:
這個使用者沒有登入
這就產生了問題。
如果改成把 Session 存在 Redis:
Redis
session:abc
→ user_id = 123
那麼:
Backend 1 ─┐
Backend 2 ─┼──→ Redis
Backend 3 ─┘
不管 Request 被送到哪一台 Backend,都可以取得同一份 Session。
因此流程變成:
Browser
│
│ session_id=abc
▼
Load Balancer
│
▼
Backend 2
│
│ GET session:abc
▼
Redis
│
▼
user_id=123
Backend 2 就知道:
這個使用者已登入
這裡需要補充一點。
現在很多 API 使用 JWT Token 驗證,例如:
Authorization: Bearer eyJ...
JWT 本身就可以攜帶:
user_id
role
expiration
Backend 收到 Token 後,可以直接驗證簽章。
因此:
Backend 1
Backend 2
Backend 3
只要都使用相同的驗證 Key,就可以驗證同一個 JWT。
這種情況下:
登入驗證不一定需要把 Session 放進 Redis。
但 Redis 還是可能被用來處理:
Token Blacklist
Refresh Token
Logout State
Rate Limit
Temporary Authorization Data
所以 Redis 的用途不只 Session。
Redis 另一個非常常見的用途就是 Cache。
假設 API:
GET /api/products
每次都需要查 Database:
Backend
│
▼
Database
│
▼
SELECT * FROM products
如果這個 API 每秒有大量 Request:
Request 1 → DB
Request 2 → DB
Request 3 → DB
Request 4 → DB
Request 5 → DB
Database 就會承受大量查詢。
但商品資料可能五分鐘內根本不會改變。
這時就可以把查詢結果存進 Redis。
第一次 Request:
Backend
│
▼
Redis
│
│ 沒有資料
▼
Database
│
▼
取得 Product Data
│
▼
Redis
例如:
products:list
→ JSON Data
並設定:
TTL = 300 seconds
接下來其他 Request:
Backend
│
▼
Redis
│
▼
Product Data
就不用再次查 Database。
因此從:
Request
│
▼
Database
變成:
Request
│
▼
Redis
只有 Cache Miss 的時候才查 Database。
假設沒有 Redis,而是每台 Backend 自己做 Memory Cache:
Backend 1
cache:
product_1 = $100
Backend 2
cache:
product_1 = $100
Backend 3
cache:
product_1 = $100
這看起來好像沒有問題。
但是今天商品價格變成:
$120
Backend 1 更新了自己的 Cache:
Backend 1
product_1 = $120
Backend 2、Backend 3 卻可能還是:
product_1 = $100
於是:
Request 1 → Backend 1 → $120
Request 2 → Backend 2 → $100
Request 3 → Backend 3 → $100
使用者可能會看到不同結果。
如果三台 Backend 都使用同一個 Redis:
Redis
product_1 = $120
▲ ▲ ▲
│ │ │
Backend 1 Backend 2 Backend 3
Cache 就能集中管理。
Redis 也常被拿來做 API Rate Limit。
例如規定:
同一個 IP
1 分鐘最多 100 次 Request
如果只有一台 Backend,可以直接在 Backend Memory 裡計數:
192.168.1.10 = 30 requests
但如果有三台 Backend:
Backend 1 → 30
Backend 2 → 20
Backend 3 → 25
每台都只知道自己收到多少 Request。
實際上使用者已經發了:
30 + 20 + 25 = 75
但沒有任何一台 Backend 知道總數。
如果使用 Redis:
rate_limit:192.168.1.10
→ 75
每台 Backend 都更新同一個 Counter:
Backend 1 ─┐
Backend 2 ─┼── INCR → Redis
Backend 3 ─┘
這樣才能正確計算整體 Request 數量。
多台 Backend 還會碰到另一個問題:
同一個工作可能被執行很多次。
例如系統有一個排程:
每天凌晨 01:00
同步第三方訂單
現在有三台 Backend:
Backend 1
Backend 2
Backend 3
如果每一台都啟動 Scheduler:
01:00
Backend 1 → 執行同步
Backend 2 → 執行同步
Backend 3 → 執行同步
同一個工作就會被執行三次。
可能造成:
重複建立資料
重複寄 Email
重複扣庫存
重複呼叫第三方 API
因此需要一種機制:
三台 Backend 裡
只能有一台取得執行權
這就是 Distributed Lock。
Redis 可以建立一個 Lock:
job:sync_order:lock
例如 Backend 1 先取得:
SET job:sync_order:lock backend1 NX EX 300
表示:
如果 Key 不存在
才建立 Lock
300 秒後自動過期
此時:
Backend 1 → Lock 成功 → 執行工作
Backend 2 → Lock 失敗 → 不執行
Backend 3 → Lock 失敗 → 不執行
架構就是:
Redis Lock
│
┌─────────┼─────────┐
│ │ │
▼ ▼ ▼
Backend 1 Backend 2 Backend 3
✓ X X
這就是 Redis 在多台 Backend 架構中非常常見的一種用途。
Redis 常見用途包括:
Session
Cache
Rate Limit
Distributed Lock
OTP
Verification Code
Temporary Token
Shopping Cart
Queue
Realtime Counter
例如驗證碼:
otp:0912345678
→ 738291
TTL = 300 seconds
五分鐘後 Redis 自動刪除。
這種「有時效性的暫存資料」非常適合 Redis。
這裡也需要避免一個常見誤解:
Redis 通常不是拿來完全取代 MySQL。
MySQL 負責的是主要的永久資料,例如:
User
Order
Product
Payment
Booking
Redis 通常負責的是:
Cache
Session
Temporary Data
Shared State
Lock
Counter
架構可以理解成:
Backend
│
┌────────┴────────┐
│ │
▼ ▼
Redis MySQL
│ │
│ │
快速 / 暫存資料 正式持久資料
例如商品資料真正的來源還是 MySQL:
MySQL
product_id = 1
price = 120
Redis 只是保存一份 Cache:
Redis
product:1
price = 120
TTL = 300
如果 Redis 的 Cache 消失,通常還可以重新向 MySQL 查詢並建立 Cache。
加入 Redis 後,前面的架構就會從:
Load Balancer
│
┌──────────┼──────────┐
▼ ▼ ▼
Backend 1 Backend 2 Backend 3
│
▼
Database
變成:
Load Balancer
│
┌──────────┼──────────┐
▼ ▼ ▼
Backend 1 Backend 2 Backend 3
│ │ │
└─────┬────┴────┬─────┘
│ │
▼ ▼
Redis Database
兩者負責不同工作:
Redis
├── Cache
├── Session
├── Lock
├── Counter
└── Temporary Data
Database
├── User
├── Product
├── Order
├── Payment
└── Business Data
所以 Redis 真正解決的是:
多台 Backend 彼此獨立,但某些狀態卻需要共享的問題。
換句話說:
Backend 不應該依賴
「只有自己知道的 Memory 狀態」
而應該盡可能設計成:
Backend 1
Backend 2
Backend 3
任何一台收到 Request
都可以正確處理
這種 Backend 也常被稱為:
Stateless Backend。
而 Redis、Database 等外部服務,則負責保存真正需要共享的 State。
這也是系統要從單台 Backend 走向水平擴充時,一個非常重要的架構觀念。