锁和锁文件

  • 文件锁

/**
 * 使用文件锁执行程序
 *
 * @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 '';
}