Skip to content

PostgreSQL 作為 Redis 替代方案的實踐與原理

前言

在最近的專案開發中,我一直在思考一個問題:對於中等規模的應用,我們真的需要 Redis 嗎?

雖然 Redis 在極致性能和特定的數據結構上無可替代,但引入它也帶來了額外的運維負擔和數據一致性挑戰。往往為了實現一個簡單的「訂單支付後更新快取」或「非同步發送通知」,我們需要在兩個系統(PostgreSQL 和 Redis)之間處理數據同步。

I replaced Redis with PostgreSQL and it’s faster 的啟發,我整理並實現了一套完全基於 PostgreSQL 的 Redis 替代方案。本文將從原理、維護到監控,系統性地分享這套「簡化架構」的落地細節。

核心模塊實現與維護細節

1. 快取 (Cache):UNLOGGED 表與 TTL 策略

原理與實現: 使用 UNLOGGED 表 繞過 WAL 日誌,提升寫入性能。這種表在資料庫崩潰後會清空,非常適合存放「丟了也無妨」的快取數據。我們通過 expires_at 欄位實現 TTL。

維護建議:

-- 分批清理過期數據
DELETE FROM cache WHERE ctid IN (
    SELECT ctid FROM cache WHERE expires_at < NOW() LIMIT 10000
);
VACUUM cache;

監控指標: 關注 pg_stat_user_tables 中的 n_dead_tup(死行數)和表大小。

2. 发布/訂閱 (Pub/Sub):LISTEN / NOTIFY

原理與實現: 利用 PG 內置通訊機制。通知是在事務提交時發出的,這確保了「數據已落庫」與「通知已發出」的原子性。

實踐細節:

3. 任務對列 (Queue):FOR UPDATE SKIP LOCKED

原理與實現: 通過 FOR UPDATE SKIP LOCKED 實現多 Worker 併發領取任務,有效避免領取衝突和死鎖。

進階維護:

4. 會話管理 (Sessions):JSONB 與事務邊界

原理與實現: 將 Session 數據作為 JSONB 存儲。相比 Redis,PG Session 的殺手鐧是事務一致性:你可以在同一個事務內完成「訂單創建」和「更新 Session 中的最後活躍時間」。

優化策略:

5. 限流 (Rate Limit):原子 UPSERT

原理與實現: 利用 INSERT ... ON CONFLICT DO UPDATE 在單條 SQL 中完成「判斷-計數-更新」的閉環。

INSERT INTO rate_limits (key, count, window_start)
VALUES ($1, 1, NOW())
ON CONFLICT (key) DO UPDATE 
SET count = CASE 
    WHEN rate_limits.window_start < NOW() - INTERVAL '1 minute' THEN 1 
    ELSE rate_limits.count + 1 
END,
window_start = CASE 
    WHEN rate_limits.window_start < NOW() - INTERVAL '1 minute' THEN NOW() 
    ELSE rate_limits.window_start 
END;

監控告警: 對於極端熱點的 Key(如某些接口的全局限流),需要監控 pg_locks 中的行鎖競爭(Lock Contention)。


方案總結:何時「升級」?

這套方案並非銀彈。當你的業務增長到以下階段時,建議考慮專業組件:

  1. 消息可靠性要求極高:如果不能接受任何因斷線導致的 NOTIFY 遺失,建議升級到 PGMQKafka
  2. 極高併發:當吞吐量超過 10k+ ops/s,或者對延遲要求在亞毫秒級(< 1ms)時,請回歸 Redis
  3. 複雜數據結構:如果你需要 Redis 的有序集合(ZSET)、流(Streams)或位圖(Bitmaps)功能。

結語

速度不是唯一的衡量標准,系統的可推理性(System Reasoning)才是。

通過將狀態聚合在 PostgreSQL 的事務邊界內,我們極大地減少了分布式系統中「中間狀態」引發的 Bug。這是我所推崇的「極簡開發」。

具體的代碼實現與壓測數據,請參考我的開源項目:spike014/just-use-PostgreSQL


Share this post on:

Comments


Next Post
如何構建智能體應用?