Redis计数器需结合IP+时间窗口原子限流,用SET EX NX防竞态,优先取X-Real-IP并校验,异步同步至DB防写瓶颈,策略应匹配实际威胁模型。

Redis 计数器怎么避免被刷爆
直接用 INCR 统计页面浏览量,不加任何限制,等于把计数器挂在门口任人按。真实场景里,一个脚本循环发 1000 次请求,INCR 就涨 1000,根本分不清是人是 bot。
必须结合 IP + 时间窗口做原子性限流。推荐用 SET key value EX seconds NX 配合 INCR,而不是单纯靠 EXPIRE 后再 INCR——后者有竞态:两个请求同时发现 key 不存在,都会执行 INCR,导致多计一次。
- 每个请求先尝试用
SET page:123:ip:192.168.1.100 1 EX 60 NX写入带过期的标记 - 只有写入成功(返回 1)才允许执行
INCR page:123:views - key 过期时间设为 60 秒,意味着同一 IP 每分钟最多贡献 1 次浏览
IP 地址怎么拿才靠谱
直接读 request.remote_addr 在 Nginx 反向代理后基本不可用——它返回的是 Nginx 服务器自己的内网 IP,不是用户真实 IP。
必须从请求头里取,但不能无脑信 X-Forwarded-For,它可被客户端伪造。安全做法是只信任你可控的上一级代理(比如 Nginx)传来的值,并在 Nginx 配置中显式设置:
立即学习“Python免费学习笔记(深入)”;
proxy_set_header X-Real-IP $remote_addr;
然后 Python 里优先读 request.headers.get("X-Real-IP"), fallback 到 X-Forwarded-For 的第一个非私有地址(需过滤 127.0.0.1、10.0.0.0/8 等)。
- 别用
request.environ.get("HTTP_X_FORWARDED_FOR")直接切分取首项——没校验,容易被绕过 - 本地开发时 Nginx 没配,
X-Real-IP为空,得有兜底逻辑,否则所有请求都算成同一个 IP - IPv6 地址要归一化(比如压缩零、转小写),不然
::1和0:0:0:0:0:0:0:1会被当成两个 IP
为什么不用数据库自增字段做 PV 统计
因为写数据库太重。每刷一次页面就触发一次 UPDATE article SET pv = pv + 1 WHERE id = 123,高并发下会成为瓶颈:行锁、事务开销、磁盘 I/O、主从延迟都会暴露出来。
Redis 是内存操作,INCR 原子且毫秒级;真正的 PV 数据可以异步落库——比如每 5 分钟用 GET 拿一次当前值,写进 MySQL 并清零 Redis 计数器。这样既扛住流量,又不丢精度。
- 别让 Redis 计数器长期累积,定期同步到 DB 后要
DEL或SET归零,否则内存占用持续增长 - 同步过程失败(比如 DB 写挂了),得有重试机制或本地日志记录,不然数据就丢了
- 如果业务要求“实时可见 PV”,那 Redis 当前值 + DB 历史值 = 最终 PV,不能只读 DB
防刷效果差的几个典型配置坑
很多人以为加了 IP 限制就万事大吉,结果上线一周 PV 翻倍——问题往往出在策略粒度或实现细节上。
127.0.0.1 本地测试全走同一个 IP,容易误判为刷量;CDN 或云 WAF 后面的真实 IP 可能被二次转发;还有动态 IP 用户(比如移动网络)几分钟换一个地址,被当成新用户反复计数。
- 测试阶段用
curl -H "X-Real-IP: 1.2.3.4"模拟不同 IP,别只在 localhost 刷 - 对移动端或家庭宽带用户,可放宽到“15 分钟内同一 IP 最多计 3 次”,而不是死卡 1 次
- 别忽略 User-Agent 和 Referer 的辅助判断——空 UA、固定 UA 字符串、非浏览器 UA 可额外降权或拦截
真正难的不是写几行 INCR,而是想清楚你要防的是群控脚本、SEO 采集器,还是只是避免误点刷新。策略得跟着威胁模型走,不是越严越好。










