• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

秒杀代码,非lua脚本。

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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

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

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

 

简化的代码:

// 预扣减redis奖品库存(设置的有效期 >= 一个场次的有效期)。
// 如果有效期小于场次的有效期,极端情况下会出现:
// (1)redis设置的有效期失效,导致部分奖品发不出去,原因:redis incr命令 扣减库存时,在key失效的情况下,把key的值默认设置为0。
// (2)redis回滚库存会出现多加库存的情况,当然可以通过后续的mysql扣库存防止超发的情况。
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;
}

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
基于Lua语言的触动精灵脚本开发发布时间:2022-07-22
下一篇:
Lua中的元表Metatable ——table相关的使用发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap