iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0

秒殺的時候,所有人都在搶同一個 stock:1,這種被很多人同時讀寫的 key 叫熱 Key。另一種是裡面裝了太多東西的 key,叫大 Key。

KEYS * 一個指令卡住所有人,大 Key 也一樣:對它下一個指令,裡面有幾筆就要處理幾筆。今天先看大 Key 怎麼找、怎麼刪,再看熱 Key。

找大 Key

先做兩個大 Key,各塞 100 萬筆,例如把一個活動所有參加的人都放在同一個 Set。等一下一個用 DEL 刪、一個用 UNLINK 刪(在主機的終端機下):

# big:1、big:2 各塞 100 萬筆,--pipe 批次送
docker exec -i redis30days sh -c 'awk "BEGIN{for(i=0;i<1000000;i++) printf \"SADD big:1 m%d\r\nSADD big:2 m%d\r\n\", i, i}" | redis-cli --pipe'

接著找出來,再看它佔多少記憶體(在主機的終端機下):

docker exec redis30days redis-cli --bigkeys

docker exec redis30days redis-cli MEMORY USAGE big:1

https://ithelp.ithome.com.tw/upload/images/20261009/20184209UZM5LMIqNI.png

https://ithelp.ithome.com.tw/upload/images/20261009/20184209XVjd6iIahj.png

--bigkeys 會列出每種型別最大的 key,Set 最大的就是剛剛灌的,有 1000000 筆。它是用 SCAN 分批掃,不會像 KEYS * 一次卡住所有人。

MEMORY USAGE 是這個 key 佔了多少 bytes,48388712 差不多是 46MB,只是一個 key。--bigkeys 比的是筆數,想照記憶體大小排,可以換成 --memkeys。

刪大 Key

刪一個 key 看起來很簡單,但 DEL 要把裡面 100 萬筆一筆一筆釋放掉,這段時間其他人都在等。用慢查詢日誌看花了多久(在主機的終端機下):

docker exec redis30days redis-cli SLOWLOG RESET

docker exec redis30days redis-cli DEL big:1

docker exec redis30days redis-cli UNLINK big:2

docker exec redis30days redis-cli SLOWLOG GET

https://ithelp.ithome.com.tw/upload/images/20261009/20184209mOlS84Rv5d.png

慢查詢日誌會記下超過 10 毫秒的指令(slowlog-log-slower-than 預設 10000 微秒),SLOWLOG RESET 是先把之前的清掉。

DEL big:1 被記下來了,DEL 上面的 87589 是它花了多久,單位是微秒,差不多 88 毫秒。跟 KEYS * 一樣,這 88 毫秒裡其他人的指令都在排隊。

UNLINK big:2 沒有被記下來,不到 10 毫秒就回了。它不當場釋放:先把 key 拿掉,裡面的 100 萬筆丟給背景慢慢釋放。

所以刪大 Key 用 UNLINK。也可以把 lazyfree-lazy-user-del 設成 yes(預設是 no),DEL 就會跟 UNLINK 一樣丟給背景。

慢查詢日誌只留最近 128 筆(slowlog-max-len),重開就沒了。

熱 Key

大 Key 是一個 key 太大,熱 Key 是一個 key 被打太多次。找熱 Key 用 --hotkeys,但直接下會噴錯(在主機的終端機下):

docker exec redis30days redis-cli --hotkeys

https://ithelp.ithome.com.tw/upload/images/20261009/20184209qfRBR2xXbR.png

--hotkeys 看的是 OBJECT FREQ,要先換成 LFU,Redis 才會幫每個 key 算被碰了幾次。

maxmemory 也要一起設。沒設的話,Redis 為了省記憶體,值一樣的小數字(0 到 9999)只存一份,大家共用,被碰的次數也算在一起。stock:1 扣到 0 之後,其他值也是 0 的 key 就算沒人碰過,也會跟它一樣熱。1gb 只是設一個夠大、不會開始淘汰的上限。

Java 開著,用 deduct.js 打 5000 次 stock:1(在主機的終端機下):

docker exec redis30days redis-cli CONFIG SET maxmemory 1gb

docker exec redis30days redis-cli CONFIG SET maxmemory-policy allkeys-lfu

docker exec redis30days redis-cli SET stock:1 100

./k6/run.sh deduct

docker exec redis30days redis-cli --hotkeys

https://ithelp.ithome.com.tw/upload/images/20261009/20184209fTFfOf3lzv.png

掃了 10005 個 key,只找到 stock:1,counter 是 37。跟 OBJECT FREQ 一樣是取對數,不是真的次數,所以 5000 次只有 37。

測完記得還原(在主機的終端機下):

docker exec redis30days redis-cli CONFIG SET maxmemory 0

docker exec redis30days redis-cli CONFIG SET maxmemory-policy noeviction

熱 Key 怎麼辦

Redis 一次只處理一個指令,一個 key 再熱也只有一台在扛。換成 Cluster 也沒用:一個 key 只會落在一個 slot,其他台再閒也分不到。

常見的兩種做法:

  • 讀很多的,App 自己再存一份。 例如秒殺商品的介紹,每台 App 在自己的記憶體放幾秒,大部分的讀就不會到 Redis。之前為了布隆過濾器加進來的 Guava,裡面也有做本機快取的 CacheBuilder。代價是這幾秒可能讀到舊的。
  • 寫很多的,拆成好幾個 key。 例如原本 stock:1 的 100 件,拆成 stock:1:0~stock:1:9 10 個 key,每個 key 放 10 件,加起來還是 100 件,每個人隨機挑一個扣。這 10 個 key 會分到 10 個不同的 slot,Cluster 上就能分給好幾台扛。代價是挑到的那個扣完了,要換一個再試。

為什麼要在意

  • 大 Key 平常也在卡。 不只 DEL,SMEMBERS 這種整包拿的指令一樣要處理 100 萬筆,跟 LRANGE 0 -1 是同一個問題。能拆就拆成好幾個小 key,要整包看就用 SSCAN 分批拿。
  • 危險指令先關掉。 KEYS、FLUSHALL 這種一下就出事的指令,正式環境通常會用 ACL 讓 App 的帳號不能下。

明天

今天的問題都是出事了才去找。明天來看平常要盯哪些數字,在出事之前就看出來/images/emoticon/emoticon12.gif


上一篇
Day 26|秒殺:限流與削峰
系列文
從購物車到秒殺:30 天 Redis 高併發自學筆記 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言