复盘一下做的项目中秒杀模块的核心要求信息

  1. server-task

    开启两个定时任务

    1. 每天凌晨,往redis里面存的热点数据
    2. 每天晚上,删除redis里面存入的预热数据
  2. server-activity

    1. 两个方法,findAll(查询所有热点数据),findBySkuID(通过skuID查询某个热点数据)

    2. 当我们接收到server-task凌晨发送过来的数据的时候我们就需要往redis里面存储热点数据,此时我们需要根据 status (0->已经停止秒杀,1->正在进行秒杀)和 start-time 和今日的时间对比,

      找出今日符合要求的热点数据集合,然后把这些数据通过以哈希对象的数据类型存储到redis中,

      redisTemplate.boundHashOps("seckill_goods").put("sku_id", seckillGoods);
      
    3. 每一个sku对应的status (0->禁止秒杀 1->正在秒杀) 状态是通过redis的发布订阅模式实现的(因为考虑到以后这个server-activity模块可能会集群部署,为了让每一个秒杀模块都可以及时又高效的获取每个sku是否还可以继续进行秒杀,我是使用了一个本地缓存,currentHashMap来保存每一个sku的status,Key就是SkuId,Value就是状态,0或者1),定义了一个类把这个作为一个静态变量可以随时的使用

      public final static ConcurrentHashMap<String,Object> cacheMap=new ConcurrentHashMap();
      
    4. 如何实现redis的发布订阅订阅模式呢?

      1. 定义一个redis Channel的配置类,里面要去注入订阅主题、消息监听适配器、创建接收消息对象(自己实现一个接收redis发布来的消息,就是我上面说的封装实现一个ConcurrentHashMap的类)、注入操作数据的template
  3. Web-ALL

    • 首先,要实现秒杀肯定要现有一个秒杀页面,也就是前端页面要获取我们的热点数据列表,这个接口上面已经说过了,我们可以通过Feign远程调用,(思路时:在定义一个service-activity-clientm模块,创建接口,对接service-activity里面的方法),在web-all的控制器里面注入这个对象直接调用然后返回给前端页面即可

      redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values()
      
    • 实现了秒杀列表展示页面,就需要实现展示某个秒杀商品的详情的接口,此时前端就需要传递给后端商品的id,即SkuID,此时通过feign远程调用秒杀管理模块,去redis里面通过skuid查询即可

      redisTemplate.boundHashOps("seckill_goods").get(SkuID);
      
  4. 为了防止用户在非法的时间去进行商品的秒杀,为此当用户在秒杀商品详情页准备点击抢购的是时候我们后端(先判断用户抢购时的时间是否在秒杀时间的范围内,如果在的话才生成下单码,否则返回给用户非法访问)需要先生成一个下单码(根据不通的业务,我这里是根据用户ID通过MD5加密算法生成了一个下单码,也可以有其它的方式生成下单码,比如用其它的加密算法或者根据用户id + SKUID一起生成下单码)给前端,之后用户抢购的时候我们就可以根据携带过来的下单码来判断能否给用户生成订单;

  5. 用户要想秒杀,最主要的还是要拥有下单资格,仅仅获取了下单码还不能直接下单,在下单之前还需要再次判断是否有下单资格

    如何判断某个用户是否有下单资格呢?

    1. 先判断用户的下单码是否正确
    2. 判断这个秒杀SKU的status是否为1
    3. 如果以上两者有一个不满足直接返回给用户不合法的请求
    4. 只有以上两者都满足的情况下这个用户才有下单的资格,此时我通过rabbitMQ消息队列,把(生成一个实体类,里面有userId,skuId)放入到队列当中等待被处理
  6. 接下来我们就需要实现一个 下单的监听器,监听MQ发来的UserRecode,实现预下单

    如果监听到又发来的消息,我们还需要进行多重的判断才能判断该用户是否可以预下单

    1. 还是要先判断被秒杀Sku的status是否为1,为啥还要判断呢?因为秒杀属于高并发的场景了,MQ里面可能每秒有几十W个消息,随意商品很肯能一刹那就被秒杀完,所以我们首先还是要先判断一下被秒杀商品的状态是否可用,如果不可用的话就可以及时的返回给前端信息进行处理了,所以该判断是必要的

    2. 我这个业务是一个用户抢购了一个必须先完成下单支付才可以继续秒杀,为了防止用户在未支付的时候先多次抢购商品,我们使用了redis的互斥锁的方法限制用户在未支付这笔订单的时候只能抢购一个商品,

      boolean isExist = redisTemplate.opsForValue().setIfAbsent(RedisConst.SECKILL_USER + userId, skuId, RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
      

      如果返回false说明用户已经下过一个订单但未支付,此时我们直接返回给前端信息,不会在生成此订单了

    3. 如果前两条都符合,接下来也是最重要的验证了,我们还需要验证该秒杀商品的库存是否还充足,之前再存入预热商品的时候我是将该秒杀商品的粗存通过循环一个一个的存入redis的List对象类型中,所以此时判断商品库存的时候我们直接根据SkuID到List里面去取,看能否取出来对象,如果取出的对象为空,说明库存不足,此时直接返回给前端库存不足的信息即可

      //存储商品库存的核心代码           
      for (Integer i = 0; i < seckillGoods.getStockCount(); i++) {
      
      String key = RedisConst.SECKILL_STOCK_PREFIX + seckillGoods.getSkuId();
      redisTemplate.opsForList().leftPush(key, seckillGoods.getSkuId().toString());
      
      } 
      
//取出库存的核心代码
String goodsId = (String)redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).rightPop();

