<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用JMeter模擬秒殺場(chǎng)景

          共 1665字,需瀏覽 4分鐘

           ·

          2021-10-20 12:53

          最近工作中,需要開發(fā)一個(gè)健身房預(yù)定的功能,我主要負(fù)責(zé)后端的開發(fā)。這是一個(gè)很經(jīng)典的秒殺場(chǎng)景,所以想記錄一下我關(guān)于秒殺的設(shè)計(jì),以及如何使用工具來模擬秒殺。

          一、什么是秒殺?

          秒殺就是大量用戶同一時(shí)間同時(shí)進(jìn)行搶購(gòu),從系統(tǒng)層面來看,就是多個(gè)進(jìn)程(線程)同時(shí)訪問同一個(gè)共享資源。

          舉個(gè)栗子:雙十一李佳琪直播間,這個(gè)就屬于很經(jīng)典的秒殺場(chǎng)景,每放出一個(gè)商品,很多人就一起去搶購(gòu),誰搶到就是誰的。

          溫馨提示:各位男士可以提前做下筆記,雙十一給女朋友 or 老婆一個(gè)驚喜哦!

          二、秒殺場(chǎng)景的特點(diǎn)

          1、高并發(fā):秒殺時(shí)大量用戶會(huì)在同一時(shí)間同時(shí)進(jìn)行搶購(gòu),網(wǎng)站瞬時(shí)訪問流量激增。
          2、讀多寫少:訪問請(qǐng)求量遠(yuǎn)遠(yuǎn)大于庫(kù)存數(shù)量,只有少部分用戶能夠搶購(gòu)成功。
          3、防止超賣現(xiàn)象:秒殺流程比較簡(jiǎn)單,一般就是下訂單減庫(kù)存。但是,很容易出現(xiàn)超賣問題,我們可以使用分布式鎖(集群部署)或者synchrnized(單機(jī)部署),或者先修改庫(kù)存再生成訂單等方法,防止訂單生成了但沒有庫(kù)存的超賣問題。

          超賣問題:比如Lamer面霜庫(kù)存有 100 件,但是在搶購(gòu)過程中,導(dǎo)致 1000 個(gè)用戶下單成功。那么就會(huì)有 9900 個(gè)用戶,顯示下單成功,但庫(kù)存不夠,沒有商品發(fā)給她們,這個(gè)體驗(yàn)太不好了,很容易招到瘋狂投訴。

          三、秒殺 Demo

          我做的這個(gè)健身房預(yù)定屬于一個(gè)小項(xiàng)目,由于公司內(nèi)部使用,并發(fā)量不是很高,所以是單機(jī)部署,我使用的是 Java 中的 synchronized 關(guān)鍵字來控制超賣。

          /**
          ?*?預(yù)定控制類
          ?*/

          @RestController
          @RequestMapping("/ding")
          public?class?DingController?{
          ????@Resource
          ????private?DingService?dingService;

          ????/**
          ?????*?
          ?????*?@param?dingDetail?
          ?????*?@return
          ?????*/

          ????@RequestMapping("/saveDing")
          ????public?Result?saveDingDetail(@RequestBody?DingDetail?dingDetail){
          ????????return?Result.success(dingService.saveDingDetail(dingDetail));
          ????}
          ????
          }
          /**
          ?*?預(yù)定接口類
          ?*/

          public?interface?DingService?{
          ????/**
          ?????*?
          ?????*?@param?dingDetail
          ?????*?@return
          ?????*/

          ????String?saveDingDetail(DingDetail?dingDetail);
          }
          /**
          ?*?預(yù)定接口實(shí)現(xiàn)類
          ?*/

          @Service
          public?class?DingServiceImpl?implements?DingService?{
          ????/**
          ?????*?
          ?????*?@param?dingDetail
          ?????*?@return
          ?????*/

          ????@Override
          ????public?synchronized?String?saveDingDetail(DingDetail?dingDetail)?{
          ????????String?dingId?=?dingDetail.getDingId();
          ????????Ding?ding?=?dingMapper.selectById(dingDetail.getDingId());
          ????????int?dingNum?=?ding.getDingNum();
          ????????int?num?=?ding.getNum();
          ????????if(dingNum?????????????//?下單:將當(dāng)前用戶插入到預(yù)定詳情表
          ????????????dingDetailMapper.insert(dingDetail);
          ????????????
          ????????????//?修改庫(kù)存:預(yù)定表,已預(yù)定人數(shù)加1
          ????????????dingNum?=?dingNum?+?1;
          ????????????dingMapper.updateDingNum(dingId,?dingNum);
          ????????????
          ????????????return?"預(yù)定成功!";
          ????????}
          ????????return?"預(yù)定失敗!";
          ????}
          }
          /**
          ?*?預(yù)定表實(shí)體類
          ?*?存儲(chǔ)每個(gè)預(yù)定的信息,包括預(yù)定名稱、預(yù)定類型......可預(yù)定人數(shù)、已預(yù)定人數(shù)
          ?*/

          @Data
          @TableName("t_ding")
          public?class?Ding?extends?BaseEntity?{
          ????/**
          ?????*?預(yù)定名稱
          ?????*/

          ????private?String?name;
          ????/**
          ?????*?預(yù)定類型
          ?????*/

          ????private?String?type;
          ????/**
          ?????*?可預(yù)定開始時(shí)間
          ?????*/

          ????@DateTimeFormat(pattern?=?"yyyy-MM-dd?HH:mm:ss")
          ????@JsonFormat(pattern?="yyyy-MM-dd?HH:mm:ss",?timezone?=?"GMT+8")
          ????@JSONField(format?=?"yyyy-MM-dd?HH:mm:ss")
          ????private?Date?startTime;
          ????/**
          ?????*?可預(yù)定結(jié)束時(shí)間
          ?????*/

          ????@DateTimeFormat(pattern?=?"yyyy-MM-dd?HH:mm:ss")
          ????@JsonFormat(pattern?="yyyy-MM-dd?HH:mm:ss",?timezone?=?"GMT+8")
          ????@JSONField(format?=?"yyyy-MM-dd?HH:mm:ss")
          ????private?Date?endTime;
          ????/**
          ?????*?預(yù)定日期
          ?????*/

          ????@DateTimeFormat(pattern?=?"yyyy-MM-dd")
          ????@JsonFormat(pattern?="yyyy-MM-dd",?timezone?=?"GMT+8")
          ????@JSONField(format?=?"yyyy-MM-dd")
          ????private?Date?dingDate;
          ????/**
          ?????*?預(yù)定開始時(shí)間
          ?????*/

          ????@DateTimeFormat(pattern?=?"yyyy-MM-dd?HH:mm:ss")
          ????@JsonFormat(pattern?="yyyy-MM-dd?HH:mm:ss",?timezone?=?"GMT+8")
          ????@JSONField(format?=?"yyyy-MM-dd?HH:mm:ss")
          ????private?Date?dingStartTime;
          ????/**
          ?????*?預(yù)定結(jié)束時(shí)間
          ?????*/

          ????@DateTimeFormat(pattern?=?"yyyy-MM-dd?HH:mm:ss")
          ????@JsonFormat(pattern?="yyyy-MM-dd?HH:mm:ss",?timezone?=?"GMT+8")
          ????@JSONField(format?=?"yyyy-MM-dd?HH:mm:ss")
          ????private?Date?dingEndTime;
          ????/**
          ?????*?可預(yù)定人數(shù)
          ?????*/

          ????private?Integer?num;
          ????/**
          ?????*?已預(yù)定人數(shù)
          ?????*/

          ????private?Integer?dingNum;
          }
          /**
          ?*?預(yù)定詳情表實(shí)體類
          ?*?存儲(chǔ)用戶信息,包括用戶ID、用戶姓名、預(yù)定ID(和預(yù)定表的?ID?關(guān)聯(lián),是預(yù)定表的外鍵)......
          ?*/

          @Data
          @TableName("t_ding_detail")
          public?class?DingDetail?extends?BaseEntity?{
          ????/**
          ?????*?用戶ID
          ?????*/

          ????private?String?userId;
          ????/**
          ?????*?用戶姓名
          ?????*/

          ????private?String?userName;
          ????/**
          ?????*?預(yù)定ID
          ?????*/

          ????private?String?dingId;
          ????/**
          ?????*?是否簽到(0-否,1-是)
          ?????*/

          ????private?Integer?signIn;
          ????/**
          ?????*?預(yù)定開始時(shí)間
          ?????*/

          ????@DateTimeFormat(pattern?=?"yyyy-MM-dd?HH:mm:ss")
          ????@JsonFormat(pattern?="yyyy-MM-dd?HH:mm:ss",?timezone?=?"GMT+8")
          ????@JSONField(format?=?"yyyy-MM-dd?HH:mm:ss")
          ????private?Date?dingStartTime;
          ????/**
          ?????*?預(yù)定結(jié)束時(shí)間
          ?????*/

          ????@DateTimeFormat(pattern?=?"yyyy-MM-dd?HH:mm:ss")
          ????@JsonFormat(pattern?="yyyy-MM-dd?HH:mm:ss",?timezone?=?"GMT+8")
          ????@JSONField(format?=?"yyyy-MM-dd?HH:mm:ss")
          ????private?Date?dingEndTime;
          }

          四、使用 JMeter 模擬秒殺場(chǎng)景

          上述就是預(yù)定過程的代碼,在將這段代碼交付給前端開發(fā)調(diào)用之前,需要做好充分的測(cè)試,所以我想模擬秒殺場(chǎng)景,對(duì)這個(gè)接口做個(gè)測(cè)試,確保其沒有問題,再將其交給前端同事。

          有想過自己寫一個(gè)線程池,創(chuàng)建 100 個(gè)線程去模擬這個(gè)場(chǎng)景,但是覺得有點(diǎn)麻煩,上網(wǎng)查了一下,發(fā)現(xiàn)有很多現(xiàn)成的壓測(cè)工具,最終決定使用 Apache JMeter 來做這個(gè)模擬。

          1、JMeter 下載

          JMeter 官網(wǎng)下載安裝包,JMeter 官網(wǎng)下載地址:https://jmeter.apache.org/,見圖1、2,下載下來的 JMeter 安裝包見圖3。

          圖1
          圖2
          圖3
          2、JMeter 安裝

          下載之后,解壓到任意目錄。由于我本機(jī)所有軟件都放在 D:\software 下,所以我將其解壓到這個(gè)目錄。

          首先將壓縮包從下載目錄移動(dòng)到 D:\software 目錄,右鍵壓縮包,選擇“解壓到當(dāng)前文件夾”。

          圖4
          3、JMeter 啟動(dòng)

          解壓之后,以后每次需要啟動(dòng) JMeter,就進(jìn)到 bin 目錄,雙擊 jmeter.bat 即可啟動(dòng)。

          圖5

          注意:在安裝 JMeter 之前,本機(jī)應(yīng)該已經(jīng)安裝了 1.8 及以上版本的 JDK,因?yàn)?JMeter 是用 Java 寫的,運(yùn)行的時(shí)候需要 Java 環(huán)境。否則你雙擊 jmeter.bat 啟動(dòng) Jmeter 時(shí)會(huì)報(bào)錯(cuò),報(bào)錯(cuò)信息見圖6。

          圖6 Java未安裝錯(cuò)誤

          注意:雙擊 jmeter.bat 啟動(dòng) JMeter 的時(shí)候會(huì)有兩個(gè)窗口,Jmeter 的命令窗口(圖7)和 Jmeter 的圖形操作界面(圖8),不可以關(guān)閉命令窗口。

          圖7
          圖8
          4、JMeter 基礎(chǔ)設(shè)置
          (1)圖形操作界面語言切換

          JMeter 默認(rèn)界面語言是英文,為了方便,我們將其切換成中文,有兩種方式,臨時(shí)切換和永久切換,臨時(shí)切換,下次重啟 JMeter ,又變回英文了,永久切換,下次重啟仍然是中文。

          (a)臨時(shí)切換

          圖9

          (b)永久切換

          修改 JMeter 配置文件,進(jìn)入 bin 目錄,找到 jmeter.properties 文件,使用編輯器打開,在 #language=en 下面插入一行 language=zh_CN,修改后保存,然后重啟 JMter。以后每次啟動(dòng) Jmeter 界面顯示的都是簡(jiǎn)體中文。

          圖10
          圖11
          (2)修改 Jmeter 默認(rèn)編碼為 utf-8 解決控制臺(tái)亂碼

          JMeter 下載下來之后,默認(rèn)編碼是 ISO-8859-1,但是使用這種編碼方式,如果 HTTP 響應(yīng)中包含中文,就會(huì)出現(xiàn)中文亂碼的問題,見圖12。

          圖12

          解決方案就是將編碼方式修改為 utf-8,有兩種方式修改編碼,一種是使用后置處理器 BeanShell PostProcessor,但是重啟之后,編碼又變回 ISO-8859-1,還是會(huì)出現(xiàn)中文亂碼問題,一種是修改 JMeter 配置文件,永久修改編碼。

          (a)通過后置處理器 BeanShell PostProcessor 修改編碼

          • 右鍵點(diǎn)擊 “剛才創(chuàng)建的線程組” → “添加” → “后置處理器” → “BeanShell PostProcessor”
          圖13

          輸入 “prev.setDataEncoding("utf-8"); ”,修改響應(yīng)數(shù)據(jù)編碼格式為utf-8,此時(shí)發(fā)起請(qǐng)求,響應(yīng)結(jié)果中就沒有亂碼了。

          (b)修改配置文件

          進(jìn)入 bin 目錄,找到 jmeter.properties 文件,使用編輯器打開,在 #sampleresult.default.encoding=ISO-8859-1 下面插入一行 sampleresult.default.encoding=utf-8,修改后保存,然后重啟 JMeter。

          圖14
          5、JMeter 模擬秒殺
          (1)新建測(cè)試計(jì)劃
          • 點(diǎn)擊 "文件” → “新建”
          圖15
          圖16
          (2)添加線程組
          • 右鍵點(diǎn)擊 "測(cè)試計(jì)劃” → “添加” → “線程(用戶)” → “線程組”
          圖17
          • 配置線程組參數(shù)
          圖18

          線程組主要參數(shù)詳細(xì)介紹

          • 線程數(shù):虛擬用戶數(shù)。一個(gè)虛擬用戶占用一個(gè)進(jìn)程或線程,模擬多少用戶訪問就填寫多少個(gè)線程數(shù)。
          • Ramp-Up(秒):設(shè)置的虛擬用戶數(shù)需要多長(zhǎng)時(shí)間全部啟動(dòng)。如果線程數(shù)為100,Ramp-Up 為 5 秒,那么需要 5 秒鐘啟動(dòng) 100 個(gè)線程,也就是每秒鐘啟動(dòng) 20 個(gè)線程,相當(dāng)于每秒模擬20個(gè)用戶進(jìn)行訪問。Ramp-Up 設(shè)置為 0 既是并發(fā)訪問。
          • 循環(huán)次數(shù):如果線程數(shù)為 100,循環(huán)次數(shù)為 100。那么總請(qǐng)求數(shù)為 100*100=10000 。如果勾選了“永遠(yuǎn)”,那么所有線程會(huì)一直發(fā)送請(qǐng)求,直到選擇停止運(yùn)行腳本。

          因?yàn)槲蚁肽M 30 個(gè)人同一時(shí)間去預(yù)定 10 臺(tái)跑步機(jī),所以我設(shè)置的參數(shù)如下,線程數(shù):30,Ramp-Up:0(模擬 10 個(gè)線程在同一時(shí)間并發(fā)執(zhí)行),循環(huán)次數(shù):1,總請(qǐng)求數(shù) 30*1=30 次,見圖17。

          (3)添加我們要測(cè)試的接口
          • 右鍵點(diǎn)擊 “剛才創(chuàng)建的線程組” → “添加” → “取樣器” → “HTTP請(qǐng)求”
          圖19
          • 填寫接口請(qǐng)求參數(shù),我要測(cè)試的接口屬于 Spring Boot 項(xiàng)目,配置如下:
          圖20

          Http請(qǐng)求主要參數(shù)詳細(xì)介紹

          • 協(xié)議:向目標(biāo)服務(wù)器發(fā)送HTTP請(qǐng)求協(xié)議,可以是 HTTP 或 HTTPS,默認(rèn)為 HTTP。
          • 服務(wù)器名稱或IP :HTTP請(qǐng)求發(fā)送的目標(biāo)服務(wù)器名稱或IP,我們這里是要測(cè)試本地接口,所以服務(wù)器名稱或IP為 localhost 或者 127.0.0.1。
          • 端口號(hào):目標(biāo)服務(wù)器的端口號(hào),我們這里是 8080。
          • 方法:發(fā)送 HTTP 請(qǐng)求的方法,可用方法包括GET、POST、HEAD、PUT、OPTIONS、TRACE、DELETE等,我們這個(gè)接口使用 POST 訪問。
          • 路徑:目標(biāo) URL 路徑,即 URL 中去掉服務(wù)器地址、端口及參數(shù)后剩余的部分,我們這里是 xxx/ding/saveDing,xxx 是應(yīng)用名稱(spingboot ?項(xiàng)目中的 spring.application.name)。
          • 內(nèi)容編碼:編碼方式,默認(rèn)為ISO-8859-1編碼,我們這里配置為 utf-8。
          • 參數(shù):接口參數(shù),如果是GET請(qǐng)求,我們將參數(shù)設(shè)置在參數(shù)表中,表中每一行為一個(gè)參數(shù)(key:參數(shù)名,value:參數(shù)值),注意參數(shù)傳入中文時(shí)需要勾選“編碼”,如果是POST請(qǐng)求,我們可以將參數(shù)以JSON方式寫在消息體數(shù)據(jù)框中。
          (4)添加察看結(jié)果樹
          • 右鍵點(diǎn)擊 “剛才創(chuàng)建的線程組” → “添加” → “監(jiān)聽器” → “察看結(jié)果樹”
          圖21
          • 然后修改響應(yīng)數(shù)據(jù)格式,我這里用的是JSON格式,運(yùn)行上面的 HTTP 請(qǐng)求,就可以在取樣器結(jié)果中看到本次請(qǐng)求返回的響應(yīng)數(shù)據(jù)。
          圖22
          (5)運(yùn)行 HTTP 請(qǐng)求,察看模擬結(jié)果

          接下來,我將使用 JMeter 來模擬健身房預(yù)定場(chǎng)景,我將健身房跑步機(jī)設(shè)置為 10 臺(tái),線程組線程數(shù)設(shè)置為 30,模擬 30 個(gè)用戶搶購(gòu)這 10 臺(tái)跑步機(jī),誰搶到就是誰的。

          圖23

          期間踩過一些坑,我都用紅色文字做了標(biāo)記,大家可以注意一下,如果遇到同樣的問題,可以拿來借鑒。

          選擇你要運(yùn)行的 HTTP 請(qǐng)求,點(diǎn)擊上側(cè)的綠色三角形,運(yùn)行該 HTTP 請(qǐng)求,此時(shí)界面會(huì)彈出一個(gè)提示框,大概意思是“是否要在測(cè)試之前保存這個(gè)測(cè)試案例”,一般選擇“No”或者 X 掉就行

          圖24

          HTTP 請(qǐng)求運(yùn)行起來之后,察看結(jié)果樹會(huì)顯示請(qǐng)求運(yùn)行結(jié)果。此時(shí)發(fā)現(xiàn)察看結(jié)果樹中 “HTTP請(qǐng)求”字樣是紅色的,說明 HTTP 請(qǐng)求失敗了,見圖25。

          圖25

          查看請(qǐng)求失敗的原因,隨便點(diǎn)開一個(gè)“HTTP請(qǐng)求”,一般的問題從 “取樣器結(jié)果”、“請(qǐng)求”、“響應(yīng)數(shù)據(jù)” 這三個(gè)地方基本都能找到原因。

          見圖26,分析請(qǐng)求失敗的原因,響應(yīng)碼是 415,415 一般是由 HTTP 請(qǐng)求頭中 Content-Type 不對(duì)引起的。從報(bào)錯(cuò)信息可以看到我們剛才發(fā)送的 HTTP 請(qǐng)求的 Content-type 是 text/plain 類型,而這個(gè)請(qǐng)求是 POST 請(qǐng)求,POST 請(qǐng)求默認(rèn)是 JSON 數(shù)據(jù)格式。我們只要將請(qǐng)求的 Content-type 修改成 JSON 格式,即可解決這個(gè)問題

          圖26

          解決方法是將 HTTP 請(qǐng)求的 Content-Type 修改為 JSON 格式,怎么修改呢?

          右鍵點(diǎn)擊 “剛才創(chuàng)建的線程組” → “添加” → “配置元件” → “HTTP信息頭管理器”,見圖27,在 HTTP 信息頭管理器中,將 Content-Type 修改為 application/json,見圖28。

          圖27
          圖28

          將察看結(jié)果樹清除,“右鍵察看結(jié)果樹” → “清除”,見圖29,再次運(yùn)行 HTTP 請(qǐng)求,此時(shí)查看結(jié)果樹中的“HTTP請(qǐng)求”字樣是綠色的,表示HTTP請(qǐng)求成功,見圖30。

          圖29
          圖30

          查看模擬結(jié)果,30 個(gè)用戶,10 個(gè)預(yù)定席位,察看結(jié)果樹中,30 個(gè)請(qǐng)求,有 10 個(gè)預(yù)定成功,20個(gè)預(yù)定失敗,見圖31、32,再看預(yù)定詳情表,只插入了 10 條數(shù)據(jù),見圖33,綜上這些說明秒殺模擬成功了。

          圖31
          圖32
          圖33


          瀏覽 272
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天日夜夜做 | 无码首页 | 99在线精品视频 | 激情五月色情在线播放 | 黑人嫖中国女精品视频 |