当前位置:首页 > 问答 > 正文

Redis秒杀抢购怎么整的,详细讲解和实战案例分享

掘金社区《Redis秒杀实战:从零搭建高并发抢购系统》作者:码农小高) 来源:CSDN博客《亿级流量秒杀方案之Redis核心设计》作者:架构师老陈) 来源:GitHub开源项目《miaosha》作者:qiurunze)

好,直接开讲Redis秒杀怎么整,秒杀说白了就是一瞬间很多人来抢很少的东西,比如100个手机,几十万人来抢,核心问题就两个:第一,不能超卖,库存100个绝对不能卖出101个;第二,系统不能崩,海量请求进来数据库直接被打垮就全完了,Redis因为速度极快(内存操作)和支持原子性命令,就成了解决这俩问题的关键。

第一步:活动开始前,把商品库存预热到Redis里。

(来源:掘金社区《Redis秒杀实战》) 你不能等活动开始了才去数据库查库存,那太慢了,必须在秒杀开始之前,就把要秒杀的商品和对应的库存数量提前塞到Redis里,通常用一个简单的key-value结构存。

key: sk:1001:stock   (sk是秒杀缩写,1001是商品ID)
value: 100            (表示库存100件)

这一步叫“预热”,这样所有的库存检查都在内存里完成,速度飞快。

第二步:扣减库存是关键,必须用Redis的原子操作。

Redis秒杀抢购怎么整的,详细讲解和实战案例分享

(来源:CSDN博客《亿级流量秒杀方案》) 最核心的一步就是用户点击“立即抢购”时扣减库存,这里千万不能用“先读库存,判断大于0,再减1”这种三步走的代码,在高并发下一定会超卖,必须用Redis提供的原子性命令,一步到位。

最常用的命令是DECR或者DECRBY

// 对指定的key进行减1操作
Long remainingStock = jedis.decr("sk:1001:stock");

这个命令的妙处在于,它是原子性的,意思是Redis执行这个命令时不会被其他请求打断,多个请求过来也是排着队一个一个地减,执行完后,返回值remainingStock就是减完之后剩下的库存。

你拿到这个返回值后判断一下:

Redis秒杀抢购怎么整的,详细讲解和实战案例分享

  • remainingStock >= 0,恭喜你,扣减成功,说明你抢到了。
  • remainingStock < 0,抱歉,库存已经扣成负数了,说明你没抢到。

重要细节: 一旦发现remainingStock < 0,说明库存已经没了,但是因为你用的是decr,它会把库存减成-1,-2...这时候你得把库存“加回来”,不然数据不对,所以通常会在判断小于0之后,再执行一个incr命令把库存加回0,这样后续所有请求再来执行decr,结果都是负数,都代表抢购失败。

第三步:过滤重复请求和机器人。

(来源:GitHub项目《miaosha》) 同一个用户可能连续点击多次,或者有机器人用脚本刷,这会给系统带来巨大压力,可以在Redis里用set结构给每个用户做个“已参与”的标记。 当用户扣减库存成功后,立即执行:

sadd sk:1001:users userId

这个命令是把用户ID塞进一个集合,表示这个用户已经参与过秒杀1001商品了,在扣减库存之前,可以先查一下这个集合里有没有这个用户ID(sismember命令),如果有,直接返回“请勿重复抢购”,这样能有效减轻系统负担。

Redis秒杀抢购怎么整的,详细讲解和实战案例分享

第四步:异步处理后续事宜。

(来源:掘金社区《Redis秒杀实战》) 用户抢购资格的判断(第二步)是同步实时完成的,必须立刻告诉用户成功还是失败,但是生成订单、扣减数据库的库存等比较耗时的操作,不能阻塞这个快速的响应流程。

正确的做法是,一旦在Redis中扣减库存成功,就立即向一个消息队列(比如RabbitMQ、Kafka)发送一条“用户XXX秒杀成功”的消息,然后就直接返回前端“抢购成功”,后端有专门的消费者服务慢慢从队列里取出消息,不慌不忙地生成订单、写数据库,这就是所谓的“异步削峰”,把一瞬间的巨大压力分摊到一段时间内慢慢处理,数据库就扛得住了。

一个简单的实战代码思路:

  1. 预热: set sk:1001:stock 100

  2. 秒杀接口逻辑:

    public boolean doSecKill(String userId, String prodId) {
        // 1. 校验用户是否重复抢购
        if (jedis.sismember("sk:" + prodId + ":users", userId)) {
            return false; // 重复抢购
        }
        // 2. 原子扣减库存
        Long stock = jedis.decr("sk:" + prodId + ":stock");
        // 3. 判断库存
        if (stock < 0) {
            // 库存不足,把减掉的库存加回来
            jedis.incr("sk:" + prodId + ":stock");
            return false;
        }
        // 4. 记录用户,防止重复抢购
        jedis.sadd("sk:" + prodId + ":users", userId);
        // 5. 发送消息到队列,通知生成订单(异步)
        // mqService.sendSecKillSuccessMessage(userId, prodId);
        return true;
    }

Redis秒杀的核心就是:库存预热 + 原子扣减 + 防重校验 + 异步下单,通过这套组合拳,把最核心、最压力山大的库存判断环节放在速度极快的Redis内存中完成,再配合消息队列把瞬时高峰“熨平”,就能较好地应对秒杀场景,实际生产中还要考虑Redis集群、消息队列高可用、限流熔断等更多细节,但这四步是基石。