秒杀系统的核心功能点:
1.商家创建活动,设置时间段,选择参与活动的商品,设置折扣、库存等;
2.用户端可以看到秒杀预告列表、商品秒杀详情页用户点击立即抢购;
3.如果库存充足,则创建订单成功,否则秒杀失败。
4.提交订单后超时未支付,系统会自动关闭订单,回滚库存。
秒杀系统的核心技术难点:
1.并发流量大,秒杀开始前后,系统的瞬间请求流量飙升。
2.数据库负担过重。
3.超卖的情况。
4.不支付抢占名额,导致商品未销售出去。
项目针对以上问题做的优化方案:
目前主要用到了redis,在秒杀活动中,Redis具有以下优势:
1.高速读写:Redis基于内存,读写速度非常快,可以处理高并发的请求。
2.高可靠性:Redis支持主从复制和数据持久化,可以实现数据备份和恢复,保证数据的可靠性和一致性。
3.高并发性:Redis采用单线程模型,避免了线程切换和锁竞争的问题,可以处理大量的并发请求。
首先获取秒杀商品规格库存时从redis中读取,降低数据库压力,代码如下:
//从redis中获取规则库存
skuVo.setSeckillStock(seckillCache.getSkuStock(sku.getSeckillProductSkuId()));
//从redis中获取规格库存,判断库存是否充足
Integer seckillStock = seckillCache.getSkuStock(product.getSeckillSku().getSeckillProductSkuId());
if(product.getTotalNum() > seckillStock){
throw new BusinessException("很抱歉,秒杀商品库存不足");
}
//获取秒杀商品规格库存
public Integer getSkuStock(Integer seckillProductSkuId){
//查询是否有商品改库存锁
boolean exits = redisLock.selectLock(CommonRedisKey.getRedisKey(CommonRedisKey.SECKILL_SKU_UPDATE_STOCK,seckillProductSkuId));
// 存在锁
if(exits){
try {
Thread.sleep(1);//库存修改完毕后再查询,防止超卖
} catch (InterruptedException e) {
e.printStackTrace();
}
//重新尝试
this.getSkuStock(seckillProductSkuId);
}
String cacheKey = CommonRedisKey.getRedisKey(CommonRedisKey.SECKILL_SKU_STOCK,seckillProductSkuId);
Object object = redisTemplate.opsForValue().get(cacheKey);
if(object != null){
return (Integer)object;
}
SeckillProductSku sku = productSkuService.getById(seckillProductSkuId);
if(sku == null){
return 0;
}
//秒杀库存
Integer seckillStock = sku.getSeckillStock();
redisTemplate.opsForValue().set(cacheKey, seckillStock);
return seckillStock;
}
然后用户进行秒杀下单的时候,redis执行减库存操作,代码如下
/**
* 减少库存
* @param vo
*/
private void decStock(UpdateProductStockVo vo){
//给商品改库存上锁
boolean lock = redisLock.getLock(CommonRedisKey.getRedisKey(CommonRedisKey.SECKILL_SKU_UPDATE_STOCK,vo.getSkuSourceId()), 5);
// 拿到锁
if(lock){
SeckillProductSku sku = productSkuService.getById(vo.getSkuSourceId());
//减少redis中秒杀商品库存
Long stock = seckillCache.decrSkuStock(sku.getSeckillProductSkuId(), vo.getTotalNum());
//减少后库存
if(stock < 0){
throw new BusinessException("很抱歉,秒杀商品库存不足");
}
// 更新商品主表库存
productService.update(new LambdaUpdateWrapper<SeckillProduct>().eq(SeckillProduct::getSeckillProductId, sku.getSeckillProductId())
.setSql("`stock` = `stock` - " + vo.getTotalNum()));
// 更新规格库存
productSkuService.update(new LambdaUpdateWrapper<SeckillProductSku>().eq(SeckillProductSku::getSeckillProductSkuId, sku.getSeckillProductSkuId())
.setSql("`seckill_stock` = `seckill_stock` - " + vo.getTotalNum()));
}else {
try {
Thread.sleep(5);//等会再去拿锁
} catch (InterruptedException e) {
e.printStackTrace();
}
//重新尝试
this.decStock(vo);
}
}
//减少redis中秒杀商品库存
public Long decrSkuStock(Integer seckillProductSkuId,Integer num){
String cacheKey = CommonRedisKey.getRedisKey(CommonRedisKey.SECKILL_SKU_STOCK,seckillProductSkuId);
//先查询再操作,避免不存在报错
this.selectSkuStock(seckillProductSkuId,cacheKey);
return redisTemplate.opsForValue().decrement(cacheKey, num);
}
//订单超时未付款自动取消,回退redis库存
//增加redis中秒杀商品库存
public Long addSkuStock(Integer seckillProductSkuId,Integer num){
String cacheKey = CommonRedisKey.getRedisKey(CommonRedisKey.SECKILL_SKU_STOCK,seckillProductSkuId);
//先查询再操作,避免不存在报错
this.selectSkuStock(seckillProductSkuId,cacheKey);
return redisTemplate.opsForValue().increment(cacheKey, num);
}
//后台修改或者删除商品后,删除redis中秒杀商品库存信息,同步数据库库存
public void deleteSkuStock(Integer seckillProductSkuId) {
String cacheKey = CommonRedisKey.getRedisKey(CommonRedisKey.SECKILL_SKU_STOCK,seckillProductSkuId);
redisTemplate.delete(cacheKey);
}
另外还有其他要注意的点:
秒杀后不付款定时取消,回退库存等操作。这个时间可以设置短一点,以免长时间锁住库存不释放,导致回滚库存时活动已结束。

压力测试



1404

被折叠的 条评论
为什么被折叠?



