锁和锁文件
文件锁
/**
* 使用文件锁执行程序
*
* @param string $name 锁文件名称
* @param string $type 执行类型,hold=等待上一个锁执行,other=抛出异常提示执行中
* @param \Closure $function 执行的程序
* @return mixed
* @copyright jcleng
* @author lxx
*/
function do_lock_file($name, $type, \Closure $function)
{
if (empty($name)) {
$name = 'lock_' . time();
}
$fliename = BASE_PATH . '/runtime/' . $name . '.lock';
$fp = fopen($fliename, 'a');
if ($type == 'hold') { // 等待上一个锁执行
if (!flock($fp, LOCK_EX)) {
// hold
}
} else {
if (!flock($fp, LOCK_EX | LOCK_NB)) { // 执行中, 直接返回异常
throw new \Exception("请等待...");
}
}
$ret = null;
try {
$ret = $function();
} catch (\Throwable $th) {
throw $th;
} finally {
// 执行完成, 删除文件
@flock($fp, LOCK_UN);
@fclose($fp);
@unlink($fliename);
}
return $ret;
}
redis 锁
/**
* redis方法锁
*
* @param string $lockKey 锁key名称
* @param string $type hold=等待上一个锁执行,other=抛出异常提示执行中
* @param \Closure $function
* @param integer $hold_time 等待超时时间,秒
* @return mixed
* @author jcleng
*/
function do_lock_file($lockKey, $type, \Closure $function, int $hold_time = 30)
{
/**
* @var \redis
*/
$redis = redis();
$lockKey = '{lock}:' . $lockKey;
$ttl = 90; // 锁超时时间, 一般时间较长, 不能太短吗否则$function还没执行完成就删除过期key了
//
$hold_utime = 0;
check_loop:
$has = $redis->exists($lockKey);
if (!empty($has)) {
if ($type == 'hold') {
if ($hold_utime > $hold_time * 1000 * 1000) {
throw new \Exception('等待超时!', 500);
}
// 微秒和秒
$usleep = 100000;
\Swoole\Coroutine::sleep(0.1);
$hold_utime = $hold_utime + $usleep;
goto check_loop;
}
throw new \Exception('操作处理中,请稍候再试!', 500);
} else {
$redis->set($lockKey, 1, ['nx', 'ex' => $ttl]);
// 执行
try {
$ret = $function();
} catch (\Throwable $th) {
throw $th;
} finally {
redis()->del($lockKey);
}
return $ret;
}
}
频率限制
/** 频率限制
* @param string $key
* @param int $expire
* @param int $num
* @param string $msg
* @param bool $isReturn
* @return string
*/
function astrict(string $key, int $expire, int $num, string $msg = '', bool $isReturn = false): string
{
$redis = redis();
$key = 'astrict_' . $key;
$val = (int)($redis->get($key));
$val !== 0 || $redis->setEx($key, $expire, 0);
$ttl = $redis->ttl($key);
$ttl !== -1 || $redis->expire($key, $expire);
if ($val >= $num) {
$msg = $msg ?: "操作频率被限制,{$num}次/{$expire}秒,请在 $ttl 秒后再试...";
throwRpcException($msg);
return $msg ?: "操作频率被限制,{$num}次/{$expire}秒,请在 $ttl 秒后再试...";
}
$redis->incr($key);
return '';
}