//此处有一个细节,如果我们取出的对象为空的话,我们还需要通过redis去发布  该sku的库存不足的TOPIC的信号给订阅者,此时订阅者就会把这个sku对应的status设置为0,表示禁止秒杀	

​ 4. 前三个条件都满足的情况下我们就可以往redis里面存入一条某用户的订单记录了,并且更新redis和mysql里面的库存了

在往redis里面存的时候,我选择的数据类型还是hash,key为 “seckill:orders”,field为userId,value为我自定的一个orderRecode —>

public class OrderRecode implements Serializable {

	private static final long serialVersionUID = 1L;

	private String userId;

	private SeckillGoods seckillGoods;

	private Integer num;

	private String orderStr;
}

​ 5. 在更新库存信息的时候,还是通过skuID,去redis里面找到List直接.size()即可,实际开发中每当粗存数量少一个就去更新粗存数据的话可能会给mysql的服务器带来较大的压力,所以我们可以判断一下每当 count %2 ==0 的时候采取更新 ,或者也可以有其它的判断逻辑

  1. 当我们点击抢购的时候,前端会使用一个定时器通过传来skuid,userid,不断的获取抢购的状态

    • 排队中
    • 抢购成功,可以去下订单了
    • 抢购成功,并且已经生成了订单,此时就可以去我的订单里面查看自己的订单
    • 抢购失败,非法访问
  2. 当我们抢购成功的时候就可以点击去下单了,此时前端就需要向后端请求下单页面所需要数据(用户的地址列表以及orderInfo(里面包含了orderDetail集合,然而在我们这个秒杀的业务当中订单明细只有一条记录),订单总金额 这三个信息存入到Map中返回给前端);但是注意,此时我们还需要删除redis里面的seckill:order:userId,订单记录的信息,同时存储新的下单成功的记录到redis中以

    seckill:order:user最为key,UserID最为value即可存入到redis中

  3. 最后就是用户点击下单到支付页面,我们后端需要做的就是保存订单了,并且此时我们需要删除存入到redis里面相关的缓存数据了, 比如 ( seckill:user: userId seckill:order:userId,orderRecode 这些缓存记录)

  4. 最后,我们还需要写一个每天定时关闭的定时任务,用来删除过期的秒杀数据同时删除那些已经下单成功的旧的订单信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C_x_330

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值