我们将秒杀的商品Spu列表查询出来
当用户选择一个商品时
我们要将这个商品的sku也查询出来
也就是根据SpuId查询Sku的列表
创建SeckillSkuMapper
@Repository
public interface SeckillSkuMapper {
// 根据spuId查询sku列表
List<SeckillSku> findSeckillSkusBySpuId(Long spuId);
}
SeckillSkuMapper.xml文件添加内容
<sql id="SimpleField">
<if test="true">
id,
sku_id,
spu_id,
seckill_stock,
seckill_price,
gmt_create,
gmt_modified,
seckill_limit
if>
sql>
<select id="findSeckillSkusBySpuId" resultMap="BaseResultMap">
select
<include refid="SimpleField" />
from
seckill_sku
where
spu_id=#{spuId}
select>
根据给定时间查询出正在进行秒杀的商品列表
在秒杀过程中,一定会将当前时间正在进行秒杀商品查询出来的
首先保证数据库中的seckill_spu表的数据正在秒杀时间段(检查数据,如果不在秒杀时间段,将结束时间后移如2024年)
SeckillSpuMapper添加方法
// 根据指定时间,查询正在进行秒杀的商品
List<SeckillSpu> findSeckillSpusByTime(LocalDateTime time);
SeckillSpuMapper.xml
<select id="findSeckillSpusByTime" resultMap="BaseResultMap">
select
<include refid="SimpleField" />
from
seckill_spu
where
start_time < #{time}
and
end_time > #{time}
select>
SeckillSpuMapper接口添加方法
// 根据SpuId查询spu秒杀信息
SeckillSpu findSeckillSpuById(Long spuId);
SeckillSpuMapper.xml添加内容
<select id="findSeckillSpuById" resultMap="BaseResultMap">
select
<include refid="SimpleField" />
from
seckill_spu
where
spu_id=#{spuId}
select>
这个查询需要是为了后面布隆过滤器加载数据库中包含的所有SpuId时使用
因为布隆过滤器的特性,只需要查询出所有商品的spu_id即可
SeckillSpuMapper接口添加方法
// 布隆过滤器用:查询获得所有秒杀商品的SpuId数组
Long[] findAllSeckillSpuIds();
SeckillSpuMapper.xml添加内容
<select id="findAllSeckillSpuIds" resultType="long" >
select spu_id from seckill_spu
select>
在即将发生高并发业务之前,我们将一些高并发业务中需要的数据保存到Redis中,这种操作,就是"缓存预热",这样发生高并发时,这些数据就可以直接从Redis中获得,无需查询数据库了
我们要利用Quartz周期性的将每个批次的秒杀商品,预热到Redis
例如每天的12:00 14:00 16:00 18:00进行秒杀
那么就在 11:55 13:55 15:55 17:55 进行预热
我们预热的内容有
我们预热的内容是将参与秒杀商品的sku查询出来,根据skuid将该商品的库存保存在Redis中
还要注意为了预防雪崩,在向Redis保存数据时,都应该添加随机数
(待完善).在秒杀开始前,生成布隆过滤器,访问时先判断布隆过滤器,如果判断商品存在,再继续访问
在秒杀开始之前,生成每个商品对应的随机码,保存在Redis中,随机码可以绑定给有Spu,防止有用户知道SpuId暴力访问
利用Quartz将库存数和随机码保存到Redis
1.创建Job接口实现类
2.创建配置类,配置JobDetail和Trigger
在seckill包下创建timer.job包
在seckill包下创建timer.config包
首先我们编写缓存预热的操作,在job包下创建类SeckillInitialJob
@Slf4j
public class SeckillInitialJob implements Job {
// 查询秒杀sku库存数的mapper
@Autowired
private SeckillSkuMapper skuMapper;
// 需要查询秒杀spu相关的信息
@Autowired
private SeckillSpuMapper spuMapper;
// 操作Redis的对象
@Autowired
private RedisTemplate redisTemplate;
/*
RedisTemplate对象在保存数据到Redis时,会先将当前数据序列化后保存
这样做的好处是序列化后的数据保存到Redis读写效率更高,缺点是不能在Redis中修改数据
我们现在要预热的是秒杀sku库存数,这个库存数如果也用上面的redisTemplate保存的方式
就容易在高并发情况下由于线程安全问题导致"超卖"
解决方案就是我们需要创建一个能够直接在Redis中修改数据的对象,来避免超卖的发生
SpringDataRedis提供了一个可以直接在Redis中操作数值的对象:StringRedisTemplate
使用StringRedisTemplate向Redis保存数据,默认都会以字符串的方式保存(不序列化了)
又因为Redis支持直接将字符串类型的数值进行修改,所以可以将库存数保存并直接修改
这样就不需要写java代码读取和修改了,直接使用Redis内部的功能修改库存数
最后结合Redis天生单线程的特性,避免线程安全问题方式超卖
*/
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 当前方法是缓存预热操作
// 它执行的时间是秒杀开始前5分钟,所以获取一个5分钟后的时间对象
LocalDateTime time=LocalDateTime