spring boot + Mybatis + redis 秒殺系統
最近開了一些高併發的東西,以及一些秒殺系統,但感覺都沒有完整的描述。於是自己就動手實現了一個簡單版本的搶購系統。
本系統採用spring boot + mybatis + redis實現。
專案結構圖如下:
專案工程已放到GitHub上了,https://github.com/feibabm/seckill,需要的請自行下載。
本文主要借鑑網上一些通用的做法,做出一個例子,主要實現了一個搶購介面:
http://localhost:8080/seckill/product/1?userId=1
seckill.sql檔案為建表sql
pro_insert.sql檔案為success_killed表中資料新增10000條使用者預約記錄
seckill_insert.sql檔案為seckill生成一條產品資訊
具體驗證邏輯是執行test資料夾下的兩個test類:
RemoteInvote.java
RemoteInvote2.java
這兩個test類沒有什麼差別,主要是為了增加併發量
主要的搶票邏輯如下:
-
public SecKillResult secKillProduct(String userPhone, long productId) {
-
String state = (String)redisTemplate.opsForValue().get(userPhone + "_"+ productId);
-
//使用者資訊載入
-
if(null == state){
-
SuccessKilled successKilled = new SuccessKilled();
-
successKilled.setSeckillId(productId);
-
successKilled.setUserPhone(Long.valueOf(userPhone));
-
successKilled = successKilledMapper.selectOne(successKilled);
-
if(null == successKilled){
-
return new SecKillResult(false, "該使用者沒有預約");
-
}else{
-
synchronized (this){
-
state = (String)redisTemplate.opsForValue().get(userPhone + "_"+ productId);
-
if(null == state){
-
redisTemplate.opsForValue().set(userPhone + "_" + productId, successKilled.getState().toString(), 300, TimeUnit.SECONDS);
-
state = String.valueOf(successKilled.getState());
-
}
-
}
-
}
-
}
-
if(state.equals("-1")){
-
//查詢產品資訊
-
// ProductInfo productInfo = (ProductInfo)redisTemplate.opsForValue().get(productId + "");
-
List values = redisTemplate.opsForHash().values(productId + "");
-
if(values.size() == 0){
-
Seckill seckill = seckillMapper.selectByPrimaryKey(productId);
-
if(null == seckill){
-
return new SecKillResult(false, "沒有該秒殺商品資訊");
-
}
-
synchronized (this){
-
if(!redisTemplate.opsForHash().hasKey(productId + "", "number")){
-
// productInfo = new ProductInfo(seckill.getSeckillId(), seckill.getNumber(), seckill.getStartTime(), seckill.getEndTime());
-
HashMap<String, String> productHash = new HashMap<>();
-
productHash.put("number", seckill.getNumber() + "");
-
productHash.put("startTime", seckill.getStartTime().getTime() + "");
-
productHash.put("endTime", seckill.getEndTime().getTime() + "");
-
redisTemplate.opsForHash().putAll(productId +"", productHash);
-
redisTemplate.expire(productId + "", 300, TimeUnit.SECONDS);
-
values = redisTemplate.opsForHash().values(productId + "");
-
}
-
}
-
}
-
if( new Date(Long.valueOf((String)values.get(1))).after(new Date(System.currentTimeMillis()))){
-
return new SecKillResult(false, "搶購還沒有開始");
-
} else if(new Date(Long.valueOf((String)values.get(2))).before(new Date(System.currentTimeMillis()))){
-
return new SecKillResult(false, "搶購已經結束");
-
} else {
-
Long userState = redisTemplate.opsForValue().increment(userPhone + "_" + productId, 1);
-
if(userState == 0){
-
// Long increment = redisTemplate.opsForValue().increment(productId, -1);
-
Long number = redisTemplate.opsForHash().increment(productId + "", "number", -1);
-
if(number >= 0){
-
//訊息佇列非同步更新庫存,以及使用者的預約資訊
-
QueueEntity queueEntity = new QueueEntity(userPhone, productId);
-
ExecutorPool.queue.offer(queueEntity);
-
}else {
-
return new SecKillResult(false, "商品已經搶購完成");
-
}
-
}else {
-
redisTemplate.opsForValue().increment(userPhone + "_" + productId, -1);
-
return new SecKillResult(false, "您已搶購過該產品");
-
}
-
}
-
} else {
-
return new SecKillResult(false, "您已搶購過該產品");
-
}
-
return null;
-
}
自己用兩程序執行緒,大概兩分鐘掉了20000次,暫時沒有出現啥問題。如果有什麼問題,希望大家指出,謝謝。