RabbitMQ在秒殺場景中的簡單應用
秒殺業務的核心是庫存處理,使用者購買成功後會進行減庫存操作,並記錄購買明細。當秒殺開始時,大量使用者同時發起請求,這是一個並行操作,多條更新庫存數量的SQL語句會同時競爭秒殺商品所處資料庫表裡的那行資料,導致庫存的減少數量與購買明細的增加數量不一致,因此,我們使用RabbitMQ進行削峰限流並且將請求資料序列處理。
首先我先設計了兩張表,一張是秒殺庫存表,另一張是秒殺成功表。
CREATE TABLE seckill
(
seckill_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '商品庫存id',
NAME VARCHAR(120) NOT NULL COMMENT '商品名稱',
number INT NOT NULL COMMENT '庫存數量',
initial_price BIGINT NOT NULL COMMENT '原價',
seckill_price BIGINT NOT NULL COMMENT '秒殺價',
sell_point VARCHAR(500) NOT NULL COMMENT '賣點',
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒殺建立時間',
start_time TIMESTAMP NOT NULL COMMENT '秒殺開始時間',
end_time TIMESTAMP NOT NULL COMMENT '秒殺結束時間',
PRIMARY KEY (seckill_id)
);
ALTER TABLE seckill COMMENT '秒殺庫存表';
CREATE INDEX idx_create_time ON seckill
(
create_time
);
CREATE INDEX idx_start_time ON seckill
(
start_time
);
CREATE INDEX idx_end_time ON seckill
(
end_time
);
CREATE TABLE success_killed
(
success_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '秒殺成功id',
seckill_id BIGINT NOT NULL COMMENT '秒殺商品id',
user_phone BIGINT NOT NULL COMMENT '使用者手機號',
state TINYINT NOT NULL DEFAULT -1 COMMENT '狀態標誌:-1:無效;0:成功',
create_time TIMESTAMP NOT NULL COMMENT '秒殺成功建立時間',
PRIMARY KEY (success_id)
);
ALTER TABLE success_killed COMMENT '秒殺成功表';
CREATE INDEX idx_create_time ON success_killed
(
create_time
);
接下來我開始模擬使用者請求,往RabbitMQ中傳送100個手機號。
public String goods(@PathVariable("seckillId")Long seckillId){
for(int i = 100;i<200;i++){
seckillService.setGoods(seckillId,"13145678"+i);
}
return "success";
}
public void setGoods(Long seckillId,StringuserPhone){
String goods = seckillId+"/"+userPhone;
rabbitTemplate.convertAndSend("executeSeckill",goods);
}
然後我用RabbitMQ監聽seckill_queue佇列,當佇列中接收到訊息就會自動觸發RabbitMQService類中的executeSeckill方法,訊息將作為方法的引數傳遞進來執行秒殺操作。public class RabbitMQService {
@Autowired
privateSeckillMapper seckillMapper;
@Autowired
privateSuccessKilledMapper successKilledMapper;
privatestatic final Logger logger = Logger.getLogger(RabbitMQService.class);
publicvoid executeSeckill(String goods){
String[] good = goods.split("/");
Long seckillId = Long.parseLong(goods.substring(0, goods.indexOf('/')));
String userPhone = goods.substring(goods.lastIndexOf('/')+1);
Date nowTime = new Date();
Seckill seckill = seckillMapper.queryById(seckillId);
Date startTime = seckill.getStartTime();
Date endTime = seckill.getEndTime();
try {
//更新庫存數量
int updateCount = seckillMapper.updateReduceNumber(seckillId, nowTime);
if(updateCount > 0) {
//記錄購買行為
int insertCount = successKilledMapper.insertSuccessKilled(seckillId, userPhone);
}else {
logger.info("手機號為"+userPhone+"的使用者秒殺失敗");
}
}catch (RuntimeException e) {
//spring事務回滾只對執行期異常起作用
throw new RuntimeException("seckill error:" + e.getMessage());
}
}
}
最後我在前端頁面使用倒計時外掛增強使用者體驗效果。