Redis作为单线程 为什么我用它还是出现了超卖的情况?

本文通过分析不同场景,揭示了在使用Redis作为单线程缓存时仍可能出现超卖的原因。通过逐步引入文件锁、Redis单线程写、分布式锁等方案,讨论了它们的优缺点,并提出利用Lua脚本确保锁的原子性操作以解决超卖问题。文章还探讨了自旋锁的概念,并引发思考关于并发处理和Redis在主从复制、集群环境下的锁策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实战说明

最近在一个项目营销活动中,一位同事用到了Redis来实现商品的库存管理。在压测的过程中,发现存在超卖的情况。这里总结一篇如何正确使用Redis来解决秒杀场景下,超卖的情况。

演示步骤

这里不会直接给大家说明,该怎么去实现安全、高效的分布式锁。而是通过循序渐进的方式,通过不同的方式实现锁,并发现每一种锁的缺点以及针对该类型的锁进行如何优化,最终达到实现一个高效、安全的分布锁。

第一种场景

该场景是利用Redis来存储商品数量。先获取库存,针对库存判断,如果库存大于0,则减少1,再更新Redis库存数据。大致示意图如下:

Snipaste_2021-10-25_21-19-53

  1. 当第一个请求来之后,去判断Redis的库存数量。接着给商品库存减少一,然后去更新库存数量。

  2. 当在第一个请求处理库存逻辑之间,第二个请求来了,同样的逻辑,去读Redis库存,判断库存数量接着执行减少库存操作。此时他们操作的商品其实就是同一个商品。

  3. 然后这样的逻辑,在秒杀这样大量请求来,就很容易实际商品售卖的数量远远大于商品库存数量。

public function demo1(ResponseInterface $response)
{
    $application = ApplicationContext::getContainer();
    $redisClient = $application->get(Redis::class);
    /** @var int $goodsStock 商品当前库存*/
    $goodsStock = $redisClient->get($this->goodsKey);

    if ($goodsStock > 0) {
        $redisClient->decr($this->goodsKey);
        // TODO 执行额外业务逻辑
        return $response->json(['msg' => '秒杀成功'])->withStatus(200);
    }
    return $response->json(['msg' => '秒杀失败,商品库存不足。'])->withStatus(500);
}
复制代码

问题分析:

  1. 该方式使用Redis来管理商品库存,减少对MySQL的压力。

  2. 假设此时库存只有1,第一请求在判断库存为大于0,减少库存的过程中。如果存在第二个请求来读取到了数据,发现商品库存是大于0的。两者都会执行秒杀的逻辑,然而库存只有一个,就遇到了超卖的情况。

  3. 此时,我们试想一下,如果我们只能让一个请求处理库存,其他的请求只有等待直到上一个请求结束才能去进行获取商品库存,是不是就能实现超卖呢?这就是下面几种场景提到的锁机制实现。

第二种场景

使用文件锁,第一请求来了之后,打开文件锁。处理完毕业务之后,释放当前的文件锁,接着处理下一个请求,依次循环。保证当前的所有请求,只有一个请求在处理库存。请求处理完毕之后,则释放锁。

Snipaste_2021-10-25_21-28-40

  1. 使用文件锁,来一个请求给一个文件加锁。此时另外的请求就会被阻塞,直到上一个请求成功释放锁文件,下一个请求才会执行。

  2. 所有的请求就犹如一个队列一样,前一个先入队列后一个后入队列,一次按照FIFO的顺序进行。

public function demo3(ResponseInterface $response)
{
    $fp = fopen("/tmp/lock.txt", "r+");

    try {
        if (flock($fp, LOCK_EX)) {  // 进行排它型锁定
            $application = ApplicationContext::getContainer();
            $redisClient = $application->get(Redis::class);
            /** @var int $goodsStock 商品当前库存*/
            $goodsStock = $redisClient->get($this->goodsKey);
            if ($goodsStock > 0) {
                $redisClient->decr($this->goodsKey);
                /
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值