高并發(fā)下的下單功能設(shè)計(jì)

功能需求:設(shè)計(jì)一個秒殺系統(tǒng)

初始方案

商品表設(shè)計(jì):熱銷商品提供給用戶秒殺匾灶,有初始庫存孔飒。

@Entity
public class SecKillGoods implements Serializable{
    @Id
    private String id;
 
    /**
     * 剩余庫存
     */
    private Integer remainNum;
 
    /**
     * 秒殺商品名稱
     */
    private String goodsName;
}

秒殺訂單表設(shè)計(jì):記錄秒殺成功的訂單情況

@Entity
public class SecKillOrder implements Serializable {
    @Id
    @GenericGenerator(name = "PKUUID", strategy = "uuid2")
    @GeneratedValue(generator = "PKUUID")
    @Column(length = 36)
    private String id;
 
    //用戶名稱
    private String consumer;
 
    //秒殺產(chǎn)品編號
    private String goodsId;
 
    //購買數(shù)量
    private Integer num;
}

Dao設(shè)計(jì):主要就是一個減少庫存方法,其他CRUD使用JPA自帶的方法

public interface SecKillGoodsDao extends JpaRepository<SecKillGoods,String>{
 
    @Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1")
    @Modifying(clearAutomatically = true)
    @Transactional
    int reduceStock(String id,Integer remainNum);
 
}

數(shù)據(jù)初始化以及提供保存訂單的操作:

@Service
public class SecKillService {
 
    @Autowired
    SecKillGoodsDao secKillGoodsDao;
 
    @Autowired
    SecKillOrderDao secKillOrderDao;
 
    /**
     * 程序啟動時:
     * 初始化秒殺商品沉馆,清空訂單數(shù)據(jù)
     */
    @PostConstruct
    public void initSecKillEntity(){
        secKillGoodsDao.deleteAll();
        secKillOrderDao.deleteAll();
        SecKillGoods secKillGoods = new SecKillGoods();
        secKillGoods.setId("123456");
        secKillGoods.setGoodsName("秒殺產(chǎn)品");
        secKillGoods.setRemainNum(10);
        secKillGoodsDao.save(secKillGoods);
    }
 
    /**
     * 購買成功,保存訂單
     * @param consumer
     * @param goodsId
     * @param num
     */
    public void generateOrder(String consumer, String goodsId, Integer num) {
        secKillOrderDao.save(new SecKillOrder(consumer,goodsId,num));
    }
}

下面就是controller層的設(shè)計(jì)

@Controller
public class SecKillController {
 
    @Autowired
    SecKillGoodsDao secKillGoodsDao;
    @Autowired
    SecKillService secKillService;
 
    /**
     * 普通寫法
     * @param consumer
     * @param goodsId
     * @return
     */
    @RequestMapping("/seckill.html")
    @ResponseBody
    public String SecKill(String consumer,String goodsId,Integer num) throws InterruptedException {
        //查找出用戶要買的商品
        SecKillGoods goods = secKillGoodsDao.findOne(goodsId);
        //如果有這么多庫存
        if(goods.getRemainNum()>=num){
            //模擬網(wǎng)絡(luò)延時
            Thread.sleep(1000);
            //先減去庫存
            secKillGoodsDao.reduceStock(num);
            //保存訂單
            secKillService.generateOrder(consumer,goodsId,num);
            return "購買成功";
        }
        return "購買失敗,庫存不足";
    }
 
}

上面是全部的基礎(chǔ)準(zhǔn)備汰扭,下面使用一個單元測試方法,模擬高并發(fā)下冀痕,很多人來購買同一個熱門商品的情況荔睹。

@Controller
public class SecKillSimulationOpController {
 
    final String takeOrderUrl = "http://127.0.0.1:8080/seckill.html";
 
    /**
     * 模擬并發(fā)下單
     */
    @RequestMapping("/simulationCocurrentTakeOrder")
    @ResponseBody
    public String simulationCocurrentTakeOrder() {
        //httpClient工廠
        final SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
        //開50個線程模擬并發(fā)秒殺下單
        for (int i = 0; i < 50; i++) {
            //購買人姓名
            final String consumerName = "consumer" + i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ClientHttpRequest request = null;
                    try {
                        URI uri = new URI(takeOrderUrl + "?consumer=consumer" + consumerName + "&goodsId=123456&num=1");
                        request = httpRequestFactory.createRequest(uri, HttpMethod.POST);
                        InputStream body = request.execute().getBody();
                        BufferedReader br = new BufferedReader(new InputStreamReader(body));
                        String line = "";
                        String result = "";
                        while ((line = br.readLine()) != null) {
                            result += line;//獲得頁面內(nèi)容或返回內(nèi)容
                        }
                        System.out.println(consumerName+":"+result);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        return "simulationCocurrentTakeOrder";
    }
 
}

訪問localhost:8080/simulationCocurrentTakeOrder狸演,就可以測試了
預(yù)期情況:因?yàn)槲覀冎粚γ霘⑸唐罚?23456)初始化了10件,理想情況當(dāng)然是庫存減少到0僻他,訂單表也只有10條記錄严沥。

實(shí)際情況:訂單表記錄

Paste_Image.png

商品表記錄

Paste_Image.png

下面分析一下為啥會出現(xiàn)超庫存的情況:

因?yàn)槎鄠€請求訪問,僅僅是使用dao查詢了一次數(shù)據(jù)庫有沒有庫存中姜,但是比較惡劣的情況是很多人都查到了有庫存消玄,這個時候因?yàn)槌绦蛱幚淼难舆t,沒有及時的減少庫存丢胚,那就出現(xiàn)了臟讀翩瓜。如何在設(shè)計(jì)上避免呢?最笨的方法是對SecKillController的seckill方法做同步携龟,每次只有一個人能下單兔跌。但是太影響性能了,下單變成了同步操作峡蟋。

