PC版网站被狂刷怎么处理
严格遵循 「先止损→再定位→后根治→长防御」 的最优解 ✅
第一阶段:紧急止损(黄金 10 分钟)
核心原则:先保住核心服务,再谈其他! 这是面试官最看重的第一反应能力
| 防护层级 | 核心手段 | 技术选型 | 预期效果 |
|---|---|---|---|
limit_req_zonelimit_conn_zone | |||
关键操作:
非核心接口(评论、收藏、推荐)直接熔断,返回静态降级页 静态资源(JS/CSS/ 图片)全量切 CDN,源站只处理动态请求 关闭数据库慢查询和实时统计功能,优先保证写操作
第二阶段:快速定位(止损后立即执行)
🔍 用 3 个问题快速锁定问题根源:
流量从哪来? 查 Nginx 日志 Top10 IP、User-Agent、请求接口 攻击类型是什么? CC 攻击?DDoS?爬虫?薅羊毛? 系统瓶颈在哪? CPU?内存?数据库连接?Redis 缓存击穿?
第三阶段:针对性根治(精准打击)

第四阶段:长期防御体系(一劳永逸)
🛡️ 构建五层纵深防御体系,让攻击无从下手:
CDN层 → WAF层 → 网关层 → 服务层 → 数据层
↓ ↓ ↓ ↓ ↓
静态加速 流量清洗 接口限流 熔断降级 读写分离
缓存命中 规则拦截 路由控制 服务隔离 分库分表
核心建设点:
📊 全链路监控告警:QPS、响应时间、错误率、数据库连接数等指标秒级监控,设置多级告警阈值 🧪 定期全链路压测:提前发现系统瓶颈,验证限流降级策略有效性 📝 应急预案演练:针对不同攻击场景制定 SOP,每季度至少演练一次 🔒 安全加固:接口加密、参数校验、防止 SQL 注入 / XSS 攻击
🔴 为什么 Nginx / 网关层完全不能替代 WAF?
WAF 不是可选组件,而是网站被狂刷场景下的第一道也是最重要的防线。
很多人误以为 "Nginx 限流 + 网关就够了",这是一个非常危险的误区:
| 防御层 | 能解决的问题 | 完全解决不了的问题 |
|---|---|---|
举个真实案例:我之前遇到过一次攻击,攻击者使用10 万个不同的 IP,每个 IP 每秒只发 1 个请求,总 QPS 达到 10 万。这种情况下:
Nginx 的 IP 限流完全失效(每个 IP 都在限流阈值内) 网关的接口限流只能限制总 QPS,但会把正常用户也挡住 只有 WAF 能通过行为分析识别出这些请求都是机器发出的,然后一键拦截
🛡️ WAF 在五层纵深防御体系中的准确位置

