php redis分布式锁如何防止并发,[Redis]laravel中使用Redis分布式锁解决并发问题

本文介绍了如何利用Laravel和Redis解决第三方接口回调时并发请求导致的重复入库问题。通过使用Redis的分布式锁,确保同一时刻只有一个请求能够执行入库操作。在加锁和解锁过程中,使用Lua脚本来保证操作的原子性,防止死锁,并提供了解锁时的请求ID验证,以确保解锁正确。文章还解释了为何使用Lua以及为何不直接使用Redis的set方法,并给出了完整的代码示例。
摘要由CSDN通过智能技术生成

需求描述

应用中一个第三方接口回调会产生并发请求,单次同时推送很多条信息,出现重复入库情况,需要在入库前拦截。

解决方案使用laravel队列(不在此文章讨论范围);

使用Redis锁

实现方法

1.请求处理开始前,先尝试获取锁,如果获取成功则继续执行,否则,终止执行。加锁时,需要考虑如果后续任务执行失败,能定时清理掉该锁,以防出现死锁。代码示例如下:/**

* 尝试获取锁

* @param String $key 锁

* @param String $requestId 请求id

* @param int $expireTime 过期时间

* @return bool 是否获取成功

*/

public static function tryGetLock(String $key, int $expireTime, String $requestId) {

$lua ="return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2])";

$result = Redis::eval($lua, 1,$key, $requestId, $expireTime);

return self::LOCK_SUCCESS === (String)$result;

}

2.该请求执行完成后,解除锁。示例代码如下:/**

* 解除锁

* @param String $key 锁

* @param String $requestId 请求id

*/

public static function releaseLock( String $key, String $requestId) {

$lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

$result = Redis::eval($lua, 1, $key, $requestId);

return self::RELEASE_SUCCESS === $result;

}

完整代码use Illuminate\Support\Facades\Redis;

class RedisTool

{

const LOCK_SUCCESS = 'OK';

const RELEASE_SUCCESS = 1;

/**

* 尝试获取锁

* @param String $key 锁

* @param String $requestId 请求id

* @param int $expireTime 过期时间

* @return bool 是否获取成功

*/

public static function tryGetLock(String $key, int $expireTime, String $requestId) {

$lua ="return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2])";

$result = Redis::eval($lua, 1,$key, $requestId, $expireTime);

return self::LOCK_SUCCESS === (String)$result;

}

/**

* 解除锁

* @param String $key 锁

* @param String $requestId 请求id

*/

public static function releaseLock( String $key, String $requestId) {

$lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

$result = Redis::eval($lua, 1, $key, $requestId);

return self::RELEASE_SUCCESS === $result;

}

}

总结备注

1.为什么要用Lua脚本来实现?

· 加锁时,先通过setnx加锁,然后在通过expire设置过期时间,无法保证redis原子性,在setnx执行后,程序可能挂掉,造成死锁;

· 解锁时,如果通过Redis::del($key),可能解除的是其他请求的锁;

总结:执行单个redis时,是可以保证原子性,如果是两个操作,则无法保证原子性。

2.加锁时为什么不直接用Redis::set($Key, $requestId, ['nx', 'ex' => $expireTime])?

· 这里我的laravel使用的是predis,Reis::set()方法不支持这种写法。

3.请求id$requestId是做什么的?怎么保证唯一?

· $requestId是区分本次请求与其他请求的标识。在加锁区间的业务执行完成后,需要解锁,$requestId保证了解锁的是当前请求的锁,而不是其他锁。

· $requestId要保证全局唯一才安全,可以使用Str::uuid()生成。

参考文章:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值