Node.js實作Mutex(互斥鎖)防止緩存擊穿
最近在工作上需要寫一隻會高併發的API。為了不讓DB被灌爆,所以在DB前面再加一層redis當緩存,並且設定TTL過期,因此整個API程式流程大致上變成:
- 從redis取資料 -> 成功取到資料 -> 返回
如果今天redis因為資料TTL過期導致取不到資料,這時候才訪問DB:
- 從redis取資料 -> 沒有取到資料 -> 從DB取資料 -> 成功取到資料,將資料回存redis並設定TTL -> 返回
轉換成程式碼的話看起來也並沒有特別的複雜:
const item = await redisClient.hgetall(itemKey); if(item) { return item; } const sql = 'SELECT * FROM `Product` WHERE `id` = ?'; const [rows, fields] = await dbConnection.query(sql, [itemKey]); if(rows.length > 0) { const [item] = rows; await redisClient .multi() .hmset(itemKey, item) .expire(itemKey, 60) .exec(); return item; } return null;
不過,魔鬼藏在細節裡。Node.js雖然說是使用單執行緒在執行,但因為用到了async/await
的語法,所以實際上當遇到異步函式時,Node.js會將異步函式丟進event loop等有空時才執行,並且當promise被resolve或reject後才從剛剛await
處接續往下。這就讓在高併發的情況下,會發生如下圖中的狀況。
近期迴響