秒殺系統(tǒng)之一:防止超賣(樂(lè)觀鎖)

前提:只是為了了解和學(xué)習(xí)關(guān)于秒殺的學(xué)習(xí)順便鞏固學(xué)到的技術(shù)點(diǎn)

1.1 秒殺場(chǎng)景

  • 電商搶購(gòu)限量商品
  • 賣周董演唱會(huì)的門(mén)票
  • 火車票搶座 12306
  • ..........

1.2 為什么要做個(gè)系統(tǒng)

如果你的項(xiàng)目流量非常小,完全不用擔(dān)心有并發(fā)的購(gòu)買(mǎi)請(qǐng)求,那么做這樣一個(gè)系統(tǒng)意義不大。但如果你的系統(tǒng)要像12306那樣,接受高并發(fā)訪問(wèn)和下單的考驗(yàn)仰剿,那么你就需要一套完整的流程保護(hù)措施,來(lái)保證你系統(tǒng)在用戶流量高峰期不會(huì)被搞掛了沛厨。

  • 嚴(yán)格防止超賣:庫(kù)存100件你賣了120件系馆,等著辭職吧(超賣)

  • 防止黑產(chǎn):防止不懷好意的人群通過(guò)各種技術(shù)手段把你本該下發(fā)給群眾的利益全收入了囊中。

  • 保證用戶體驗(yàn):高并發(fā)下搂蜓,別網(wǎng)頁(yè)打不開(kāi)了狼荞,支付不成功了,購(gòu)物車進(jìn)不去了帮碰,地址改不了了相味。這個(gè)問(wèn)題非常之大,涉及到各種技術(shù)殉挽,也不是一下子就能講完的丰涉,甚至根本就沒(méi)法講完。

1.3 保護(hù)措施有哪些

  • 樂(lè)觀鎖防止超賣 ---核心基礎(chǔ)斯碌,為了解決超賣的問(wèn)題一死,保持?jǐn)?shù)據(jù)的統(tǒng)一性
  • 令牌桶限流
  • Redis 緩存
  • 消息隊(duì)列異步處理訂單
  • ....

2. 防止超賣

畢竟,你網(wǎng)頁(yè)可以卡住最多是大家沒(méi)參與到活動(dòng)傻唾,上網(wǎng)口吐芬芳投慈,罵你一波。但是你要是賣多了冠骄,本該拿到商品的用戶可就不樂(lè)意了伪煤,輕則投訴你,重則找漏洞起訴賠償凛辣。讓你吃不了兜著走抱既。

2.1 數(shù)據(jù)庫(kù)表

-- ----------------------------
-- Table structure for stock
-- ----------------------------
DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名稱',
  `count` int(11) NOT NULL COMMENT '庫(kù)存',
  `sale` int(11) NOT NULL COMMENT '已售',
  `version` int(11) NOT NULL COMMENT '樂(lè)觀鎖,版本號(hào)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for stock_order
