前言
在一些业务场景中用户或用进程同时处理一块逻辑时会导致异常冲突, 因此使用并发锁只允许获得锁的那个进入下一步的处理, 未获得锁的进入等待或直接拒绝操作, 这样能极大程度避免并发冲突问题.
示例
Redis执行命令是单线程按顺序执行的, 使用Redis命令现实锁功能也就意味着先执行的先拿到锁.
环境说明:
- 下面示例中使用composer安装的
predis/predis:^1.1
扩展连接Redis - config()函数是用来获取Redis服务配置的,你可以替换成你环境中的对应值
<?php
namespace util;
use Predis\Client;
class Redis
{
const LOCK_PREFIX = 'lk:'; //并发锁键名前缀
private static $client;
/**
* 单例模式获取redis连接实例
* @return Client
*/
public static function client()
{
if (!self::$client) {
$config = [
'scheme' => 'tcp',
'host' => config('redis.host'),
'port' => config('redis.port'),
];
//没有配置密码时,不传入密码项参数
if (config('redis.password')) $config['password'] = config('redis.password');
//所有键名都带上默认前缀
self::$client = new Client($config, ['prefix' => config('redis.prefix')]);
}
return self::$client;
}
/**
* 添加自定义并发锁
* 原理是redis的单线程操作
* @param string $name 锁名
* @param int $ttl 锁的存在时长,秒
* @param int $retries 重试次数
* @param int $interval 重试间隔,毫秒
* @return bool 是否由当前调用加锁成功
*/
public static function lock(string $name, int $ttl = 10, int $retries = 0, int $interval = 200)
{
$redis = self::client();
$key = self::LOCK_PREFIX . $name;
$is_lock = false; //默认是加锁失败
$interval = $interval * 1000; //毫秒转微秒
//默认只试一次,如设了重试次数则叠加
$num = 1 + $retries;
for ($i = 1; $i <= $num; $i++) {
//关键操作,键不存在时才设置值且带过期时间
$is_lock = (bool)$redis->set($key, 1, 'NX', 'EX', $ttl);
if ($is_lock) {
break;
} elseif ($retries > 0 && $i < $num) {
//间歇后再重试;如当前已是最后一次重试,则不休眠
usleep($interval);
}
}
return $is_lock;
}
/**
* 解除自定义并发锁
* @param string $name 锁名
* @return bool 是否成功
*/
public static function unlock(string $name)
{
$key = self::LOCK_PREFIX . $name;
return (bool)self::client()->del([$key]);
}
}
//使用
//加锁, 返回 true 表示成功获取锁
$lk = Redis::lock('lock_key');
if ($lk) {
//进行一些业务处理
}
//解锁, 业务处理完后解锁, 解锁后其他人可重新获得锁
Redis::unlock('lock_key');
end.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。