環境架好了,先跑內建的壓測工具,有數字再談原理:
docker exec redis30days redis-benchmark -t set,get -n 100000 -q (-n 送 10 萬次請求,-q 只印結果)

每秒 24 萬次寫、26 萬次讀,一半以上的請求 0.1 毫秒上下就回來了。
工作上的 API 大概 50 毫秒回來就算不錯。當然那是一整趟請求(查 DB、跑邏輯、組回傳),跟這裡單一個 GET 不能直接比,但跟 Redis 還是差很大!
第一個原因是資料都放在記憶體裡。
| 存取來源 | 大約耗時 |
|---|---|
| 記憶體 | 100 奈秒 |
| SSD 隨機讀 | 100 微秒(1,000 倍) |
| 傳統硬碟尋軌 | 10 毫秒(100,000 倍) |
MySQL 也有 Buffer Pool 在記憶體裡放資料,但每個查詢還是得經過 SQL 解析、交易和鎖的管理,記憶體對它來說只是快取。Redis 反過來,記憶體就是它的家,持久化才是額外做的事(之後會學持久化)。
第二個原因是每個型別底層都為特定操作優化過。
LPUSH 往 List 開頭插入是 O(1),SISMEMBER 判斷在不在集合裡也是 O(1)。同樣的事在關聯式資料庫裡都得繞索引一圈。
之後講到型別會順便看它底層是什麼。
第三個原因最酷,Redis 處理指令是單執行緒的。
只有一條執行緒代表沒有鎖競爭、沒有 context switch、也沒有多執行緒的同步成本。反正瓶頸本來就不在 CPU,在記憶體和網路。
那上萬個連線怎麼辦?靠 I/O 多工(epoll),一條執行緒同時盯著所有連線,誰有資料進來就處理誰。
單執行緒還有一個副作用:每個指令天然就是原子的。 不用加鎖、不用 synchronized,INCR 就是不會少加。
單執行緒讓指令天然原子,但代價是一個慢指令會卡住所有人。
KEYS * 會把所有 key 撈出來,有幾筆就得掃幾筆。資料少的時候看不出來,先灌 100 萬筆進去:
# --pipe 批次送,一筆一筆打要跑很久
docker exec -i redis30days sh -c \
"awk 'BEGIN{for(i=0;i<1000000;i++) printf \"SET key:%d v%d\r\n\", i, i}' | redis-cli -n 1 --pipe"
接著開兩個終端機。A 只做一件事,不停送 PING 量往返時間:
docker exec -it redis30days redis-cli --latency
B 去跑 KEYS *:
docker exec redis30days redis-cli -n 1 KEYS '*'
A 的那行數字會即時更新,B 一打下去就看得到:
B 還沒動作
B 跑了一次 KEYS *
A 從頭到尾只是在送 PING,什麼都沒做錯,卻有一次等了 108 毫秒才拿到回應,因為 Redis 那條唯一的執行緒正忙著 B 的 KEYS *,誰都插不了隊。
順便看一下 avg,只從 0.12 變成 0.24 毫秒,低到完全看不出問題,但 max 跳到了 108。一千多次取樣裡就那麼一次,監控看平均一切正常,但那個使用者是真的等了 108 毫秒。
用前面 26 萬 QPS 換算,那 108 毫秒本來可以處理將近三萬個請求。
正式環境不要用 KEYS,要掃 key 請用 SCAN 分批拿。 大 Key 也會有一樣的問題,留到之後會遇到。
原理講完了,明天從最簡單的 String 開始![]()