![]()
2010年某個(gè)凌晨,Salvatore Sanfilippo在Redis 1.2版本里塞進(jìn)去一個(gè)命令。當(dāng)時(shí)沒人想到,這個(gè)叫INCR的東西會(huì)在15年后成為每秒處理千萬(wàn)級(jí)請(qǐng)求的標(biāo)配——而實(shí)現(xiàn)它,開發(fā)者只需要敲一行代碼。
分布式系統(tǒng)里最諷刺的事:數(shù)數(shù)比算微積分還難。
兩個(gè)進(jìn)程同時(shí)讀到"10",各自加1,都想寫回"11"。結(jié)果你丟了數(shù)據(jù),用戶看了假統(tǒng)計(jì),老板看了假報(bào)表。這種race condition(競(jìng)態(tài)條件)不是新手專利,Twitter早期就栽過(guò),某次活動(dòng)計(jì)數(shù)偏差直接讓運(yùn)營(yíng)團(tuán)隊(duì)通宵返工。
Redis的解法粗暴得讓人懷疑人生:既然并發(fā)讀寫會(huì)打架,那就別讓它們同時(shí)發(fā)生。
單線程不是缺陷,是設(shè)計(jì)
Redis把所有命令塞進(jìn)一個(gè)線程排隊(duì)執(zhí)行。INCR的讀取-計(jì)算-寫入被打包成原子操作,中間插不進(jìn)任何其他命令。這相當(dāng)于給每個(gè)數(shù)字配了個(gè)專屬柜臺(tái)柜員,而不是讓顧客自己從貨架上拿貨、算賬、再放回去。
對(duì)比傳統(tǒng)方案:數(shù)據(jù)庫(kù)行鎖需要維護(hù)鎖表,樂觀鎖需要版本號(hào)碰撞重試,分布式鎖需要操心網(wǎng)絡(luò)分區(qū)。INCR的代價(jià)是零——沒有鎖超時(shí),沒有重試風(fēng)暴,沒有腦裂。
![]()
一組數(shù)字能說(shuō)明問題。某內(nèi)容平臺(tái)用INCR替換自研計(jì)數(shù)服務(wù)后,P99延遲從23ms降到0.8ms,CPU占用下降67%。不是優(yōu)化,是降維。
INCR家族:你以為的1個(gè)命令,實(shí)際是5個(gè)
基礎(chǔ)版INCR每次加1,適合瀏覽量、點(diǎn)贊數(shù)這種"來(lái)一個(gè)算一個(gè)"的場(chǎng)景。但業(yè)務(wù)需求從來(lái)不這么乖。
INCRBY讓你指定步長(zhǎng),比如統(tǒng)計(jì)帶寬消耗每次加1024字節(jié)。DECR和DECRBY做減法,庫(kù)存扣減、余額消費(fèi)直接復(fù)用同一套原子語(yǔ)義。最冷門的是INCRBYFLOAT,浮點(diǎn)數(shù)計(jì)數(shù)——某金融科技公司用它做實(shí)時(shí)匯率累計(jì),精度問題在Redis內(nèi)部解決,業(yè)務(wù)代碼不用碰BigDecimal的坑。
一個(gè)細(xì)節(jié)很多人漏掉:INCR對(duì)不存在的鍵會(huì)自動(dòng)初始化為0。這意味著你不需要先SET再INCR,少一次網(wǎng)絡(luò)往返,也少一個(gè)競(jìng)態(tài)窗口。
生產(chǎn)環(huán)境的三個(gè)暗礁
第一個(gè)坑是TTL。INCR創(chuàng)建的鍵默認(rèn)永不過(guò)期,計(jì)數(shù)器跑一年能把內(nèi)存撐爆。正確做法是在第一次INCR后判斷返回值,如果是1就設(shè)過(guò)期時(shí)間——代碼里那個(gè)if (count == 1)不是潔癖,是救命。
![]()
第二個(gè)坑是集群模式。Redis Cluster的key被哈希到不同槽位,如果想按用戶ID分片統(tǒng)計(jì)全局總量,得用Hash Tag把相關(guān)key強(qiáng)制落到同一節(jié)點(diǎn)。否則你的"實(shí)時(shí)總榜"會(huì)變成跨節(jié)點(diǎn)聚合的分布式事務(wù), latency直接起飛。
第三個(gè)坑最隱蔽:INCR返回的是增量后的值,不是增量本身。某電商系統(tǒng)曾用這個(gè)返回值做庫(kù)存校驗(yàn),結(jié)果高并發(fā)下兩個(gè)請(qǐng)求都讀到"還剩1件",都以為搶到了。應(yīng)該用INCR配合判斷returnedValue <= stockLimit,而不是等INCR完再查。
誰(shuí)在靠這行代碼吃飯
微博熱搜的實(shí)時(shí)熱度、抖音的播放進(jìn)度條、滴滴的派單計(jì)數(shù)、Coinbase的成交筆數(shù)——底層全是INCR或其變體。不是因?yàn)樗鼈兗夹g(shù)選型保守,而是這個(gè)問題域里,簡(jiǎn)單方案就是最優(yōu)解。
有個(gè)反直覺的事實(shí):Redis作者antirez在2019年博客寫過(guò),INCR的實(shí)現(xiàn)代碼15年沒變過(guò)。單線程原子性這個(gè)設(shè)計(jì)決策,比后續(xù)所有性能優(yōu)化都長(zhǎng)壽。
你現(xiàn)在打開任意一個(gè)App,大概率有某個(gè)數(shù)字正在Redis里被INCR。它可能是你這條內(nèi)容的閱讀數(shù),可能是某個(gè)紅包的剩余個(gè)數(shù),也可能是系統(tǒng)正在監(jiān)控的QPS閾值。
問題是:你的計(jì)數(shù)場(chǎng)景,還在用鎖嗎?
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.