iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0

Day 22 的商品價格都沒有改過,快取裡的跟 DB 一定一樣。今天來改價格。

只改 DB、不管快取的話,快取要等 TTL 過期才會變,這段時間查到的都是舊價格。所以 DB 跟快取兩邊都要動,但兩邊不可能同時改好,中間一定有一段時間不一樣。

今天來看怎麼改,快取才不會一直留著舊價格。下面的例子都是 1 號商品,原本 1299,A 是改價格的人,B 是查商品的人,由上往下照時間順序排。

改快取還是刪快取

DB 改好之後,直覺是把新價格也 SET 進快取。但如果 A 跟另一個人 C 同時改價:

A:把 DB 改成 999
C:把 DB 改成 888
C:把快取改成 888
A:把快取改成 999(A 慢了一點)
--------------------
DB:888,快取:999

DB 是 C 最後寫的,快取卻是 A 最後寫的,兩邊不一樣,要等 TTL 過期才會好。

改成刪掉就不會這樣,不管誰先刪,結果都是快取沒有,下一個查的人去 DB 撈到的就是最新的。

那要先刪快取,還是先改 DB?

先刪快取再改 DB

A:刪掉快取
B:查快取,沒有
B:查 DB,拿到 1299(A 還沒改好)
B:把 1299 寫回快取
A:把 DB 改成 999
--------------------
DB:999,快取:1299

B 剛好在 A 刪完快取、DB 還沒改好的中間來查,把舊價格寫回去了。之後查到的都是快取裡的 1299,要等 TTL 過期才會變成 999。

只要有人在這段中間來查就會發生,熱門商品一直有人在查,很容易遇到。

先改 DB 再刪快取

A:把 DB 改成 999
B:查快取,拿到 1299(還沒刪)
A:刪掉快取
B:查快取,沒有
B:查 DB,拿到 999
B:把 999 寫回快取
--------------------
DB:999,快取:999

A 改好 DB、還沒刪快取的中間,B 查到的是舊價格。但 A 一刪掉,下一次就會去 DB 撈到 999,舊價格不會一直留著。

先改 DB 再刪快取,查的時候快取沒有再去 DB 撈,這種做法叫 Cache Aside,是最常見的寫法。

寫成程式就是兩行:先改 DB,再刪快取。ProductController 加一個改價格的 /product/price:

/** 先改 DB 再刪快取(Cache Aside) */
@PostMapping("/price/{id}")
public Map<String, Object> updatePrice(@PathVariable Long id, @RequestParam BigDecimal price)
        throws InterruptedException {
    updateDb(id, price);
    redis.delete("product:" + id); // DB 改好才刪,下一個來查的會去 DB 撈新價格
    return Map.of("price", price);
}

updateDb 是改假 DB 裡的價格。之前的假 DB 價格寫死 1299,所以另外用一個 Map 存改過的價格,查的時候 Map 裡有就用 Map 的,沒有才是 1299。

延遲雙刪

先改 DB 再刪快取也不是完全不會錯。快取剛好過期的時候:

B:查快取,沒有
B:查 DB,拿到 1299
A:把 DB 改成 999
A:刪掉快取
B:把 1299 寫回快取
--------------------
DB:999,快取:1299

B 從讀到 1299 到寫回快取只有一下子,A 要剛好在這一下子裡改好 DB、又刪完快取,機會很小。

怕的話,可以在刪完快取之後,過一小段時間再刪一次,把中間被寫回去的舊價格也刪掉,這叫延遲雙刪。等多久只能估,至少要比查一次 DB 再寫回快取還久。

網路上的延遲雙刪常寫成先刪快取、再改 DB、再刪一次。這樣改 DB 的途中有人來查,一樣會把舊價格寫回去,要等第二次刪才會好。接在 Cache Aside 後面再刪一次,舊價格留的時間比較短。

為什麼要在意

  • TTL 是最後一道保險。 刪快取也可能失敗,例如剛好 Redis 連不上,快取就會一直留著舊價格。有設 TTL,時間到了就會自己過期,沒設的話會一直錯下去。
  • 快取跟 DB 只能做到「最終一致」。 中間一定有一段時間不一樣,最後才會一樣,像 A 改好 DB、還沒刪快取的中間,B 查到的就是舊的。商品價格晚一下更新還可以接受;金融業的帳戶餘額、可用額度,畫面上顯示可以讀快取,真的要扣款的時候,一定要回 DB 確認。

相關範例程式碼可以參考 https://github.com/gary880306/redis-30days/tree/dev

明天

Day 22 的互斥鎖說過還有問題。明天來看分散式鎖:鎖過期了、刪到別人的鎖,要怎麼處理/images/emoticon/emoticon12.gif


上一篇
Day 22|快取擊穿與雪崩
下一篇
Day 24|分散式鎖
系列文
從購物車到秒殺:30 天 Redis 高併發自學筆記 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言