zoukankan      html  css  js  c++  java
  • 秒杀代码,非lua脚本。

    需求:举办一场活动(activity),可以设置多个场次(设置的场次,即session,每隔一段时间举行一场)进行秒杀,每个场次可以秒杀多个奖品(prize)。

    方案:redis存储奖品的库存,使用incr命令扣库存, redis扣成功的情况下,再mysql扣库存。

     说明:设置redis有效期>=场次时间(秒杀时间)非常重要,不然redis层面无法解决redis扣库存超发的问题,只能通过mysql解决。

    简化的代码:

    // 预扣减redis奖品库存(设置的有效期 >= 一个场次的有效期)。
    // 如果有效期小于场次的有效期,极端情况下会出现:
    // (1)redis设置的有效期失效,导致部分奖品发不出去,原因:redis incr命令 扣减库存时,在key失效的情况下,把key的值默认设置为0。
    // (2)redis回滚库存会出现多加库存的情况,当然可以通过后续的mysql扣库存防止超发的情况。
    Boolean isLeftStock = this.decreasePrizeStock(activityId,sessionId,prizeId);
    if(!isLeftStock){
    log.info("prizeId={},没有奖品了",prizeId);
    throw new BusinessException(GameErrorCodeEnum.LOTTY_ERROR.getErrorCode(),"没有抽中奖品");
    }

    try {
    // mysql扣减库存
       // 代码略 主要操作 UPDATE activity_prize SET left_stock = left_stock - 1,version = version + 1 WHERE left_stock > 0 AND id = #{id, jdbcType=INTEGER}

    }catch (Exception ex){
    log.error("抽奖异常");
    // 没抽到奖,则回滚redis的奖品剩余库存
    this.increasePrizeStock(activityId,sessionId,prizeId);
    throw ex;
    }

    主要方法:
    /**
    * @desc : 预扣减redis奖品库存(有效期1分钟)
    * @author : 毛会懂
    * @create: 2021/8/26 10:53:00
    **/
    private Boolean decreasePrizeStock(Integer activityId,Integer sessionId,Integer prizeId){
    Integer leftStock = this.getPrizeLeftStock(activityId,sessionId,prizeId);
    if(leftStock <= 0){
    log.info("没有库存了,prizeId={}",prizeId);
    return Boolean.FALSE;
    }
    String prizeCountKey = RedisKeyManagement.getKey(RedisKeyManagement.ACTIVITY_SESSION_PRIZE_INFO, Arrays.asList(activityId.toString(),sessionId.toString(),prizeId.toString()));
    leftStock = (int)redisService.decrease(prizeCountKey,1);

    if(leftStock < 0){
    // 回滚多扣减的库存 情况1:最后1个库存,多个user同时扣减 情况2:redis失效,redis的值被初始化为0,永久有效
    log.error("回滚多扣减的库存,prizeId={}",prizeId);
    this.increasePrizeStock(activityId,sessionId,prizeId);
    return Boolean.FALSE;
    }
    return Boolean.TRUE;
    }

    /**
    * @desc : 回滚预扣减的redis奖品库存
    * @author : 毛会懂
    * @create: 2021/8/26 10:53:00
    **/
    private Boolean increasePrizeStock(Integer activityId,Integer sessionId,Integer prizeId){
    String prizeCountKey = RedisKeyManagement.getKey(RedisKeyManagement.ACTIVITY_SESSION_PRIZE_INFO, Arrays.asList(activityId.toString(),sessionId.toString(),prizeId.toString()));
    Long expire = redisDubboService.getExpire(prizeCountKey);
    if(expire == null || expire <= 0){
    // 避免 redis 扣库存,redis的值被初始化为0,永久有效的问题
    redisService.delete(prizeCountKey);
    return Boolean.TRUE;
    }
    redisDubboService.increment(prizeCountKey,1);
    return Boolean.TRUE;
    }

    /**
    * @desc : 获取某奖品的库存
    * @author : 毛会懂
    * @create: 2021/8/27 14:58:00
    **/
    private Integer getPrizeLeftStock(Integer activityId,Integer sessionId,Integer prizeId){
    String prizeCountKey = RedisKeyManagement.getKey(RedisKeyManagement.ACTIVITY_SESSION_PRIZE_INFO, Arrays.asList(activityId.toString(),sessionId.toString(),prizeId.toString()));
    Integer leftStock = (Integer)redisDubboService.get(prizeCountKey);
    if(leftStock == null){
    synchronized (this){
    leftStock = (Integer) redisDubboService.get(prizeCountKey);
    // 这里最好加分布式锁(在多个实例下,会出现以下代码同时执行的情况,有可能会出现读取的leftStock不正确,比如,两个实例分别读到的是20,19,set时却是先set 19,再set 20)
    if(leftStock == null){
    // 读mysql表中的剩余库存
    ActivityPrizeDO prizeDO = prizeService.getById(prizeId);
    leftStock = prizeDO.getLeftStock();
    redisService.set(prizeCountKey,leftStock,Constant.ONE_MINUTE);
    }
    }
    }
    return leftStock;
    }
  • 相关阅读:
    python_tkinter弹出对话框2
    python_tkinter弹出对话框1
    python生成图片二维码(利用pillow)
    nginx配置ssl证书流程及常见问题
    Django app安装,配置mysql,时区,模板,静态文件,媒体,admin
    使用Git Flow规范!
    python快速生成验证码
    json&pickle模块
    sys模块
    常用模块
  • 原文地址:https://www.cnblogs.com/maohuidong/p/15202275.html
Copyright © 2011-2022 走看看