侧边栏壁纸
博主头像
ToDream博主等级

行动起来,活在当下

  • 累计撰写 13 篇文章
  • 累计创建 5 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Redis 分布式锁

X
X
2024-02-21 / 0 评论 / 0 点赞 / 7 阅读 / 9160 字

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);
    }
}

关键点说明:

  1. 锁的存储: 锁的信息被存储在Redis中,通过$this->lockName来唯一标识锁的键。
  2. 获取锁: getLock方法通过调用Redis的set方法来设置锁,使用nx选项确保只有在锁不存在时才能设置成功。过期时间($ttl参数)保证即使获取锁的节点崩溃,锁也会在一定时间后自动释放。
  3. 设置过期时间: expire方法用于设置锁的过期时间,确保即使在获取锁之后,锁也会在一定时间后自动释放。
  4. 释放锁: 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" => []];
}
0

评论区