目录
秒杀
1 秒杀业务分析
1.1 需求分析
-
所谓“秒杀”,就是网络卖家发布一些超低价格的商品,所有买家在同一时间网上抢购的一种销售方式。通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动。由于商品价格低廉,往往一上架就被抢购一空,有时只用一秒钟。
-
秒杀商品通常有两种限制:库存限制、时间限制。
-
需求:
-
(1)录入秒杀商品数据,主要包括:商品标题、原价、秒杀价、商品图片、介绍、秒杀时段等信息
-
(2)秒杀频道首页列出秒杀商品(进行中的)点击秒杀商品图片跳转到秒杀商品详细页。
-
(3)商品详细页显示秒杀商品信息,点击立即抢购实现秒杀下单,下单时扣减库存。当库存为0或不在活动期范围内时无法秒杀。
-
(4)秒杀下单成功,直接跳转到支付页面(扫码),支付成功,跳转到成功页,填写收货地址、电话、收件人等信息,完成订单。
-
(5)当用户秒杀下单5分钟内未支付,取消预订单,调用支付的关闭订单接口,恢复库存。
-
1.2 表结构说明
秒杀商品信息表
CREATE TABLE `tb_seckill_goods` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`goods_id` bigint(20) DEFAULT NULL COMMENT 'spu ID',
`item_id` bigint(20) DEFAULT NULL COMMENT 'sku ID',
`title` varchar(100) DEFAULT NULL COMMENT '标题',
`small_pic` varchar(150) DEFAULT NULL COMMENT '商品图片',
`price` decimal(10,2) DEFAULT NULL COMMENT '原价格',
`cost_price` decimal(10,2) DEFAULT NULL COMMENT '秒杀价格',
`create_time` datetime DEFAULT NULL COMMENT '添加日期',
`check_time` datetime DEFAULT NULL COMMENT '审核日期',
`status` char(1) DEFAULT NULL COMMENT '审核状态,0未审核,1审核通过,2审核不通过',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`num` int(11) DEFAULT NULL COMMENT '秒杀商品数',
`stock_count` int(11) DEFAULT NULL COMMENT '剩余库存数',
`introduction` varchar(2000) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
秒杀订单表
CREATE TABLE `tb_seckill_order` (
`id` bigint(20) NOT NULL COMMENT '主键',
`seckill_id` bigint(20) DEFAULT NULL COMMENT '秒杀商品ID',
`money` decimal(10,2) DEFAULT NULL COMMENT '支付金额',
`user_id` varchar(50) DEFAULT NULL COMMENT '用户',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`pay_time` datetime DEFAULT NULL COMMENT '支付时间',
`status` char(1) DEFAULT NULL COMMENT '状态,0未支付,1已支付',
`receiver_address` varchar(200) DEFAULT NULL COMMENT '收货人地址',
`receiver_mobile` varchar(20) DEFAULT NULL COMMENT '收货人电话',
`receiver` varchar(20) DEFAULT NULL COMMENT '收货人',
`transaction_id` varchar(30) DEFAULT NULL COMMENT '交易流水',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2 秒杀商品压入缓存
秒杀商品压入缓存
-
我们这里秒杀商品列表和秒杀商品详情都是从Redis中取出来的,所以我们首先要将符合参与秒杀的商品定时查询出来,并将数据存入到Redis缓存中。
-
数据存储类型我们可以选择Hash类型。
-
秒杀分页列表这里可以通过获取redisTemplate.boundHashOps(key).values()获取结果数据。
-
秒杀商品详情,可以通过redisTemplate.boundHashOps(key).get(key)获取详情。
2.1 秒杀服务工程
-
我们将商品数据压入到Reids缓存,可以在秒杀工程的服务工程中完成,可以按照如下步骤实现:
-
1.查询活动没结束的所有秒杀商品
-
1)状态必须为审核通过 status=1
-
2)商品库存个数>0
-
3)活动没有结束 endTime>=now()
-
4)在Redis中没有该商品的缓存
-
5)执行查询获取对应的结果集
-
-
2.将活动没有结束的秒杀商品入库
-
2.2 定时任务
-
我们采用Spring的定时任务定时将符合参与秒杀的商品查询出来再存入到Redis缓存,所以这里需要使用到定时任务。
-
这里我们了解下定时任务相关的配置,配置步骤如下:
-
1)在定时任务类的指定方法上加上@Scheduled开启定时任务
-
2)定时任务表达式:使用cron属性来配置定时任务执行时间
-
2.3 秒杀商品压入缓存实现
-
2.3.1 数据检索条件分析
-
按照2.1中的几个步骤实现将秒杀商品从数据库中查询出来,并存入到Redis缓存
-
1.查询活动没结束的所有秒杀商品
-
1)计算秒杀时间段
-
2)状态必须为审核通过 status=1
-
3)商品库存个数>0
-
4)活动没有结束 endTime>=now()
-
5)在Redis中没有该商品的缓存
-
6)执行查询获取对应的结果集
-
-
2.将活动没有结束的秒杀商品入库
-
-
-
2.3.2 时间菜单分析
-
我们将商品数据从数据库中查询出来,并存入Redis缓存,但页面每次显示的时候,只显示当前正在秒杀以及往后延时2个小时、4个小时、6个小时、8个小时的秒杀商品数据。我们要做的第一个事是计算出秒杀时间菜单,这个菜单是从后台获取的。
-
而现实的菜单只需要计算出当前时间在哪个时间段范围,该时间段范围就属于正在秒杀的时间段,而后面即将开始的秒杀时间段的计算也就出来了,可以在当前时间段基础之上+2小时、+4小时、+6小时、+8小时。
-
-
2.3.3 查询秒杀商品导入Reids
-
我们可以写个定时任务,查询从当前时间开始,往后延续4个时间菜单间隔,也就是一共只查询5个时间段抢购商品数据,并压入缓存
-
3 秒杀频道页![](https://img-blog.csdnimg.cn/20210824170715146.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNjI5Njg3,size_16,color_FFFFFF,t_70)
概述
-
秒杀频道首页,显示正在秒杀的和未开始秒杀的商品(已经开始或者还没开始,未结束的秒杀商品)
3.1 秒杀时间菜单
-
时间菜单需要根据当前时间动态加载,时间菜单的计算上面功能中已经实现,在DateUtil工具包中。我们只需要将时间菜单获取,然后响应到页面,页面根据对应的数据显示即可。
3.2 秒杀频道页
-
秒杀频道页是指将对应时区的秒杀商品从Reids缓存中查询出来,并到页面显示。对应时区秒杀商品存储的时候以Hash类型进行了存储,key=SeckillGoods_2019010112,value=每个商品详情。
-
每次用户在前端点击对应时间菜单的时候,可以将时间菜单的开始时间以yyyyMMddHH格式提交到后台,后台根据时间格式查询出对应时区秒杀商品信息。
4 秒杀详情页
概述
-
通过秒杀频道页点击请购按钮,会跳转到商品秒杀详情页,秒杀详情页需要根据商品ID查询商品详情,我们可以在频道页点击秒杀抢购的时候将ID一起传到后台,然后根据ID去Redis中查询详情信息。
5 下单实现
用户下单,从控制层->Service层->Dao层
用户下单,为了提升下单速度,我们将订单数据存入到Redis缓存中,如果用户支付了,则将Reids缓存中的订单存入到MySQL中,并清空Redis缓存中的订单。
6 多线程抢单![](https://img-blog.csdnimg.cn/20210824170846570.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNjI5Njg3,size_16,color_FFFFFF,t_70)
6.1 实现思路分析
-
下订单这里,我们一般采用多线程下单,但多线程中我们又需要保证用户抢单的公平性,也就是先抢先下单。我们可以这样实现,用户进入秒杀抢单,如果用户符合抢单资格,只需要记录用户抢单数据,存入队列,多线程从队列中进行消费即可,存入队列采用左压,多线程下单采用右取的方式。
6.2 异步实现
-
要想使用Spring的异步操作,需要先开启异步操作,用
@EnableAsync
注解开启,然后在对应的异步方法上添加注解@Async
即可。
6.3 多线程抢单![](https://img-blog.csdnimg.cn/20210824170910128.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNjI5Njg3,size_16,color_FFFFFF,t_70)
-
用户每次下单的时候,我们都让他们先进行排队,然后采用多线程的方式创建订单,排队我们可以采用Redis的队列实现,多线程下单我们可以采用Spring的异步实现。
-
6.3.1 多线程下单
-
6.3.2 排队下单
-
6.3.2.1 排队信息封装
-
用户每次下单的时候,我们可以创建一个队列进行排队,然后采用多线程的方式创建订单,排队我们可以采用Redis的队列实现。 排队信息中需要有用户抢单的商品信息,主要包含商品ID,商品抢购时间段,用户登录名。我们可以设计个javabean
-
-
6.3.2.2 排队实现
-
我们可以将秒杀抢单信息存入到Redis中,这里采用List方式存储,List本身是一个队列,用户点击抢购的时候,就将用户抢购信息存入到Redis中
-
多线程每次从队列中获取数据,分别获取用户名和订单商品编号以及商品秒杀时间段,进行下单操作
-
-
-
6.3.3 下单状态查询
-
按照上面的流程,虽然可以实现用户下单异步操作,但是并不能确定下单是否成功,所以我们需要做一个页面判断,每过1秒钟查询一次下单状态,多线程下单的时候,需要修改抢单状态,支付的时候,清理抢单状态。
-
6.3.3.1 下单更新抢单状态
-
用户每次点击抢购的时候,如果排队成功,则将用户抢购状态存储到Redis中,多线程抢单的时候,如果抢单成功,则更新抢单状态。
-
-
6.3.3.2 后台查询抢单状态
-