一、什么是秒杀?
秒杀技术实现核心思想是运用缓存减少数据库瞬间的访问压力!秒杀是一个高访问,高并发的活动,所以秒杀有自己独立的服务器,
二、业务逻辑
1.商家提交要秒杀的商品,商家通过审核这些秒杀商品后,通过定时任务将符合条件的秒杀商品从mysql服务器中读取到redis中当作缓存,减少了高并发去访问秒杀页面(避免了去数据库中查所有秒杀的商品)的步骤
具体步骤:去shop_web页面有一个秒杀商品管理,点击跳转seckillGoods.html页面,然后就显示该商家下(seller)所有要进行秒杀的商品(tb_seckill_goods),通过分页框架显示此商家(seller)下所有的秒杀商品
2.定时任务之SpringTask:一个单独的子模块(war),pyg_seckill_task模块,把所有符合秒杀的商品在低并发、低访问的时间段全部保存到redis中作为缓存.
使用步骤:
在该模块的applicationContext-task.xml配置文件中开启包扫描和注解驱动:
在包扫描的目录下新建一个类,并在类的上方加上@Component注解,在想要定时执行任务的方法上加上@Scheduled注解,注解里面的内容是每隔5s执行一次方法中的任务
3.这样,当用户访问秒杀首页seckill-index.html(在pyg_seckill_web模块中)时,需要展示所有秒杀商品时,就不用去数据库中获取了,而是去redis缓存中拿,优化了效率,降低了高并发访问数据库的压力
4.在seckill-index.html页面点击立即抢购按钮跳转到秒杀详情页seckill-item.html,展示该秒杀商品的详细内容.通过静态页面传参的方法#?将该商品的id传过去,秒杀详情页面通过初始化findOne()方法获取id的值并去后端通过在redis缓存中查找对应的秒杀商品(tb_seckill_goods)的所有信息并返给前端页面做显示
5.在seckill-index.html秒杀商品详情页点击立即抢购按钮,向后台传递秒杀商品的id,如果能抢购成功,那么会生成一张秒杀订单seckill_order.
具体步骤:在SeckillOrderController.java中接收前台传递过来要抢购秒杀商品的id,然后,顺便通过安全框架获取本用户user,一起传递到SeckillOrderServiceImpl层做逻辑处理,首先处理的逻辑一是防止一个用户购买多个同一件秒杀商品(限购),上来就判断redis缓存中根据秒杀商品id为key判断是否有对应的userId,如果有,则表示已经抢购过该商品,不能重复抢购,抛出异常
逻辑二是解决多个用户同时进入service层的add()方法,用户人数大于商品库存(超卖问题,通过使用reids保证了线程安全问题,线程同步问题),在之前的定时任务中,循环当前的秒杀商品库存数,利用list类型,将每个商品挨个leftpush到redis缓存中,key是秒杀商品的id,这样,如果用户通过了逻辑一(表示用户之前没有抢购过这种商品),那么就看能否从redis中rightpop出一个对象来了,如果能pop出来,那么表示抢到了,如果pop出来的结果为null,表示没有抢到,抛出异常
逻辑三是根据传过来的秒杀商品id从redis中取出秒杀商品的对象,将其的库存数量-1并把redis中对应的商品库存数量做同步(也-1)
逻辑四是用户秒杀成功后,将商品id为key,用户id为value以set类型存到redis中,配合上边防止多次购买问题,并且再做一个判断,如果秒杀商品的数量-1后变成了0,则直接将redis中的商品删除
逻辑五是根据秒杀的商品来创建一个秒杀订单对象
逻辑六是去数据库里的操作,因为这个add(Long goodId,String userId)方法在秒杀的过程中会被多个用户同时访问,高并发,所以,凡是涉及到去操作数据库中内容的,都要挪到线程池中一个新开辟的子线程中去操作,避免了在这个主线程中同时操作数据库造成数据库的压力太大而卡死.
实现步骤:
在配置文件配置线程池
在配置文件包扫描的目录下新建线程类(创建的目的完全是为了解决主线程操作数据卡顿的问题)
add方法所在的service层实现类中注入线程池和子线程对象
将子线程放到线程池中并运行
秒杀实际上主要解决的问题有三个:超卖、重复购买、主线程操作数据库卡顿