本文围绕 redis 的 SETNX 命令展开对“锁”的研究与实现。多个进程同时对 redis 执行 SETNX string_key timestamp_expired 命令,只有一个进程会成功,其余都会失败。上述命令中的 string_key 代表被当作锁的 redis 键名,其类型为 String,timestamp_expired 代表该键的过期时间戳,下同。
算法中使用 redis 的 EVAL 命令保证执行语句的原子性。
申请加锁前需约定锁的有效期、加锁失败后的重试次数、休眠时间(申请加锁时,其他线程加的锁还未过期,休眠一段时间后再重试加锁)。
申请加锁步骤:
SETNX string_key timestamp_expired ),如果加锁成功,设置锁的过期时间并返回成功。否则进入步骤 2;GETSET string_key timestamp_expired_new 命令,如果命令返回时间戳与旧的过期时间戳相等,加锁成功,设置锁的过期时间并返回成功。否则进入步骤 5;GETSET string_key timestamp_expired_new 命令,返回步骤 1 重新申请加锁。释放锁:
如果 string_key 键未被删除且还未过期,执行删除键操作( DEL string_key )。
主要类文件有 2 个,为文件 Redis.php(获取 redis 操作客户端) 和 Lock.php(加、释放锁),内容分别如下:
namespace app\service;
use Predis\Client;
use think\facade\Env;
class Redis
{
/**
* @var Client $client
*/
protected $client;
public function __construct()
{
$this->client = new Client([
'host' => Env::get('redis.host