总体介绍
为了提升下单速度,我们将订单数据存入到Redis缓存中,以及一些中间形成的缓存信息都保存在redis中,不去访问mysql。
只有三次访问mysql
- 查询到用户订单已支付,将redis里面的订单信息删掉,将订单信息更新插入到mysql当中去
- 检测到秒杀商品卖完了,将秒杀商品数量和信息更新到mysql数据库的秒杀商品信息表中。
- 超时订单是某个秒杀商品的最后一个,发现订单延迟时已经在redis中删除了商品信息,需要从mysql取出商品信息。
秒杀整体过程
- 商品数据定时压入redis缓存
- 客户开始抢购,用户的请求经过openResty(Nginx)到redis中获取秒杀商品(到达秒杀列表页)
- 用户如果想抢某个商品,也需要经过openResty到达秒杀详情页(从redis获取数据生成秒杀商品详情页)
- 用户进行秒杀抢购,但抢购之前需要先登录,登陆前请求还需要先到达openresty(判断当前秒杀商品还有没有库存,如果没有库存就直接拦截了,也不用调用后面的微服务了减轻压力)
- openResty通过之后判断用户是否登录(没登录就先去oauth2.0进行登录)
- 登录成功访问秒杀微服务,但还不能直接抢单(因为抢单要检查的内容很多,很费时,高并发的时候就都堵在那里了)。
- 排队下单
- 如何防止重复排队(有人下两次单怎么办)
- 多线程生成订单
-
多线程生成订单
下单的方法用@async注解修饰
一旦订单创建成功了就说明抢到了
这里以队列的形式控制超卖来说明 如果能从商品数量队列取出东西来说明没有超卖,可以生成订单
- 清理用户的排队次数信息
- 从redis获取商品信息
- 创建秒杀订单
- 订单存到redis中
- redis中的对应商品库存-1
- 判断当前库存是否为0
- 如果为0将信息同步到mysql当中
- 不为0将商品信息放回redis中
- 更新抢单状态(抢单状态由一个redis hash保存)
-
商品数据定时压入redis缓存
首先进行秒杀商品的入库审核,审核通过存入mysql
- 秒杀服务定时将秒杀商品转入redis(定时载入因为中途可能有商品上架,所以不能一次性转入)
- 使用@Scheduled注解(cron = “0/5 ?”) 实现反复执行,里面cron表达式表示从第0秒开始,每5秒执行一次,将数据从mysql里面查询出来存入redis缓存当中
- 存入的商品,符合以下几个条件
- 判断审核状态为通过,
- 参与秒杀时间在计算出的秒杀时间段里面
- 原来不在redis中
- 库存个数>0
- 存入的商品,符合以下几个条件
- 有5个时间段,只载入参与秒杀时间在这5个时间段的数据,载入的形式为hash形式,命名空间为参与秒杀开始时间,key为秒杀商品id,value为秒杀商品信息
秒杀订单支付
- 排队完成后进入秒杀支付页面(这里跳转和调用支付系统是前端实现的),支付页面自动调用支付系统
- 从微信支付获取二维码数据,并在页面生成二维码
- 用户扫码支付后,微信支付服务器会进行验证,并返回调用前预留的回调地址(用户付款界面)。这是携带了支付状态信息
- 收到回调信息后会将该信息发送到秒杀系统的消息队列中
- 秒杀微服务监听支付回调信息消息队列
- 监听到支付状态成功,
- redis中根据用户名删除订单,
- 将订单信息完善,新增交易流水号等信息,将订单信息保存在mysql中
- 删除用户重复排队信息标识和排队标识
- 支付状态失败,
- 关闭微信支付
- redis中删除订单
- 回滚:
- 在对应商品的防止超卖队列加一个占位对象
- 回滚redis中的对应商品库存,如果这时候查不到对应商品了,说明出问题的是最后一个,需要从数据库里面查出来,并进行一个自增
- 删除用户排队信息和重复排队标识
- 监听到支付状态成功,
- 监听延时队列
- 监听到内容进行查询,查不到订单说明已经支付成功了,订单已经删除了
- 能监听到内容,说明订单超时了,应当按照支付失败处理。
排队下单
- 排队信息封装,pojo SeckillStatus
- 包括用户名,创建时间,订单状态,订单金额,订单号等字段
- 采用redis的list队列,用户点击抢购的时候,生成用户抢购信息(seckillStatus)并存入到redis队列当中
- 多线程生成订单时从队列依次获取SeckillStatus
- 多线程生成订单时要修改订单状态
- 支付时候,要清理订单状态
- 排队过程中可以查询当前订单状态:用username获取SeckillStatus,查看其中的status信息
如何防止重复排队
redis维护一个hash类型的表,表示用户抢单次数
key是用户名,value是一个自增值,每次都自增1
用自增值不会产生并发问题,因为redis是单线程的。
在生成排队信息之前,查询当前用户抢单次数,如果大于1,就说明存在重复抢单,抛出不能重复抢单异常
如何解决并发超卖问题
在日常订单处理中使用mysql行锁来解决订单超卖
但秒杀这种高并发情况下再使用mysql行锁会比较低效,秒杀过程中应当降低mysql的压力,这里设计了两种方案:
- 使用list,给每件商品都创建一个队列(队列key唯一,应该设为商品id),创建队列时往队列里面加入商品数量个占位对象,用户抢单时依次从队列取占位对象,能取到就说明还有库存
- 使用hash和自增操作,创建一个秒杀订单数量hash表,key是秒杀商品订单,value是商品数量,每次抢单后自增-1,如果返回值>=0,则可以下单,否则说明已经卖完
两种方案分析,使用自增好一些,如果使用list,一方面需要每次迭代添加,另一方面也需要再redis生成大量的队列。