php基于滑动窗口限流

由于系统调用的下游服务存在限流,为了保障服务,在调用下游服务前添加一个限流策略,php基于滑动窗口,redis,lua脚本实现
 

/**
     * 
     * @param mixed $redis
     * @param mixed $rateLimitKey key
     * @param mixed $timeWindow 时间窗口,单位毫秒
     * @param mixed $limit 窗口内允许通过数
     * @throws \Concrete\Exceptions\BusinessException
     * @return bool
     */
    protected function rateLimit($rateLimitKey, $timeWindow = 10000, $limit = 50) // timeWindow 以毫秒为单位
    {
        $luaScript = <<<'LUA'
        local key = KEYS[1]
        local timeWindow = tonumber(ARGV[1])
        local limit = tonumber(ARGV[2])
            
        -- 获取当前时间(毫秒)
        local currentTime = redis.call('TIME')
        local now = tonumber(currentTime[1]) * 1000 + tonumber(currentTime[2]) / 1000
            
        local windowStart = now - timeWindow
        redis.replicate_commands()
        -- 移除过期的请求(滑动窗口:移除超过 timeWindow 毫秒的请求)
        redis.call('ZREMRANGEBYSCORE', key, '-inf', windowStart)
            
        -- 获取当前窗口内请求的数量
        local requestsInWindow = redis.call('ZCARD', key)
            
        if requestsInWindow >= limit then
            -- 请求超过限制,返回  表示限流
            return 2
        end
    
        -- 添加当前请求
        redis.call('ZADD', key, now, now)
            
        -- 若 key 无过期时间则设置过期时间
        local ttl = redis.call('PTTL', key)
        if ttl < 0 then
            redis.call('PEXPIRE', key, timeWindow)
        end
            
        -- 返回 1 表示请求通过
        return 1
LUA;
        if (!$this->redis) {
            Log::error("Redis connection error: redis不可用");
            return true;
        }
        try {
            $result = $this->redis->eval($luaScript, 1, $rateLimitKey, $timeWindow, $limit);
        } catch (\Exception $e) {
            Log::error("redis eval抛异常: " . $e->getMessage() . ",key: " . $rateLimitKey);
            return true;
        }
        $error = $this->redis->getLastError();
        if ($error) {
            Log::error("redis eval执行有错: " . $error . ",key: " . $rateLimitKey);
            return true;
        }
        switch($result){
            case 1:
                return true;
            case 2:
                throw new HttpException(Code::SERVICE_CURRENT_LINT, "Too Many Requests");
            default:
                //脚本执行异常,打印异常消息,放行
                Log::error("redis eval结果非预期,结果为:" . $result . ",key: " . $rateLimitKey);
            }
        return true;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值