@RequestMapping("/seckill.html")
@ResponseBody
public synchronized String SecKill

改進(jìn)方案

根據(jù)多線程編程的規(guī)范坟桅,提倡對共享資源加鎖,在最有可能出現(xiàn)并發(fā)爭搶的情況下加同步塊的思想蕊蝗。應(yīng)該同一時刻只有一個線程去減少庫存仅乓。但是這里給出一個最好的方案,就是利用Oracle,MySQL的行級鎖–同一時間只有一個線程能夠操作同一行記錄蓬戚,對SecKillGoodsDao進(jìn)行改造:

public interface SecKillGoodsDao extends JpaRepository<SecKillGoods,String>{
 
    @Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1 and g.remainNum>0")
    @Modifying(clearAutomatically = true)
    @Transactional
    int reduceStock(String id,Integer remainNum);
 
}

僅僅是加了一個and夸楣,卻造成了很大的改變,返回int值代表的是影響的行數(shù)子漩,對應(yīng)到controller做出相應(yīng)的判斷豫喧。

@RequestMapping("/seckill.html")
    @ResponseBody
    public String SecKill(String consumer,String goodsId,Integer num) throws InterruptedException {
        //查找出用戶要買的商品
        SecKillGoods goods = secKillGoodsDao.findOne(goodsId);
        //如果有這么多庫存
        if(goods.getRemainNum()>=num){
            //模擬網(wǎng)絡(luò)延時
            Thread.sleep(1000);
            if(goods.getRemainNum()>0) {
                //先減去庫存
                int i = secKillGoodsDao.reduceStock(goodsId, num);
                if(i!=0) {
                    //保存訂單
                    secKillService.generateOrder(consumer, goodsId, num);
                    return "購買成功";
                }else{
                    return "購買失敗,庫存不足";
                }
            }else {
                return "購買失敗,庫存不足";
            }
        }
        return "購買失敗,庫存不足";
    }

在看看運(yùn)行情況

Paste_Image.png

訂單表:

Paste_Image.png

在高并發(fā)問題下的秒殺情況,即使存在網(wǎng)絡(luò)延時幢泼,也得到了保障紧显。
想學(xué)習(xí)高并發(fā)或架構(gòu)等java高級技術(shù)的可以加群:283904828

程序員渾渾噩噩的生活百態(tài),女朋友看了都心疼
http://blog.sina.com.cn/s/blog_178608ff50102wso9.html
程序員的反思:不要一輩子靠技術(shù)生存
http://blog.sina.com.cn/s/blog_178608ff50102wsmx.html
一位程序員工作10年總結(jié)了這些忠告
http://blog.sina.com.cn/s/blog_178608ff50102wsmx.html
給迷茫的JAVA員一些中肯建議缕棵,不然你就廢了孵班,快速成為架構(gòu)師
http://blog.sina.com.cn/s/blog_178608ff50102wsjk.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挥吵,隨后出現(xiàn)的幾起案子重父,更是在濱河造成了極大的恐慌,老刑警劉巖忽匈,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件房午,死亡現(xiàn)場離奇詭異,居然都是意外死亡丹允,警方通過查閱死者的電腦和手機(jī)郭厌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門袋倔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人折柠,你說我怎么就攤上這事宾娜。” “怎么了扇售?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵前塔,是天一觀的道長。 經(jīng)常有香客問我承冰,道長华弓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任困乒,我火速辦了婚禮寂屏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娜搂。我一直安慰自己迁霎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布百宇。 她就那樣靜靜地躺著考廉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恳谎。 梳的紋絲不亂的頭發(fā)上芝此,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音因痛,去河邊找鬼。 笑死岸更,一個胖子當(dāng)著我的面吹牛鸵膏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怎炊,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼谭企,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了评肆?” 一聲冷哼從身側(cè)響起债查,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瓜挽,沒想到半個月后盹廷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡久橙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年俄占,在試婚紗的時候發(fā)現(xiàn)自己被綠了管怠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡缸榄,死狀恐怖渤弛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甚带,我是刑警寧澤她肯,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站鹰贵,受9級特大地震影響晴氨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜砾莱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一瑞筐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腊瑟,春花似錦聚假、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至财松,卻和暖如春瘪贱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辆毡。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工菜秦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舶掖。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓球昨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眨攘。 傳聞我的和親對象是個殘疾皇子主慰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 功能需求:設(shè)計(jì)一個秒殺系統(tǒng) 初始方案 商品表設(shè)計(jì):熱銷商品提供給用戶秒殺,有初始庫存鲫售。 @Entity publi...
    余平的余_余平的平閱讀 247評論 0 0
  • 來源:陶邦仁 鏈接:http://my.oschina.net/xianggao/blog/524943 0 系列...
    meng_philip123閱讀 3,543評論 0 65
  • 給自己的總是很多期待共螺,期待更多的美好的回憶,我不要現(xiàn)在情竹,不求飛黃騰達(dá)藐不,只求讓自己心安理得的面對很多事情,秋天一轉(zhuǎn)眼...
    一個人的光陰閱讀 363評論 0 2
  • 有一段故事需要來訴說,我卻不知道它是否可以成文佳吞。也許拱雏,你將會是我書中的主角,然底扳,已成為我生命中的過客铸抑。好似,從未來過衷模!
    X半心閱讀 234評論 0 0
  • 有些人阱冶,從你第一次見到他開始刁憋,你就清楚的知道,于他木蹬,你將毫無尊嚴(yán)至耻。 緣分的伏筆埋得太深,時間的過度線卻又太淺镊叁。第一...
    奏世華章閱讀 442評論 0 2