类秒杀报名活动库存扣减的实践
最近开发了一次类秒杀的报名活动,记录一下实现与想法。
首先,简述一下业务场景,业务需求在开发的过程中发生了多次变更,最终的业务场景是这样的,有很多的活动,每个活动允许客户在某几天的限定时间段内选择某个线下地点报名免费领取物品,为了维持活动热点,点位的库存分天放出(提前配置到每个点位某天有多少名额),若某天某点位没有消耗所有名额则累计到下一天。由于是免费领取物品而且有一定的宣传资源,在项目实际运行的时候,最高峰期有几百的qps
此外,由于业务的需求,需要实时返回与总剩余库存相关(所有点位所有天数累计)的号码。
先说一下开发时的业务逻辑,在每天报名开始前把当天的总剩余库存和当天所有点位的剩余库存(可以运算出来)预热到redis里(原子数据),在报名时先看点位是否有剩余(点位有活动总共肯定有),没有的话直接返回报名失败,有的话进入报名流程扣减总剩余库存(流程失败的话点位库存和总库存都要返还),把简单的必须操作即刻进行,复杂的非实时性的放到消息队列里延迟执行。
// 点位剩余库存
long enrollNumLongAndDecrement = redisEnrollNumLong.getAndDecrement(); // 有库存就报名 if (enrollNumLongAndDecrement >0) {
// 总剩余库存 long activityNumLongAndDecrement = redisActivityNumLong.getAndDecrement(); try { // 一些必须的消耗少的操作 int passnum = publicActivityinfo.getJoinnub() - (int) (long) activityNumLongAndDecrement + 1; pojo.setReceivenum(passnum); publicEnrollMapper.insert(pojo); // 消息队列消息准备(某些消耗资源多需要延时后续的操作) map.put("pojo", pojo);// 向消息队列发送消息 rabbitMqActivityProducer.sendActivityInfo(JSONUtils.beanToJson(map)); } catch (Exception e) {
// 报名失败返还库存(点位剩余,活动剩余) redisEnrollNumLong.getAndIncrement(); redisActivityNumLong.getAndIncrement(); throw new RuntimeException(e); } } else { // 当天该活动点报名人数已满 return ResultInfo.fail(4, "当天该活动点报名人数已满"); }