问个锁的问题,有没有大佬指点

为了防止并发插入,所以我在接口中加了个redis锁,但是我想知道锁我应该在什么时候释放呢,程序结束我知道要手动释放,但是程序中有很多的判断然后die(json_encode([‘code’ => 0,’message’ => ‘error’,’data’ => ‘内容不能为空’]));,类似于这样的返给前端的,程序就中止了,此时要不要手动释放锁,不释放的话这锁是不是就存在,有考虑过包在try carch里面,然后这些判断都定义为异常,然后在异常里面手动解锁就行了,这样只要写一个,然后程序结束写一个就行了,但一看老代码全是die的这种写法,要改就太麻烦了

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
ShiKi
最佳答案

你的设计思路是正确的,为了保证锁在任何情况下都能正确地被释放掉,可以将加锁和解锁操作全部封装到 try-catch 块中。在 try 块中执行加锁操作,同时捕获所有的异常并在 catch 块中执行解锁操作。这样无论程序遇到什么异常都能保证锁最终会被释放。 以下是一个示例代码:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

try {
    // 加锁,使用 setnx 函数实现
    $locked = $redis->setnx('my_lock', 1);

    // 判断是否成功获得锁
    if (!$locked) {
        // 执行已经被加锁的操作,可以展示给用户一个“重复提交”的提示,
        // 然后直接结束程序(使用 die 或者 exit 语句),这里不用释放锁,程序结束后锁会自动释放
        die(json_encode(['code' => 1, 'message' => '操作已经在进行中,请稍后再试']));
    }

    // 执行正常的操作,可能会包含多次调用 die 函数
    // ...

} catch (\Exception $e) {
    // 异常处理,需要手动释放锁
    $redis->del('my_lock');
    // 输出异常信息,可以以日志的形式记录在文件或者数据库中
    error_log($e->getMessage());

    // 发送给前端格式化后的异常信息
    die(json_encode(['code' => 1, 'message' => '操作失败,请稍后再试']));
}

// 程序正常结束,需要手动释放锁
$redis->del('my_lock');

需要注意的是,在上述代码中,我们使用了 setnx 函数添加锁。这个函数的作用是在 Redis 中添加一个指定键名的值,如果该键名已经存在则返回 0,否则将设置成功并返回 1。我们可以根据返回值判断锁是否成功被添加。在尝试加锁成功后,会执行正常的操作。如果程序中执行到了 die 或 exit 等中止程序执行的语句,锁会自动被释放。在 try-catch 块中的 catch 部分中,所有的异常都会被捕获然后执行解锁操作。最后,在程序正常结束时,也要手动释放锁,保证锁资源得到正确的释放。

1年前 评论
讨论数量: 16

实在不行加个网关吧,老代码救不了。

1年前 评论

try... finally 语法。finally的代码die也会执行

1年前 评论

插入为什么要加锁呢,如果是唯一性校验,那unique不就解决了,

如果是类似订单库存,在更改库存那里校验,比如

update num = num - 1 from table where id = xxx and num > 1

成功了就插入,失败了就和插入没关系了。

1年前 评论
study_laraveler (楼主) 1年前
Adachi (作者) 1年前
nff93 1年前
Adachi (作者) 1年前
nff93 1年前

可以加finally 或者是__destruct 或者是在最外层处理

1年前 评论
sanders

在锁上加个寿命,到点就删这样可以防止死锁。

把老代码里面的 die 都换成 throw new \Exception 吧 ... 做好回归测试,程序和你有一个能跑就行 ;)

1年前 评论

可借鉴 laravel,创建时设置锁的有效期,在try的finally中手动释放。实际上 laravel 中获取锁还有重试,影响不大可以忽略。缓存系统《Laravel 6 中文文档》

问 ChatGPT 我选了一个不错的答案

ChatGPT的回答:

在编写程序时,我们通常会在获取锁之后立即进入需要加锁的代码块,并在执行完后释放锁,以保证锁的正确使用,防止并发插入。

而在你所提到的情况中,可能会出现在获取锁之后因为异常或判断错误等原因导致程序直接跳出了需要加锁的代码块,从而没有释放锁的情况。这种情况下锁就会一直被占用,影响其他请求的处理速度。

因此,对于这种情况,我们可以在使用完锁后加上 try-catch 的语句,确保不管代码是否出错都能够安全释放锁,例如:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lock_result = $redis->set('lock_key', 1, array('nx', 'ex' => 5)); // 获取锁

try {
    // 需要加锁的代码
    if ($condition) {
        die(json_encode(['code' => 0, 'message' => 'error', 'data' => '内容不能为空']));
    }
} catch (Exception $e) {
    // 异常处理
} finally {
    $redis->del('lock_key'); // 释放锁
}

在以上示例中,我们使用 try-catch 语句包裹了需要加锁的代码,在需要手动释放锁的地方增加了一个 finally 语句块,保证无论代码是否出错都能正确释放锁。这样就可以确保锁的正确使用,防止并发插入。

1年前 评论

评估一下接口最长时间,加一个自动释放锁的时间,这样子约到die 虽然偶尔会被拦截,下一次就会释放了,如果是抢购那种,不用锁,可以用redis list pop 指令保证原子性,看你场景,选最适合的方式

1年前 评论

php 有一个魔术方法 __destruct 在实例被销毁时会执行
这有个例子:
博客:laravel 实战延迟解锁

1年前 评论
shunjian_1 1年前
王小大 (作者) 1年前
ShiKi

你的设计思路是正确的,为了保证锁在任何情况下都能正确地被释放掉,可以将加锁和解锁操作全部封装到 try-catch 块中。在 try 块中执行加锁操作,同时捕获所有的异常并在 catch 块中执行解锁操作。这样无论程序遇到什么异常都能保证锁最终会被释放。 以下是一个示例代码:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

try {
    // 加锁,使用 setnx 函数实现
    $locked = $redis->setnx('my_lock', 1);

    // 判断是否成功获得锁
    if (!$locked) {
        // 执行已经被加锁的操作,可以展示给用户一个“重复提交”的提示,
        // 然后直接结束程序(使用 die 或者 exit 语句),这里不用释放锁,程序结束后锁会自动释放
        die(json_encode(['code' => 1, 'message' => '操作已经在进行中,请稍后再试']));
    }

    // 执行正常的操作,可能会包含多次调用 die 函数
    // ...

} catch (\Exception $e) {
    // 异常处理,需要手动释放锁
    $redis->del('my_lock');
    // 输出异常信息,可以以日志的形式记录在文件或者数据库中
    error_log($e->getMessage());

    // 发送给前端格式化后的异常信息
    die(json_encode(['code' => 1, 'message' => '操作失败,请稍后再试']));
}

// 程序正常结束,需要手动释放锁
$redis->del('my_lock');

需要注意的是,在上述代码中,我们使用了 setnx 函数添加锁。这个函数的作用是在 Redis 中添加一个指定键名的值,如果该键名已经存在则返回 0,否则将设置成功并返回 1。我们可以根据返回值判断锁是否成功被添加。在尝试加锁成功后,会执行正常的操作。如果程序中执行到了 die 或 exit 等中止程序执行的语句,锁会自动被释放。在 try-catch 块中的 catch 部分中,所有的异常都会被捕获然后执行解锁操作。最后,在程序正常结束时,也要手动释放锁,保证锁资源得到正确的释放。

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!