您的位置:

Redis乐观锁详解

一、乐观锁概述

乐观锁是一种并发控制机制,它假定在数据变更时不会有冲突发生,因此不会像悲观锁一样在操作时先加锁。

在Redis中,乐观锁常用于多线程、多用户同时操作同一个数据的场景,例如秒杀、抢购、投票等。

二、Redis实现乐观锁的常用方法

1. WATCH/MULTI/EXEC

Redis通过 WATCH/MULTI/EXEC 指令实现乐观锁。这种方式基于事务,所以必须使用 Redis 2.0.0 版本以上才能使用。它的具体实现步骤如下:

// PHP代码示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = 'goods_1001';
$quantity = 10;

$redis->watch($key); // 监听商品库存
$currentQuantity = $redis->get($key);
if ($currentQuantity < $quantity) { // 判断库存是否充足
    $redis->unwatch(); // 取消监听
    echo '库存不足';
} else {
    $redis->multi(); // 开启事务
    $redis->decrby($key, $quantity); // 减少商品库存
    $redis->exec(); // 提交事务
    echo '扣减成功';
}

2. Redis分布式锁

分布式锁基于SETNX(SET if Not eXists)指令实现。使用 SETNX 可以在Redis中创建一个不存在的Key,如果该Key已存在,则设置失败,返回0,设置成功返回1。

在使用Redis分布式锁时,应该注意以下问题:

(1)由于每个客户端都是独立的,所以在执行一个操作时可能会有多个客户端同时重复执行。我们需要解决相互之间的干扰。

(2)如果Redis分布式锁的Key在SETNX后会因某种原因过期,那么仍然存在多个进程同时竞争锁的情况。

// PHP代码示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = 'lock_key';
$value = uniqid(); // 生成唯一值作为锁的值
$expire = 10; // 锁过期时间,单位为秒

if ($redis->setnx($key, $value)) { // SETNX创建Key
    $redis->expire($key, $expire); // 设置Key过期时间
    echo '加锁成功';
    // TODO:执行业务逻辑
    $redis->del($key); // 释放锁
} else {
    echo '加锁失败';
}

三、实战应用:Redis实现秒杀系统

下面结合Redis实现秒杀系统,以更形象地认识乐观锁。

假设有100个商品,每个用户最多能够购买3个,当库存不足时不允许继续购买。

系统的实现思路如下:

(1)使用 Redis 分布式锁保证操作的同步。

(2)在 Redis 中存储每个用户的购买次数。

(3)通过 Redis 事务,先锁定 Redis 中的库存和用户购买次数,再判断库存和用户购买次数是否充足,若充足扣减库存和购买次数。

// PHP代码示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = 'goods_1001';
$quantity = 10;

$user_id = 101;
$user_key = "user_{$user_id}";
$user_limit = 3;

$lock_key = 'lock_key';
$lock_expire = 10;

// 获取分布式锁
while (!$redis->setnx($lock_key, uniqid())) {
    usleep(1000); // 暂停1ms再次尝试
}
$redis->expire($lock_key, $lock_expire);

// 判断商品库存是否充足
$redis->watch($key, $user_key);
$currentQuantity = $redis->get($key);
$currentUserLimit = $redis->get($user_key) ?? 0;
if ($currentQuantity >= $quantity && $currentUserLimit < $user_limit) {
    // 开启Redis事务
    $redis->multi();
    $redis->incrby($user_key, 1); // 记录用户购买次数+1
    $redis->decrby($key, $quantity); // 商品库存-10
    // 执行Redis事务
    $res = $redis->exec();
    if ($res) {
        echo '购买成功';
    } else {
        echo '购买失败';
    }
} else {
    echo '购买失败,库存不足或购买次数已达上限';
}
$redis->del($lock_key); // 释放分布式锁

总结

本文详细介绍了Redis乐观锁的实现方式,其中包括使用 Redis 事务和 Redis 分布式锁两种方式。在实际应用中,需要根据具体业务场景选择合适的方式来实现乐观锁。希望能对读者有所帮助。