• 秒杀代码,非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;
    }
  • 相关阅读:
    poj 2425 AChessGame(博弈)
    poj2975 Nim 胜利的方案数
    hdu 5724 SG+状态压缩
    hdu 5274 Dylans loves tree(LCA + 线段树)
    hdu 5266 pog loves szh III(lca + 线段树)
    hdu 4031 attack 线段树区间更新
    51 nod 1188 最大公约数之和 V2
    51nod 1040 最大公约数之和(欧拉函数)
    51nod 1035:最长的循环节
    Nim游戏(组合游戏Combinatorial Games)
  • 原文地址:https://www.cnblogs.com/maohuidong/p/15202275.html
Copyright © 2020-2023  润新知