前提:只是為了了解和學(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ù)
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方法
- StockDAOMapper.xml
- OrderDAOMapper.xml