Lock 锁

优质
小牛编辑
132浏览
2023-12-01

锁(Lock),在并发处理,防止冲突的场景非常常用。

在 imi 中,你可以使用注解,或者自己实例化Lock类来实现加锁处理。

除了内置的锁驱动外,你可以实现Imi\Lock\Handler\ILockHandler接口,来实现其他方式的锁。

配置用法

需要在配置中预定义

配置说明

// 锁
'lock'  =>[
    'list'  =>  [
        // 锁 ID => 配置
        'redis' =>  [
            'class' =>  'RedisLock', // Handler 类 Bean 名或完整类名
            'options'   =>  [
                // Handler 类所需配置
                'poolName'  =>  'redis_test',
            ],
        ],
    ],
],

使用说明

顺序用法:

use Imi\Lock\Lock;
$lockId = ''; // 你定义的ID
if(Lock::lock($lockId))
{
    try {
        // 干一些事情
    } catch(\Throwable $th) {
        throw $th;
    } finally {
        Lock::unlock($lockId);
    }
}

回调用法(无需手动释放锁):

$result = Lock::lock($lockId, function(){
    // 执行任务
}, function(){
    // return 非null则不执行任务
    // 一般用于防止缓存击穿
    // 这个回调可以不传
});
if($result)
{
    // 加锁并执行成功
}
else
{
    // 加锁失败
}

获取 Handler 对象:

$lock = Lock::getInstance($lockId);
$lock->lock(); // 使用方法参考下面的“实例化使用方法”

注解使用

@Lockable

无需在配置中预定义

支持参数:

/**
 * 锁ID
 * 支持{id}、{data.name}形式,代入参数
 * 如果为null,则使用类名+方法名+全部参数,序列化后hash
 *
 * @var string|null
 */
public $id;

/**
 * 锁类型,如:RedisLock
 * 为null则使用默认锁类型(@currentServer.lock.defaultType)
 *
 * @var string|null
 */
public $type;

/**
 * 等待锁超时时间,单位:毫秒,0为不限制
 * 
 * @var int
 */
public $waitTimeout = 3000;

/**
 * 锁超时时间,单位:毫秒
 * 
 * @var int
 */
public $lockExpire = 3000;

/**
 * 锁初始化参数
 *
 * @var array
 */
public $options = [];

/**
 * 当获得锁后执行的回调。该回调返回非 null 则不执行加锁后的方法,本回调的返回值将作为返回值
 * 一般用于防止缓存击穿,获得锁后再做一次检测
 * 如果为{"$this", "methodName"}格式,$this将会被替换为当前类,方法必须为 public 或 protected
 * 
 * @var callable
 */
public $afterLock;

用法示例

最简单的锁:

只指定id,其它全部默认值

@Lockable(id="锁ID")

定义执行任务前的操作:

class Test
{
    /**
     * @Lockable(id="锁ID", afterLock={"$this", "check"})
     */
    public function index()
    {
        return 1;
    }

    protected function check()
    {
        return 2;
    }

    /**
     * @Lockable(id="锁ID", afterLock={"$this", "check2"})
     */
    public function index2()
    {
        return 3;
    }

    protected function check2()
    {

    }
}

$result = App::getBean('Test')->index();
echo $result, PHP_EOL; // 2

$result = App::getBean('Test')->index2();
echo $result, PHP_EOL; // 3

实例化使用方法

无需在配置中预定义

顺序用法

$redisLock = new \Imi\Lock\Handler\Redis('锁ID', [
    'poolName'  => 'redis', // Redis 连接池名称,默认取redis.default配置
    'db'        =>  1, // Redis 几号库,默认0
    'waitSleepTime' =>  20, // 获得锁每次尝试间隔,单位:毫秒
    'keyPrefix' =>  'imi:lock:', // Redis key 前置
]);

if($redisLock->lock())
{
    // 加锁后的处理

    // 解锁
    $redisLock->unlock();
}
else
{
    // 加锁失败的处理
}

回调用法

$redisLock = new \Imi\Lock\Handler\Redis('锁ID', [
    'poolName'  => 'redis', // Redis 连接池名称,默认取redis.default配置
    'db'        =>  1, // Redis 几号库,默认0
    'waitSleepTime' =>  20, // 获得锁每次尝试间隔,单位:毫秒
    'keyPrefix' =>  'imi:lock:', // Redis key 前置
]);

// 执行后自动解锁
$result = $redisLock->lock(function(){
    // 执行任务
}, function(){
    // return 非null则不执行任务
    // 一般用于防止缓存击穿
});

if($result)
{
    // 加锁并执行成功
}
else
{
    // 加锁失败
}