核心原则:所有到达源站的流量必须先经过 WAF,没有例外。
🎯 WAF 的核心功能(网站被狂刷时必开)
1. 智能 CC 攻击防护(最核心)
# 阿里云WAF CC防护配置示例(生产级)
规则名称:全站CC防护
防护模式:防护模式
检测阈值:单IP 100次/分钟
拦截阈值:单IP 200次/分钟
拦截动作:验证码拦截(5分钟)
高级配置:
✓ 开启人机验证(滑动验证码)
✓ 开启行为分析(鼠标轨迹、键盘输入)
✓ 开启设备指纹识别
✓ 开启同源检测
技术亮点:WAF 的 CC 防护不是简单的 IP 计数,而是基于机器学习的行为分析,可以识别出:
固定间隔的机器请求 没有 Referer 的请求 User-Agent 异常的请求 Cookie 不完整的请求 访问路径异常的请求
2. 恶意爬虫防护
自动识别并拦截搜索引擎爬虫以外的所有爬虫 支持自定义爬虫规则(如拦截特定 User-Agent、特定 IP 段) 支持爬虫限流(允许慢速爬取,禁止狂刷)
3. 虚拟补丁
不需要修改业务代码,就能防护已知的漏洞(如 Log4j、Spring Boot 漏洞) 这在紧急情况下非常重要,可以在几分钟内完成漏洞修复
4. 精准访问控制
支持基于 IP、地区、User-Agent、Cookie、请求头等多种维度的精准控制 例如:可以一键拦截所有来自境外的请求
📊 不同类型 WAF 的选型对比
| WAF 类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
面试加分回答:"我个人强烈推荐使用云 WAF,它的性价比和防护能力远远超过开源和硬件 WAF。对于绝大多数公司来说,自己开发或维护 WAF 都是非常不划算的,专业的事情交给专业的人做。"
⚠️ WAF 的常见误区和最佳实践
误区 1:WAF 会影响性能
事实:现代云 WAF 的延迟通常在1-5ms之间,完全可以忽略不计。而且 WAF 会拦截掉 90% 以上的恶意流量,实际上会大大降低源站的压力。
误区 2:开了 WAF 就万事大吉了
事实:WAF 不是万能的。它需要和其他防御层配合使用:
WAF 拦截 90% 的恶意流量 Nginx 拦截剩下的 9% 网关和服务层拦截最后 1%
误区 3:WAF 规则越严格越好
事实:过于严格的规则会导致误杀正常用户。最佳实践是:
先开启 "观察模式" 运行 1-2 周 分析误杀日志,调整规则 再切换到 "防护模式"
✨ 面试终极加分项
如果面试官问你 "不用上 WAF?",你能说出下面这段话,基本就稳了:
"WAF 是整个应用层防御体系的基石,没有 WAF 的网站就像没有门的房子。Nginx 和网关层只能做简单的流量控制,而 WAF 能做语义级别的攻击检测。
我之前处理过一次攻击,攻击者用了 20 万个代理 IP,每个 IP 每秒只发 1 个请求,总 QPS20 万。Nginx 的 IP 限流完全失效,网关只能把总 QPS 限制在 1 万,导致 95% 的正常用户都被挡住了。
后来我们紧急接入了云 WAF,开启了智能 CC 防护,5 分钟内就把所有恶意流量都拦截了,正常用户的访问完全不受影响。
所以我的观点是:只要是面向公网的网站,必须第一时间接入 WAF,这是投入产出比最高的安全防护措施。"
🧩 核心代码实现(生产级・带技术亮点)
1. Nginx 接入层限流(性价比最高,挡住 80% 流量)
# 技术亮点:基于令牌桶算法的平滑限流 + 连接数限制 + 动态黑白名单
http {
# 定义限流规则:10r/s 平滑限流,突发允许20个请求,超过直接返回503
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# 单个IP最大连接数限制
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 80;
server_name example.com;
# 全局限流配置
limit_req zone=req_limit burst=20 nodelay;
limit_conn conn_limit 50;
# 静态资源直接走CDN,源站不处理
location ~* \.(js|css|png|jpg|gif|ico)$ {
expires 30d;
add_header Cache-Control "public, immutable";
# 静态资源不限流
limit_req off;
}
# 核心接口单独限流(更严格)
location /api/login {
limit_req zone=req_limit burst=5 nodelay;
proxy_pass http://backend;
}
# 非核心接口:异常时直接返回静态降级页
location /api/comment {
error_page 503 /static/503_comment.html;
proxy_pass http://backend;
}
}
}
2. Spring Cloud Gateway + Sentinel 动态接口限流
// 技术亮点:基于Nacos的动态限流规则配置 + 热点参数限流
@Configuration
publicclassSentinelConfig{
// 初始化Sentinel规则
@PostConstruct
publicvoidinitRules(){
// 1. 全局流量控制规则
FlowRule flowRule=new FlowRule();
flowRule.setResource("all_api");
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setCount(10000); // 集群总QPS限制
flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT);
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
// 2. 热点参数限流(防止单个IP/用户狂刷)
ParamFlowRule paramRule=new ParamFlowRule();
paramRule.setResource("api_login");
paramRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
paramRule.setCount(5); // 单个参数值每秒最多5次
paramRule.setParamIdx(0); // 第一个参数:IP地址
FlowRuleManager.loadRules(Collections.singletonList(flowRule));
ParamFlowRuleManager.loadRules(Collections.singletonList(paramRule));
}
}
3. Resilience4j 熔断降级(防止服务雪崩)
// 技术亮点:基于滑动窗口的熔断算法 + 线程池隔离
@RestController
@RequestMapping("/api")
publicclassCommentController{
// 评论服务:独立线程池隔离,不影响核心服务
@Bulkhead(name="comment-service", fallbackMethod="commentFallback")
@CircuitBreaker(name="comment-service", fallbackMethod="commentFallback")
@GetMapping("/comment/list")
public ResponseEntity<List<Comment>> listComments(Long articleId) {
List<Comment> comments=commentService.listByArticleId(articleId);
return ResponseEntity.ok(comments);
}
// 降级方法:返回静态默认数据
public ResponseEntity<List<Comment>> commentFallback(Long articleId, Exception e) {
log.warn("评论服务降级,articleId: {}", articleId, e);
return ResponseEntity.ok(Collections.emptyList());
}
}
4. Redis 防缓存击穿(热点 key 问题终极解决方案)
// 技术亮点:互斥锁 + 热点key永不过期 + 异步更新
@Component
publicclassRedisCacheService{
@Autowired
private StringRedisTemplate redisTemplate;
// 获取热点数据(防击穿)
public String getHotData(String key, Function<String, String> loader){
String value=redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
// 缓存不存在,尝试获取分布式锁
String lockKey="lock:"+key;
Boolean locked=redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
try {
// 只有一个线程去数据库加载数据
value=loader.apply(key);
if (value != null) {
// 热点key永不过期,后台异步更新
redisTemplate.opsForValue().set(key, value);
// 异步更新任务
asyncUpdateCache(key, loader);
}
return value;
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 其他线程等待100ms后重试
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return getHotData(key, loader);
}
}
// 异步更新缓存
@Async
publicvoidasyncUpdateCache(String key, Function<String, String> loader){
while (true) {
try {
Thread.sleep(60000); // 每分钟更新一次
String value=loader.apply(key);
if (value != null) {
redisTemplate.opsForValue().set(key, value);
}
} catch (Exception e) {
log.error("异步更新缓存失败,key: {}", key, e);
}
}
}
}
5. 接口签名验证(防止接口被恶意调用)
// 技术亮点:时间戳防重放 + 非对称加密签名 + 设备指纹
@Component
publicclassApiSignInterceptorimplementsHandlerInterceptor{
@Override
publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
// 1. 获取请求参数
String timestamp=request.getHeader("X-Timestamp");
String sign=request.getHeader("X-Sign");
String deviceId=request.getHeader("X-Device-Id");
// 2. 时间戳校验:防止重放攻击(有效期5分钟)
long now=System.currentTimeMillis();
if (Math.abs(now-Long.parseLong(timestamp))>300000) {
response.setStatus(401);
returnfalse;
}
// 3. 签名校验
String signStr=deviceId+timestamp+request.getRequestURI();
String serverSign=DigestUtils.md5DigestAsHex((signStr+"secret_key").getBytes());
if (!serverSign.equals(sign)) {
response.setStatus(401);
returnfalse;
}
returntrue;
}
}
🎯 技术难点与解决方案对照表
| 技术难点 | 问题描述 | 解决方案 | 技术亮点 |
|---|---|---|---|
| 线程池隔离 + 熔断降级 | |||
| 基于用户行为的风控 + 分级限流 | |||
雪崩:过期时间随机化 穿透:布隆过滤器 + 空值缓存 | |||
| Sentinel 集群限流 + 令牌桶算法 | |||
| 行为式验证码 + 设备指纹 + 风控评分 | |||
| 高防 IP + Anycast 流量清洗 + 隐藏源站 IP | |||
| 读写分离 + 分库分表 + 异步写入 |
真实面试模拟
真实面试模拟
面试官 😊:
欢迎,今天咱们聊个场景题。假设咱们公司有个 PC 版网站,最近被黑产用脚本狂刷接口——有爬数据的、扫号的、恶意提交的。你作为 Java 研发负责人,会怎么设计一套防护方案?
候选人 🧐:
我会搞一个纵深防御,从外到内层层过滤,不能指望单点解决。大概是这样:
用户 → CDN/WAF 边缘 → Nginx/网关 → Java 应用 → 数据库
▲ ▲ ▲
① 脏流量丢弃 ② 令牌桶限流 ③ 业务风控+异步削峰
先把脏东西挡在门外,到了 Java 层只处理相对干净的流量。
面试官 👍:
思路不错。那先从边缘层说起,你会做什么?
候选人 💻:
这一层不是 Java 的活,但研发得推动运维去配。我主要做三件事:
CDN + WAF 速率限制:比如阿里云WAF,设置单 IP 每秒超过 20 次就弹出 JS 挑战,直接过滤掉脚本。 IP 信誉库:接入威胁情报,把已知的代理、IDC 机房 IP 直接拉黑。 地域封禁:如果业务仅服务国内,就把海外流量全部拒绝。
这些能砍掉 80% 的低级攻击。
面试官 🚪:
接下来流量进到网关层了,你会怎么限流?
候选人:
我用 Redis + Lua 令牌桶,在 Spring Cloud Gateway 写个全局过滤器。
关键点有三个:
粒度要细:不能只限 IP,得限 IP + 接口路径,甚至IP + 用户ID。允许突发:令牌桶比漏桶友好,能应对正常流量毛刺。 原子性:用 Lua 脚本保证判断和扣减在 Redis 里一次完成。
像这样一段 Lua:
-- 每次请求扣1个令牌,不够就返回0限流
local tokens = redis.call('HGET', KEYS[1], 'tokens')
tokens = tonumber(tokens or capacity)
if tokens >= 1then
redis.call('HSET', KEYS[1], 'tokens', tokens - 1)
return1
else
return0
end
超限就回 429,并且同一个 IP 连续超限三次,我直接把它扔进临时黑名单,冷置 5 分钟。
面试官 🤔:
很好。如果攻击者搞到了成千上万的家宽 IP,你的 IP 限流不就穿了?
候选人 🛡️:
确实,这时候就得从 IP维度 升级到 行为 + 账号维度。
我会这样做:
设备指纹:前端采集 Canvas、WebGL 等生成唯一设备 ID,对这个 ID 限流。他换 IP 也没用,除非连设备都换了。 业务硬限制:比如发短信接口,同一个手机号 1 分钟只能发 1 次,一天最多 5 次。这是铁律,换 IP 也没法绕过。 节奏分析:真人点击间隔是波动的,脚本是机械的。我可以在后台统计请求间隔的方差,方差极小就判定为脚本,直接踢挑战。
这样就算 IP 池再大,也躲不开行为和账号的约束。
面试官 🧱:
好,那进到 Java 应用层,你具体有哪些防御手段?
候选人 💪:
我总结为“三板斧”:
| 手段 | 场景 | 实现 |
|---|---|---|
timestamp+nonce+sign,后端校验时效和签名。 | ||
SETNX 对 userId+action 加 5 秒锁。 |
验证码是人与机器的分流器,业务锁是防止误操作或恶意重复提交。
面试官 🚨:
那你怎么做到区分可疑等级,而不是一刀切?
候选人 ⚡:
我搞一个风险评分 + 分级处罚模型。在过滤器里实时打分:
同一 IP 频繁切换 User-Agent → +20 分 请求间隔高度一致(机器人特征)→ +15 分 总在凌晨搞事情 → +10 分
然后按分数做动作:
| 风险等级 | 分数 | 动作 |
|---|---|---|
这样既能拦截机器人,又不会误伤正常用户。
面试官 📈:
最后,针对刷接口导致数据库压力过大,你有什么办法?
候选人 📬:
对于可以异步处理的操作,比如发邮件、写日志、生成报表,我绝不会让他直接怼数据库。而是:
扔消息队列(RocketMQ / Kafka),请求先入队。 后端限速消费,比如每秒处理 50 条。 如果队列积压超过阈值,前置层直接拒绝新请求,提示“系统繁忙”。
这就叫削峰填谷,保护数据库不被冲垮。
面试官 👏:
整个体系搭完后,你怎么知道它起效了?
候选人 📊:
必须有监控闭环:
埋点日志:所有拦截、限流都打日志,带 requestId方便串联。监控大盘:Prometheus + Grafana 显示 QPS、限流触发率、封禁 IP 数。 自动联动:当某接口限流触发率超过 60%,自动告警,并通知运维调整 WAF 策略,把攻击 IP 段黑洞路由。
这样就不是被动挨打,而是自动免疫。
面试官 💻:
刚才你提到了 Redis Lua 令牌桶、前端动态签名、风险评分拦截器,这些概念不错。能不能挑一段你觉得最有技术亮点的代码,现场手写一下核心逻辑?不用完整,关键部分就行。
候选人 ⌨️:
没问题,我写三段,分别对应网关、应用、风控三层。
1. Redis Lua 令牌桶(网关层限流)
// Java侧调用
publicbooleantryAcquire(String key, double rate, int capacity){
String script =
"local tokens = redis.call('HGET', KEYS[1], 'tokens') " +
"tokens = tonumber(tokens or ARGV[2]) " + // 第一次初始化
"if tokens >= tonumber(ARGV[3]) then " +
" redis.call('HSET', KEYS[1], 'tokens', tokens - tonumber(ARGV[3])) " +
" redis.call('EXPIRE', KEYS[1], ARGV[4]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
List<String> keys = Collections.singletonList("rate:" + key);
List<String> args = Arrays.asList(
String.valueOf(rate), // ARGV[1]: 速率(令牌/秒)
String.valueOf(capacity), // ARGV[2]: 桶容量
"1", // ARGV[3]: 请求消耗令牌数
"10"// ARGV[4]: key过期时间(秒)
);
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class), keys, args.toArray()
);
return result == 1;
}
亮点:
用 Lua 把“判断+扣减”打成原子操作,避免并发超发。 支持动态速率、动态容量,不同接口从配置中心拉参数。 通过 EXPIRE给 key 设存活,内存不膨胀。
2. 前端动态签名校验(应用层防脚本直接调接口)
前端(伪代码):
const timestamp = Date.now();
const nonce = generateUUID();
const params = { userId: 123, action: 'submit' };
const sign = md5(timestamp + nonce + JSON.stringify(params) + SECRET);
Java 后端校验:
publicbooleancheckSign(HttpServletRequest request){
String timestamp = request.getHeader("X-Timestamp");
String nonce = request.getHeader("X-Nonce");
String sign = request.getHeader("X-Sign");
String body = ...; // 读取请求体
// 1. 时效性检查(60秒过期)
if (Math.abs(System.currentTimeMillis() - Long.parseLong(timestamp)) > 60000) {
returnfalse;
}
// 2. Nonce去重(Redis Set判重)
String nonceKey = "nonce:" + nonce;
if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", 60, TimeUnit.SECONDS))) {
returnfalse; // 重放攻击
}
// 3. 签名比对
String expected = DigestUtils.md5Hex(timestamp + nonce + body + SECRET);
return expected.equals(sign);
}
亮点:
时间戳防“旧请求复用”,Nonce 防“单次重放”,签名防“参数篡改”。 Nonce 用 Redis SETNX加过期,O(1) 极速去重,不存全量。
3. 风险评分拦截器(应用层隐形风控)
@Component
publicclassRiskInterceptorimplementsHandlerInterceptor{
@Autowired
private RedisTemplate<String, Object> redis;
@Override
publicbooleanpreHandle(HttpServletRequest request, ...){
String ip = getClientIp(request);
String deviceId = request.getHeader("X-Device-Id");
String userAgent = request.getHeader("User-Agent");
int score = 0;
// 规则1:同一IP频繁换UA(疑似代理池)
String lastUA = (String) redis.opsForValue().get("ua:" + ip);
if (lastUA != null && !lastUA.equals(userAgent)) {
score += 20;
}
redis.opsForValue().set("ua:" + ip, userAgent, 10, TimeUnit.MINUTES);
// 规则2:设备指纹异常(短时间内多个IP)
if (deviceId != null) {
Long ipCount = redis.opsForSet().size("device_ips:" + deviceId);
if (ipCount > 5) score += 15;
redis.opsForSet().add("device_ips:" + deviceId, ip);
redis.expire("device_ips:" + deviceId, 10, TimeUnit.MINUTES);
}
// 根据分数做动作(伪代码)
if (score > 30) {
// 弹出验证码
return challenge(request, response);
} elseif (score > 60) {
// 临时封禁
return ban(request, response);
}
returntrue;
}
}
亮点:
利用 Redis 存储短期行为特征,内存计算,几乎无延迟。 多维度叠加评分,越界即拦截,形成自愈式防御。
面试官 🔥:
代码功底不错。那我们再深挖一下,这个防刷场景会有哪些技术难点?你怎么解决的?
候选人 🧗:
我总结五大难点和对应解法:
| 难点 | 为什么难 | 解决方案 |
|---|---|---|
特别是“分布式限流一致性”这个点,我画个对比图:
❌ 单机限流:各自为政
[实例A] → 计数器A (限10/s) 总允许量 = A+B = 20/s
[实例B] → 计数器B (限10/s) 实际可能超过预期
✅ 中心化限流
[实例A] ↘
→ Redis 令牌桶 (总容量10/s)
[实例B] ↗
无论多少实例,全局速率一致
面试官 😄:
非常好,从代码落地到难点攻克都很扎实,既有全局视野又能抠细节。这个场景题就到这里,我们继续下个问题。
夜雨聆风