以下是個簡單的庫存扣減流程:
image.png
如果并發非常低的時候,基本就按這個流程走就行了。
而這個設計,并發量稍大時,就會導致超賣的情況出現,兩個同時要9臺手機的請求,同時查到庫存有12臺,那操作下來,就會導致超賣:
image.png
嗯,那么出于職業道德,得加把鎖:
image.png
嗯,這個設計,在高并發但庫存量極少的秒殺場景,或者庫存很高但并發量不高的(每秒10個請求),都是可行解。
然而,這個相當于把程序串行化了。那么假設處理一個訂單要200毫秒,在庫存量很高(10萬臺),并發量極高(每秒1000個請求),有個請求就要等待200秒了,這個肯定不能接受的。
解決辦法:
1.庫存分段
將10萬庫存,分成100段,每段1000個庫存。對應的,就有100把鎖去鎖這100個庫存段了,可以滿足100個線程同時跑。
image.png
這套方案確實可以解決高并發,高庫存問題,然而庫存分段也是個麻煩的事。
我這里還有個方案,雖然效率略低,但是跑起來應該還好。
2.庫存占用
image.png
把串行化的步驟,改成了只是簡單地往“庫存占用表”插入數據即可,耗費的時間是低的,可以應付較高的并發量。
實現方式:
public static void main(String[] args) {
try{
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6222");
config.useSingleServer().setPassword("test");
RedissonClient finalRedisson = Redisson.create(config);
//需求數量
int requireQty = 9;
for(int i=0;i<30;i++){
Thread t = new Thread(()->{
try{
RLock rLock = finalRedisson.getLock("myLock");
System.out.println(Thread.currentThread().getName()+ "開始");
RAtomicLong stockQty = finalRedisson.getAtomicLong("stockQty");
RAtomicLong stockOccupy = finalRedisson.getAtomicLong("stockOccupy");
rLock.lock();
long l1 = stockQty.get();
long l2 = stockOccupy.get();
long l = l1 - l2;
System.out.println(Thread.currentThread().getName() + "獲得鎖");
System.out.println(Thread.currentThread().getName() + "do something");
if(l >= requireQty) {
stockOccupy.set(stockOccupy.get()+requireQty);
}
rLock.unlock();
//創建訂單,扣減庫存
Thread.sleep(200);
if(l >= requireQty) {
System.out.println(Thread.currentThread().getName() + "done,庫存剩下:" + l);
}
else {
System.out.println(Thread.currentThread().getName() + "庫存不足,庫存剩下:" + l);
}
// System.out.println(Thread.currentThread().getName() + "準備釋放鎖");
System.out.println(Thread.currentThread().getName() + "結束");
}
catch (Exception ex){
System.out.println(ex.getMessage());
}
});
t.start();
}
}
catch (Exception ex){
System.out.println(ex.getMessage());
}
finally {
}
}
執行前:
image.png
image.png
執行后:
image.png
部分執行結果:
image.png
剩下1個庫存的時候,就跑步下去了。