redis秒杀之lua脚本

Lua 脚本核心原理:

​1.单线程模型:Redis 使用单线程处理命令,所有命令按顺序执行。Lua 脚本会被视为一个整体任务,执行期间不会被其他命令中断。

​2.原子性保证:将库存检查、扣减、订单记录等多个操作放在一个脚本中,会连续执行,中间不会有其他客户端操作插入。保证了脚本的原子性。

​3.无需锁机制:由于 Redis 的单线程特性,Lua 脚本天然避免了并发冲突,无需额外加锁。

实现流程:

1.首先库存预热:活动开始前将对应商品的库存存redis;

2.然后实现lua脚本(可以将库存检查、扣减、订单记录等多个操作放在一个脚本中)。

下述的字段备注

-- 秒杀Lua脚本
-- KEYS1: 库存key
-- KEYS2: 订单集合key(用于去重)
-- ARGV1: 用户ID
-- ARGV2: 当前时间戳(可选)

-- 返回: 1-秒杀成功; 0-秒杀失败

php具体实现如下:

php 复制代码
class SeckillService
{
    private $redis;
    
    public function __construct()
    {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    
    /**
     * 执行秒杀
     * @param int $productId 商品ID
     * @param int $userId 用户ID
     * @return array ['success'=>bool, 'msg'=>string]
     */
    public function seckill($productId, $userId)
    {
        // 定义Redis key
        $stockKey = "product:{$productId}:stock";
        $orderKey = "product:{$productId}:orders";
        
        // Lua脚本
        $luaScript = <<<LUA
local stockKey = KEYS[1]
local orderKey = KEYS[2]
local userId = ARGV[1]

local stock = tonumber(redis.call('GET', stockKey) or 0)
if stock <= 0 then
    return 0
end

if redis.call('SISMEMBER', orderKey, userId) == 1 then
    return 0
end

redis.call('DECR', stockKey)
redis.call('SADD', orderKey, userId)
return 1
LUA;
        
        try {
            // 执行Lua脚本
            $result = $this->redis->eval(
                $luaScript, 
                [$stockKey, $orderKey, $userId], 
                2 // KEYS的数量
            );
            
            if ($result == 1) {
                // 秒杀成功,异步创建实际订单到数据库
                $this->createOrderAsync($productId, $userId);
                return ['success' => true, 'msg' => '秒杀成功'];
            }
            
            return ['success' => false, 'msg' => '秒杀失败,可能已售罄或您已参与过'];
            
        } catch (Exception $e) {
            // 记录日志
            error_log("秒杀异常: " . $e->getMessage());
            return ['success' => false, 'msg' => '系统繁忙,请稍后再试'];
        }
    }
    
    /**
     * 异步创建订单(实际项目中可用消息队列实现)
     */
    private function createOrderAsync($productId, $userId)
    {
        // 这里可以写入消息队列或直接处理
        // 示例: 写入文件日志,实际项目中不要这样做
        $orderData = json_encode([
            'product_id' => $productId,
            'user_id' => $userId,
            'time' => time()
        ]);
        file_put_contents('orders.log', $orderData . "\n", FILE_APPEND);
        
        // 实际项目建议使用消息队列如RabbitMQ、Kafka等
        // $this->mq->publish('order_queue', $orderData);
    }
    
    /**
     * 初始化商品库存(仅在活动前调用)
     */
    public function initStock($productId, $quantity)
    {
        $stockKey = "product:{$productId}:stock";
        return $this->redis->set($stockKey, $quantity);
    }
}
相关推荐
CLOUD ACE11 小时前
谷歌云代理|零售商Target 如何利用 Spanner Graph 提升零售发现体验并将数据库维护成本降低 50%
数据库·零售
少晓年11 小时前
从 MySQL 迁移到人大金仓 KingbaseES:完整指南与实践
数据库·mysql
SelectDB12 小时前
Apache Doris AI RAG 实战:从基础 RAG 到知识图谱增强的技术能力与选型
数据库
SelectDB12 小时前
电商企业 PostgreSQL 迁移 Apache Doris:80TB 分析数据统一平台技术能力与实践
数据库
SelectDB12 小时前
阶跃星辰 Agent 可观测:Apache Doris / SelectDB 的技术能力与实践
数据库
SelectDB12 小时前
ApacheDoris Iceberg V3 湖仓 DML:Apache Doris / SelectDB 的技术能力与实践
数据库
SelectDB12 小时前
ApacheDoris Python UDF:SQL 调用 Python 的技术能力、选型对比与实践
数据库
Crazy________13 小时前
Redis03:持久化存储,大key分析,主从复制及哨兵模式
数据库·redis·容器
自由能燃气设备13 小时前
燃气容积式热水器厂家选购指南:2026年商用热水设备采购避坑手册
大数据·数据库·数据仓库·人工智能
oradh13 小时前
Oracle数据文件的大小和数量的限制总结
数据库·oracle·数据文件大小限制·数据文件数量限制