Better late than never.

PHP使用redis与file的“锁”笔记
  早前被一个redis缓存击穿的问题狠狠的砸了一拳,故整理记录一下之前留下的笔记。

关于redis的缓存击穿

  • 出现原因:对于一个并发很高的接口,缓存的key在失效的一瞬间会有机会将所有的请求都会打在数据库上面,造成数据库查询压力过大

  • 一般解决思路:通常采用redis加锁(悲观锁/互斥锁)来解决,每次请求都会向redis进行拿锁操作,拿锁成功->写入缓存,否则进入等待时期(继续拿锁至成功为止)

部分代码

Redis乐观锁(optimism)

/**
     * 乐观锁
     * @param string $key
     * @param callable $function
     * @param string $value
     * @param int $ttl
     * @return mixed
     * @throws \Exception
     */
    public function optimismLock(
        string $key,
        callable $function,
        string $value = RedisFuncService::LOCK_VALUE,
        int $ttl = RedisFuncService::LOCK_TTL
    )
    {
        try {
            $lock = $this->lock($key, $value, $ttl);

            if (!$lock) {
                logger("get lock fail return", compact('key'));
                throw new \Exception("locking");
            }
            // 执行主程序
            $action = $function();
            // 执行完毕 ,使用lau脚本解锁 
            $this->unlock($key, $value);
        } catch (\Exception $exception) {
            $this->unlock($key, $value);
            throw new \Exception($exception->getMessage());
        }

        return $action;
    }

Redsi悲观锁(pessimistic)

/**
     * 悲观锁
     * @param string $key
     * @param callable $function
     * @param string $value
     * @param int $ttl
     * @return mixed
     * @throws \Exception
     */
    public function pessimisticLock(
        string $key,
        callable $function,
        string $value = RedisFuncService::LOCK_VALUE,
        int $ttl = RedisFuncService::LOCK_TTL
    )
    {
        try {

            do {
                $lock = $this->lock($key, $value, $ttl);

                if (!$lock) {
                    // 继续等待拿锁成功
                    logger("get lock fail waiting", compact('key'));
                    usleep(5000); // 微妙
                } else {
                    // 拿锁成功直接跳出执行主程序
                    break;
                }

            } while (!$lock);

            // 执行主程序
            $action = $function();
            // 执行完毕,解锁
            $this->unlock($key, $value);
        } catch (\Exception $exception) {
            $this->unlock($key, $value);
            throw new \Exception($exception->getMessage());
        }

        return $action;
    }

文件乐观锁

public function optimismLock(string $file_name, callable $func, array $option = [])
    {
        list($mode) = $this->serializationOptions($option);

        $file_name = storage_path($file_name);
        $fp = fopen($file_name, $mode);

        try {

            if (!flock($fp, LOCK_EX | LOCK_NB)) { // 强占锁,不阻塞,$function没执行完直接返回错误
                throw new \Exception("file locking");
            }

            $func = $func();

            flock($fp, LOCK_UN);

            fclose($fp);

            @unlink($file_name);
        } catch (\Exception $exception) {
            flock($fp, LOCK_UN);

            fclose($fp);

            @unlink($file_name);
            throw new \Exception($exception->getMessage());
        }

        return $func;

    }

文件悲观锁

public function pessimisticLock(string $file_name, callable $func, array $option = [])
    {
        list($mode) = $this->serializationOptions($option);

        $file_name = storage_path($file_name);

        $fp = fopen($file_name, $mode);

        try {
            if (flock($fp, LOCK_EX)) { // 强占锁 ,阻塞,执行完$function 才解锁
                $func = $func();
                flock($fp, LOCK_UN);
            }
            fclose($fp);
            @unlink($file_name);
        } catch (\Exception $exception) {
            flock($fp, LOCK_UN);
            fclose($fp);
            @unlink($file_name);
            throw new \Exception($exception->getMessage());
        }

        return $func;
    }

关于文件锁的补充

  • 使用共享锁LOCK_SH,如果是读取,不需要等待,但如果是写入,需要等待读取完成。
  • 使用独占锁LOCK_EX,无论写入/读取都需要等待。
  • LOCK_UN,无论使用共享/读占锁,使用完后需要解锁。
  • LOCK_NB,当被锁定时,不阻塞,而是提示锁定。
  • PS:文件锁也就单机应用试试,真正的用户量大、分布式的应用大都是Redis加锁

-- END

写的不错,赞助一下主机费

扫一扫,用支付宝赞赏
扫一扫,用微信赞赏
2020-04-24 17:00

PHPLAF v.1.0.10 正式发布了!开发文档:https://blog.junphp.com/apiword.jsp