前言
在一些业务场景中用户或用过程同时解决一块逻辑时会导致异样抵触, 因而应用并发锁只容许取得锁的那个进入下一步的解决, 未取得锁的进入期待或间接回绝操作, 这样能极大水平防止并发抵触问题.
示例
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.