不能只靠前端或nginx限流,因该接口是cpu密集型,nginx限流易被代理池绕过,前端限流完全不可信;必须后端多层限流(ip+用户标识+内容特征)并配合沙箱超时与熔断机制。

为什么不能只靠前端或 Nginx 限流
HTML 在线运行接口(比如接收 html、js、css 代码并返回渲染结果)本质是 CPU 密集型服务——每次执行都需启动沙箱、解析、执行、截图或 DOM 渲染,极易被压垮。Nginx 的 limit_req 只能按 IP 或 URI 做粗粒度拦截,但恶意压测常使用代理池、短生命周期 IP 或伪造 User-Agent 绕过;而纯前端限流(如按钮禁用)完全不可信,请求可直接绕过。
后端必须做多层限流:IP + 用户标识 + 请求内容特征
单一层级容易被绕过,真实有效的防护得叠加三类维度:
-
$binary_remote_addr是基础,但必须配合burst=5 nodelay防瞬时洪峰(否则排队会拖慢整个响应队列) - 有登录态的,优先用
user_id或access_token做 key(避免一个账号开几十个窗口狂刷) - 对无状态接口,提取请求体特征:比如
content-length > 50KB或body中包含while(true)、eval(、document.write等高危模式,直接拦截或降权计数
Spring Boot 里用 Redis + 滑动窗口实现精准防刷
固定窗口(如每分钟 10 次)有临界突刺问题;滑动窗口更合理,推荐用 Redis 的 ZSET 实现。关键点:
- key 设为
rate:ip:{ip}或rate:token:{token} - score 存时间戳(秒级),value 存唯一请求 ID(避免重复提交计入)
- 每次请求前:
ZREMRANGEBYSCORE key 0 (current_timestamp - 60)清旧数据,再ZCARD判是否超限(如 > 15) - 别用
INCR + EXPIRE组合——非原子操作,在高并发下会漏判
示例伪代码:
if (redis.zcard("rate:ip:123.45.67.89") > 15) { throw new RuntimeException("too many requests"); } redis.zadd("rate:ip:123.45.67.89", System.currentTimeMillis(), UUID.randomUUID().toString());
真正容易被忽略的点:沙箱超时 + 主动熔断
限流只是“拦门”,但没解决“门内已卡死”的问题。HTML 运行接口必须配硬性超时和资源限制:
- Node.js 沙箱用
vm2时,设timeout: 3000和memoryLimit: 30(MB) - JVM 后端用
ScriptEngineManager执行 JS,必须包裹在CompletableFuture.orTimeout(3, TimeUnit.SECONDS)里 - 当 1 分钟内失败率 > 40%(比如沙箱 OOM、超时、语法错误),自动触发熔断:接下来 5 分钟所有该类请求直接返回 429,不进执行链
这个层级的保护,比调参数更重要——它防止一次恶意 for(;;){} 就让整个服务不可用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











