Redis 分布式锁
原理
使用 SET
命令
SET user_lock_1 1 NX EX 10
第一个参数: user_lock_1
key 名称
第二个参数: 1
value 值
选项参数:
NX
全称为 not exists
如果使用了 NX
选项的话只有 user_lock_1
在不存在的时候才执行 SET
操作 如果 key 已经存在的话不会更新现有值
EX
全称为 expire
如果使用了 EX
选项的话表示为 SET
的 key 设置一个到期时间 单位为秒
利用了 redis set 是原子操作的特性实现锁
实现
<?php
namespace App\Logic;
use Redis;
class Lock
{
protected Redis $redis;
protected string $lockName;
public function __construct()
{
$this->redis = redis();
}
/**
* 设置锁名称
*
* @param string $lockName
* @return void
*/
protected function setLockName(string $lockName): void
{
$this->lockName = "{$lockName}_lock";
}
/**
* 获取锁
*
* @param int $ttl
* @return bool
*/
public function getLock(int $ttl): bool
{
return $this->redis->set($this->lockName, 1, ['nx', 'ex', $ttl]);
}
/**
* 设置到期时间
*
* @param int $ttl
* @return bool
*/
public function expire(int $ttl): bool
{
return $this->redis->expire($this->lockName, $ttl);
}
/**
* 解锁
*
* @return bool
*/
public function unLock(): bool
{
return $this->redis->del($this->lockName);
}
}
关键点说明:
- 锁的存储: 锁的信息被存储在Redis中,通过
$this->lockName
来唯一标识锁的键。 - 获取锁:
getLock
方法通过调用Redis的set
方法来设置锁,使用nx
选项确保只有在锁不存在时才能设置成功。过期时间($ttl
参数)保证即使获取锁的节点崩溃,锁也会在一定时间后自动释放。 - 设置过期时间:
expire
方法用于设置锁的过期时间,确保即使在获取锁之后,锁也会在一定时间后自动释放。 - 释放锁:
unLock
方法通过调用Redis的del
方法来删除锁的键,实现了锁的释放。
基于 Lock
类自定义锁
<?php
namespace App\Logic;
/**
* 商品锁
*
* @package App\Logic
*/
class GoodsLock extends Lock
{
public function __construct(public int $goodsId)
{
$this->setLockName("goods_{$goodsId}");
parent::__construct();
}
}
使用 (模拟创建订单扣库存场景 目的 防止超卖)
public function buy(int $id)
{
$goodsLock = new GoodsLock($id);
$ttl = 10;
if (! $goodsLock->getLock($ttl)) {
return ['code' => 1001, "data" => []];
}
try {
$model = Goods::query()->where('id', $id);
if ($model->clone()->value('c') > 0) {
// 模拟减少库存创建订单
usleep(mt_rand(1, 2) * 100 * 1000);
$model->clone()->decrement('c');
}
} catch (\Exception $e) {
Log::error($e->getMessage());
} finally {
$goodsLock->unLock();
}
Log::info("操作成功");
return ['code' => 1000, "data" => []];
}
评论区