接口老是被刷?分享几个我在用的防刷限流土办法 [复制链接]

一级用户组

站里几个接口前阵子被高频请求打得很惨,日志里全是同一批 IP 在轮询。后来花了一下午做限流,这里分享几个不用上 Redis、单文件就能跑的土办法,适合小站和自用接口。

1. IP + 时间窗口计数

最朴素的思路是记录每个 IP 在固定时间窗口内请求了几次,超过阈值就拒绝。用文件做计数器就行,但并发一定要加锁,不然计数会互相覆盖。

2. 按登录用户限流

很多操作其实应该按用户而不是按 IP 来限,比如签到、发帖、转积分。按 uid 计数更准,因为换代理和换 IP 都很容易,换账号成本更高。

3. 接口分级配置

不要让所有接口共用同一个阈值。公开接口和登录后接口分开设置,正常用户的体验会稳很多。

下面是一个可以直接改出来的最小版本:

<?php
class SimpleLimiter {
    private $dir;
    private $max;
    private $window;

    public function __construct($dir, $max = 60, $window = 60) {
        $this->dir = $dir;
        $this->max = $max;
        $this->window = $window;
        is_dir($dir) || mkdir($dir, 0777, true);
    }

    public function check($key) {
        $file = $this->dir . '/' . md5($key) . '.json';
        $fp = fopen($file, 'c+');
        flock($fp, LOCK_EX);
        $data = json_decode((string)stream_get_contents($fp), true) ?: ['count' => 0, 'start' => time()];
        if (time() - $data['start'] > $this->window) {
            $data = ['count' => 0, 'start' => time()];
        }
        $data['count']++;
        ftruncate($fp, 0);
        rewind($fp);
        fwrite($fp, json_encode($data));
        flock($fp, LOCK_UN);
        fclose($fp);
        return $data['count'] <= $this->max;
    }
}

$limiter = new SimpleLimiter(sys_get_temp_dir() . '/api_limit');
if (!$limiter->check($_SERVER['REMOTE_ADDR'])) {
    http_response_code(429);
    header('Retry-After: 60');
    exit('请求太频繁了,休息一下再来');
}

if (!empty($_SESSION['uid']) && !$limiter->check('u' . $_SESSION['uid'])) {
    http_response_code(429);
    exit('操作太快了,慢一点');
}
  

几个实际踩过的坑

  • 一开始没加 flock,高并发下文件计数会互相覆盖,限流会穿透。
  • 窗口时间别设太短,60 秒比较顺手,太短容易误伤正常访问。
  • 被限流时返回 429,前端和日志都更好判断。
  • 文件做计数适合小站,流量上来后就切到 Redis 的 INCR + EXPIRE。

这种写法够应付轻量站点的日常防刷,到了更高并发场景,思路可以平滑迁移到 Redis 或网关层。主题如果想继续展开,我会下一篇补一版 Nginx 和 PHP 双层限流的实战写法。

最新回复

请先登录后再回复 登录

uid:4 一级用户组
关注
我与春风皆过客,你携秋水揽星河
发帖 20
评论 0
粉丝 0
关注 0
发新帖
目录
接口老是被刷?分享几个我在用的防刷限流土办法