借助Redis做秒殺和限流的思考
最近群里聊起秒殺和限流,我自己沒有做過類似應(yīng)用,但是工作中遇到過更大的數(shù)據(jù)和并發(fā)。
于是提出了一個(gè)簡單的模型:
var count = rds.inc(key);
if(count > 1000) throw "已搶光!"
借助Redis單線程模型,它的inc是安全的,確保每次加一,然后返回加一后的結(jié)果。如果原來是234,加一了就是235,返回的一定是235,在此中間,不會(huì)有別的請求來打斷從而導(dǎo)致返回236或者其它。
其實(shí)我們可以理解為inc的業(yè)務(wù)就是占坑排隊(duì),每人占一個(gè)坑,拿到排隊(duì)小票后看看是不是超額了,再從業(yè)務(wù)層面輸出秒殺結(jié)果,甚至做一些更加復(fù)雜的業(yè)務(wù)。
六條提到限流,可能基于某種考慮,希望把key對應(yīng)的count給限制在1000附近,可以接受1%偏差。
于是有了改進(jìn)模型:
var count = rds.inc(key);
if(count > 1000){
rds.dec(key);
throw "超出限額!"
}
就加了一句,超出限額后,把小票給減回去^_^
采用Redis有一個(gè)好處,比如支持很多應(yīng)用服務(wù)器一起搶……
當(dāng)然,對于很大量的秒殺,這個(gè)模型也不一定合理,比如要槍10萬部手機(jī),然后來了300萬用戶,瞬間擠上來。
這里有個(gè)變通方法可以試一下,那就是準(zhǔn)備10個(gè)Redis實(shí)例,每個(gè)放1萬。用戶請求過來的時(shí)候,可以隨機(jī)數(shù)或者散列取模,找對應(yīng)實(shí)例來進(jìn)行搶購。
同理可以直接更多用戶的場景。總的來說,在數(shù)據(jù)較大的時(shí)候,隨機(jī)和散列就具有一定統(tǒng)計(jì)學(xué)意義,相對來說是比較均衡的。
上面是大量秒殺的簡單場景,那么小數(shù)據(jù)場景呢?比如就只有幾萬并發(fā)的場景。
小數(shù)據(jù)場景,單應(yīng)用實(shí)例,可以考慮把Redis都給省了。
初級模型:
Interlocked.Increase(ref count);
if(count >= 1000) throw "搶光啦!"
中級模型:
private volatile Int32 count;
var old = 0;
do {
old = count;
if(old >= 1000) throw "搶光啦!"
}while(Interlocked.CompareExchange(ref count, old + 1, old) != old);
這個(gè)CAS原子操作可是好東西,在x86指令集下有專門指令CMPXCHG來處理,在處理器級別確保比較和交換數(shù)據(jù)的原子性。大多數(shù)系統(tǒng)想要邁過10萬tps的門檻向100萬tps靠齊,就必須得實(shí)現(xiàn)無鎖操作lock-free,其中CAS是最為簡單易懂,盡管有時(shí)候有ABA問題,但我們可以找到許多解決辦法。
在實(shí)際使用場景中,可能有更復(fù)雜的需求,那就另當(dāng)別論,這里只能班門弄斧幾個(gè)簡單易用的模型。

浙公網(wǎng)安備 33010602011771號