-- ----------------------------
DROP TABLE IF EXISTS `stock_order`;
CREATE TABLE `stock_order` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sid` int(11) NOT NULL COMMENT '庫(kù)存ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名稱',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2 分析業(yè)務(wù)

image-20200326184815171.png

2.3 開(kāi)發(fā)代碼

1. DAO代碼
public interface StockDAO {
    Stock checkStock(Integer id);//校驗(yàn)庫(kù)存
    void updateSale(Stock stock);//扣除庫(kù)存
}

public interface OrderDAO {
    void createOrder(Order order);//創(chuàng)建訂單
}

2. Service 代碼
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDAO orderDAO;
    @Autowired
    private StockDAO stockDAO;
    @Override
    public Integer createOrder(Integer id) {
        //校驗(yàn)庫(kù)存
        Stock stock = checkStock(id);
        //扣庫(kù)存
        updateSale(stock);
        //下訂單
        return createOrder(stock);
    }
    //校驗(yàn)庫(kù)存
    private Stock checkStock(Integer id) {
        Stock stock = stockDAO.checkStock(id);
        if (stock.getSale().equals(stock.getCount())) {
            throw new RuntimeException("庫(kù)存不足");
        }
        return stock;
    }
    //扣庫(kù)存
    private void updateSale(Stock stock){
        stock.setSale(stock.getSale() + 1);
        stockDAO.updateSale(stock);
    }
    //下訂單
    private Integer createOrder(Stock stock){
        Order order = new Order();
        order.setSid(stock.getId());
        order.setCreateDate(new Date());
        order.setName(stock.getName());
        orderDAO.createOrder(order);
        return order.getId();
    }
}
5. Controller代碼
@RestController
@RequestMapping("stock")
public class StockController {
    @Autowired
    private OrderService orderService;
    //秒殺方法
    @GetMapping("sale")
    public String sale(Integer id){
        int orderId = 0;
        try{
            //根據(jù)商品id創(chuàng)建訂單,返回創(chuàng)建訂單的id
            orderId =  orderService.createOrder(id);
            System.out.println("orderId = " + orderId);
            return String.valueOf(orderId);
        }catch (Exception e){
            e.printStackTrace();
            return e.getMessage();
        }
    }
}

2.4 正常測(cè)試

在正常測(cè)試下發(fā)現(xiàn)沒(méi)有任何問(wèn)題

2.5 使用Jmeter進(jìn)行壓力測(cè)試

官網(wǎng): https://jmeter.apache.org/

1. 介紹

Apache JMeter是Apache組織開(kāi)發(fā)的基于Java的壓力測(cè)試工具扁誓。用于對(duì)軟件做壓力測(cè)試防泵,它最初被設(shè)計(jì)用于Web應(yīng)用測(cè)試阳堕,但后來(lái)擴(kuò)展到其他測(cè)試領(lǐng)域。 它可以用于測(cè)試靜態(tài)和動(dòng)態(tài)資源择克,例如靜態(tài)文件恬总、Java 小服務(wù)程序、CGI 腳本肚邢、Java 對(duì)象壹堰、數(shù)據(jù)庫(kù)、FTP 服務(wù)器骡湖, 等等贱纠。JMeter 可以用于對(duì)服務(wù)器、網(wǎng)絡(luò)或?qū)ο竽M巨大的負(fù)載响蕴,來(lái)自不同壓力類別下測(cè)試它們的強(qiáng)度和分析整體性能谆焊。另外,JMeter能夠?qū)?yīng)用程序做功能/回歸測(cè)試浦夷,通過(guò)創(chuàng)建帶有斷言的腳本來(lái)驗(yàn)證你的程序返回了你期望的結(jié)果

2. 安裝Jmeter

# 1.下載jmeter
       https://jmeter.apache.org/download_jmeter.cgi
         下載地址:https://mirror.bit.edu.cn/apache//jmeter/binaries/apache-jmeter-5.2.1.tgz
# 2.解壓縮
        backups    ---用來(lái)對(duì)壓力測(cè)試進(jìn)行備份目錄
    bin        ---Jmeter核心執(zhí)行腳本文件
    docs         ---官方文檔和案例
    extras     ---額外的擴(kuò)展
    lib        ---第三方依賴庫(kù)
    licenses   ---說(shuō)明
    printable_docs ---格式化文檔

# 3.安裝Jmeter
      0.要求: 必須事先安裝jdk環(huán)境
      1.配置jmeter環(huán)境變量
          export JMETER_HOME=/Users/chenyannan/dev/apache-jmeter-5.2
                export PATH=$SCALA_HOME/bin:$JAVA_HOME/bin:$GRADLE_HOME/bin:$PATH:$JMETER_HOME/bin
        2.是配置生效
              source ~/.bash_profile
        3.測(cè)試jemeter

3. Jmeter使用

Don't use GUI mode for load testing !, only for Test creation and Test debugging.

For load testing, use CLI Mode (was NON GUI):

? jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

& increase Java Heap to meet your test requirements:

Modify current env variable HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m" in the jmeter batch file

Check : https://jmeter.apache.org/usermanual/best-practices.html

4. Jmeter壓力測(cè)試

jmeter -n -t [jmx file](jmx壓力測(cè)試文件) -l [results file](結(jié)果輸出的文件) -e -o [Path to web report folder](生成html版壓力測(cè)試報(bào)告)

2.6 樂(lè)觀鎖解決商品超賣問(wèn)題

說(shuō)明: 使用樂(lè)觀鎖解決商品的超賣問(wèn)題,實(shí)際上是把主要防止超賣問(wèn)題交給數(shù)據(jù)庫(kù)解決,利用數(shù)據(jù)庫(kù)中定義的version字段以及數(shù)據(jù)庫(kù)中的事務(wù)實(shí)現(xiàn)在并發(fā)情況下商品的超賣問(wèn)題辖试。

0.校驗(yàn)庫(kù)存的方法(不變)

//校驗(yàn)庫(kù)存
private Stock checkStock(Integer id){
  Stock stock = stockDAO.checkStock(id);
  if(stock.getSale().equals(stock.getCount())){
    throw  new RuntimeException("庫(kù)存不足!!!");
  }
  return stock;
}
<!--根據(jù)秒殺商品id查詢庫(kù)存-->
    <select id="checkStock" parameterType="int" resultType="Stock">
        select id,name,count,sale,version from stock
        where id = #{id}
    </select>

1. 更新庫(kù)存方法改造

//扣除庫(kù)存
private void updateSale(Stock stock){
    //在sql層面完成銷量的+1  和 版本號(hào)的+  并且根據(jù)商品id和版本號(hào)同時(shí)查詢更新的商品
    stockDAO.updateSale(stock);
}
 <update id="updateSale" parameterType="Stock">
        update stock set 
            sale=sale+1,
            version=version+1
         where 
            id =#{id}
            and 
            version = #{version}
 </update>

2. 創(chuàng)建訂單

//創(chuàng)建訂單
private Integer createOrder(Stock stock){
  Order order = new Order();
  order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
  orderDAO.createOrder(order);
  return order.getId();
}
<!--創(chuàng)建訂單-->
<insert id="createOrder" parameterType="Order" useGeneratedKeys="true" keyProperty="id" >
  insert into stock_order values(#{id},#{sid},#{name},#{createDate})
</insert>

3. 完整的業(yè)務(wù)方法與Mapper.xml

  • Service方法
  • image-20200328162237475.png
  • StockDAOMapper.xml
  • image-20200328162319138.png
  • OrderDAOMapper.xml
  • image-20200328162404085.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市劈狐,隨后出現(xiàn)的幾起案子罐孝,更是在濱河造成了極大的恐慌,老刑警劉巖肥缔,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莲兢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡续膳,警方通過(guò)查閱死者的電腦和手機(jī)改艇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坟岔,“玉大人谒兄,你說(shuō)我怎么就攤上這事∨诔担” “怎么了舵变?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瘦穆。 經(jīng)常有香客問(wèn)我纪隙,道長(zhǎng),這世上最難降的妖魔是什么扛或? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任绵咱,我火速辦了婚禮,結(jié)果婚禮上熙兔,老公的妹妹穿的比我還像新娘悲伶。我一直安慰自己艾恼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布麸锉。 她就那樣靜靜地躺著钠绍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪花沉。 梳的紋絲不亂的頭發(fā)上柳爽,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音碱屁,去河邊找鬼磷脯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛娩脾,可吹牛的內(nèi)容都是我干的赵誓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼柿赊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼俩功!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起闹瞧,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绑雄,失蹤者是張志新(化名)和其女友劉穎展辞,沒(méi)想到半個(gè)月后奥邮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡罗珍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年洽腺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片覆旱。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蘸朋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扣唱,到底是詐尸還是另有隱情藕坯,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布噪沙,位于F島的核電站炼彪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏正歼。R本人自食惡果不足惜辐马,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望局义。 院中可真熱鬧喜爷,春花似錦冗疮、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至湃密,卻和暖如春特愿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背勾缭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工揍障, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俩由。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓毒嫡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親幻梯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子兜畸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348