最近开了一些高并发的东西,以及一些秒杀系统,但感觉都没有完整的描述。于是自己就动手实现了一个简单版本的抢购系统。
本系统采用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次,暂时没有出现啥问题。如果有什么问题,希望大家指出,谢谢。