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