Springboot实现高并发秒杀系统
Springboot实现高并发秒杀系统 java高并发springbootrediszookeeper
·
背景
商品的库存为200个
实验目的: 200个商品只能生成200个订单 抵抗高并发高流量
如果最终实验结果跟理论值不一致则视为bug
一致的话我们再逐步去优化,让系统可以抵挡高并发,系统的吞吐量有明显的提升
首先我们 不加锁 不加缓存 不加事务
@PostMapping("/{productId}")
public AjaxResult seckill(@PathVariable("productId") Integer productId){
try{
orderService.Seckill(productId);
}catch (Exception e){
log.error("创建订单失败"+e);
return AjaxResult.error("创建订单失败");
}
return AjaxResult.success();
}
public void Seckill(Integer productId){
Product product = productService.getProductById(productId);
if(product.getStock()<=0){
throw new RuntimeException("商品库存已售完");
}
Order order = new Order();
order.setProductId(productId);
order.setAmount(product.getPrice());
int i = orderMapper.insertSelective(order);
//减库存
int updateNum = productService.deductProductStock(productId);
log.info("我是更新后的库存"+updateNum+"==============================");
if(updateNum<=0){
throw new RuntimeException("商品库存已售罄111");
}
}
我们可以看到运行完 系统的吞吐量是
商品的库存也变为0
然而订单表却生成了300多个订单 超卖了
很明显这不符合最终的效果 而且还是很大的一个bug(致命性的) 现在我们加上事务
@Transactional
public void Seckill(Integer productId){
Product product = productService.getProductById(productId);
if(product.getStock()<=0){
throw new RuntimeException("商品库存已售完");
}
Order order = new Order();
order.setProductId(productId);
order.setAmount(product.getPrice());
int i = orderMapper.insertSelective(order);
//减库存
int updateNum = productService.deductProductStock(productId);
log.info("我是更新后的库存"+updateNum+"==============================");
if(updateNum<=0){
throw new RuntimeException("商品库存已售罄111");
}
}
加上事务之后我们可以看到吞吐量明显下降 为300多
我们在来看订单表 200个订单没有超卖 这实现了理论上的效果 但是300多的吞吐量是不能抵挡高并发的
我们继续来优化
现在我们加上锁
@PostMapping("/{productId}")
public AjaxResult seckill(@PathVariable("productId") Integer productId){
if(productSoldOutMap.get(productId) != null){
log.error("商品已售罄");
return AjaxResult.error("商品已售完");
}
Long stock = stringRedisTemplate.opsForValue().decrement(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId);
if(stock<0){
productSoldOutMap.put(productId,true);
Long increment = stringRedisTemplate.opsForValue().increment(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId);
log.info("=======================stock"+increment);
return AjaxResult.error("商品已售完");
}
try{
orderService.Seckill(productId);
}catch (Exception e){
创建订单少卖 还原库存
stringRedisTemplate.opsForValue().increment(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId);
//修改标识
if(productSoldOutMap.get(productId) != null){
productSoldOutMap.remove(productId);
}
log.error("创建订单失败"+e);
return AjaxResult.error("创建订单失败");
}
return AjaxResult.success();
}
我们现在来看看吞吐量达到了4000多
我们再看看生成的订单
生成了200个订单是没有问题的
接下来我们配上负载均衡2台
发现吞吐量不是提升不是很明显
因为用了ConcurrentHashMap来存放是否售完的标记 所以需要使用ZooKeeper在分布式环境中同步变量
上ZooKeeper
@PostMapping("/{productId}")
public AjaxResult seckill(@PathVariable("productId") Integer productId) throws InterruptedException, KeeperException {
if(productSoldOutMap.get(productId) != null){
log.error("商品已售罄");
return AjaxResult.error("商品已售完");
}
Long stock = stringRedisTemplate.opsForValue().decrement(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId);
String zkSoldOutProductPath = Constants.getZKSoldOutProductPath(productId);
if(stock<0){
productSoldOutMap.put(productId,true);
log.error("=====================商品售{}完标记",productId);
Long increment = stringRedisTemplate.opsForValue().increment(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId);
log.info("=======================stock"+increment);
if(zooKeeper.exists(zkSoldOutProductPath,true)==null){
zooKeeper.create(zkSoldOutProductPath,"true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
}
//监听zk售完标记节点
zooKeeper.exists(zkSoldOutProductPath,true);
return AjaxResult.error("商品已售完");
}
try{
orderService.Seckill(productId);
}catch (Exception e){
// 创建订单少卖 还原库存
stringRedisTemplate.opsForValue().increment(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId);
//修改标识
if(productSoldOutMap.get(productId) != null){
productSoldOutMap.remove(productId);
}
if(zooKeeper.exists(zkSoldOutProductPath,true)!=null){
zooKeeper.setData(Constants.getZKSoldOutProductPath(productId),"false".getBytes(),-1);
}
log.error("创建订单失败"+e);
return AjaxResult.error("创建订单失败");
}
return AjaxResult.success();
}
@Configuration
@Slf4j
public class ZooKeeperWatcher implements Watcher, ApplicationContextAware {
private static ApplicationContext applicationContext;
private ZooKeeper zooKeeper;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ZooKeeperWatcher.applicationContext = applicationContext;
}
@Override
public void process(WatchedEvent event) {
if (Event.EventType.None == event.getType() && null == event.getPath()) {
log.info("=====================zookeeper连接成功");
if(zooKeeper==null){
zooKeeper = applicationContext.getBean(ZooKeeper.class);
}
try{
if(zooKeeper.exists(Constants.ZK_PRODUCT_SOLD_OUT_FLAG,false)==null){
zooKeeper.create(Constants.ZK_PRODUCT_SOLD_OUT_FLAG,"".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
}
}catch (Exception e){
e.printStackTrace();
}
} else if (event.getType() == Event.EventType.NodeDataChanged) { //zk目录节点数据变化通知事件
try {
String path = event.getPath();
String soldOutFlag = new String(zooKeeper.getData(path,true,new Stat()));
log.info("zookeeper数据节点修改变动,path={},value={}",path,soldOutFlag);
if("false".equals(soldOutFlag)){
String productId = path.substring(path.lastIndexOf("/")+1,path.length());
OrderController.getProductsoldOutMap().remove(productId);
}
} catch (Exception e) {
log.error("zookeeper数据节点修改回调事件异常");
}
}
}
}
更多推荐
已为社区贡献1条内容
所有评论(0)