秒杀系统 php + redis 库存与数据库如何保持一致?

问题

一个很常见的商品秒杀场景,为了防止商品超卖使用了 redis 原子自减方式做库存判断

流程类似这样

下单 => 扣redis库存成功 => 创建订单 => 支付成功, 更新数据库

关键代码如下

    $num = 10;
    if( $redis->decrBy('goods_id', $num) > 0) { // 库存足够
        // create_order 创建订单成功返回 true, 失败返回 false
        if( !create_order() ) {
            // 创建订单失败,回退库存
            $redis->incrBy('goods_id', num);
        }
    } else { // 库存不足,回退库存
           $redis->incrBy('goods_id', num);
    }

判断redis 里缓存的库存计数器, 如果库存充足 > 0的情况下进入到创建订单逻辑

目前这种方式可以解决超卖,但是并发人多的时候会出现少卖的情况。怀疑是redis自减成功,但是

数据库创建订单失败【?】,正常创建失败会回退库存的,但是实际就是会产生不能回退库存的情况

如何才能保证redis库存与数据库库存一致

如果自减库存无法保证一致性,可以后台做库存与实际销量做同步,但是总感觉这样子有问题,有什么好的解决方法吗?

阅读 3.7k
2 个回答

看你的伪代码,MySQL 操作和 Redis 操作不是原子性的,用了 Redis ,但没完全用

$redis->decrBy('goods_id', $num) 如果有两个进程在极短的时间内扣减成功,create_order() 里面逻辑处理得稍慢一点,在 MySQL 上就会有并发问题,如果 MySQL 更新库存没有加锁就有可能出现你说得问题

建议 Redis 库存扣减成功后,扔一个队列,然后通过队列去扣减 MySQL 库存

有两种思路;
一:同步思路,redis自减完,数据库创建订单,创建失败的异常总能补货到吧?在异常分支里打日志,并redis自增(该订单的商品数量)回退库存

二:异步思路,redis自减完,生成订单ID,然后发到延迟队列,比如30分钟后的延迟队列,30分钟后异步处理该订单,如果没付款就关闭,回退库存;支付了就啥事不做

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