CC攻击防御实战:利用Redis实现分布式限流计数器
摘要:# 别让CC攻击拖垮你的网站:用Redis做个“限流器”其实不难 我前两天刚帮一个做电商的朋友处理了一次CC攻击,那场面,真是绝了。 他的网站平时访问量也就几千,结果那天下午突然卡到打不开。登录服务器一看,CPU直接飙到100%,Nginx日志里全是密…
别让CC攻击拖垮你的网站:用Redis做个“限流器”其实不难
我前两天刚帮一个做电商的朋友处理了一次CC攻击,那场面,真是绝了。
他的网站平时访问量也就几千,结果那天下午突然卡到打不开。登录服务器一看,CPU直接飙到100%,Nginx日志里全是密密麻麻的请求,IP地址五花八门,但User-Agent都长得差不多——典型的CC攻击特征。
他之前买了个“基础版”的WAF,宣传说能防CC。结果呢?攻击一来,WAF自己先扛不住了,规则匹配不过来,直接给绕过去了。用他的话说:“PPT上吹得天花乱坠,真打起来就跟纸糊的一样。”
说白了,很多现成的防护方案,对付小打小闹还行,真遇到稍微有点规模的CC攻击,要么贵得离谱,要么就是个摆设。今天,我就想跟你聊聊一个我们自己能动手搞定的、成本低效果却不错的实战方案:用Redis实现分布式限流计数器。
一、CC攻击到底在攻击什么?(以及为什么你买的防护可能没用)
很多人一听说CC攻击,就觉得是“流量大”。其实不对。
CC(Challenge Collapsar)攻击,核心不是带宽洪水(那是DDoS干的),而是资源耗尽。攻击者控制一堆“肉鸡”或者代理IP,模拟正常用户疯狂请求你网站上那些最耗资源的页面——比如搜索页、商品详情页、或者某个复杂的API接口。
你的服务器每处理一个请求,都要分配CPU、内存、数据库连接。当这些并发请求数超过你的服务器处理能力,新的正常用户请求就排不上队了,网站就“卡死”或者直接502了。
这时候,你可能会想:“我上了高防IP/高防CDN啊!” 没错,它们能扛流量,能隐藏源站IP。但很多CC攻击的请求,看起来跟正常请求太像了——同样的HTTP头,同样的访问路径。清洗中心如果规则设得严,容易误杀;设得松,又拦不住。问题往往就出在这个“度”的把握上。
(我自己看过不少配置,发现很多站点的防护规则压根没根据自身业务特点调整过,用的全是默认策略,那能防得住才怪。)
所以,真正的防线应该往里收,收到你的应用层,在你的服务器门口设一道“安检闸机”。这就是应用层限流。
二、为什么是Redis?自己写个计数器不行吗?
“限流”的核心思想很简单:在单位时间内,只允许一个IP(或者一个用户)访问有限的次数,超过就拒绝。
你可能会想,这还不简单?我用程序写个变量,来一个请求就+1,时间到了清零不就行了?
——想法很好,但一上线就得跪。原因有两个:
- 单点问题:你在一台服务器内存里计数,如果用户请求通过负载均衡打到了另一台服务器上,计数就对不上了。攻击者正好利用这点,把请求分散到不同服务器,你的限流就形同虚设。
- 重启失效:服务器一重启,内存里的计数全清空,攻击者笑出声。
所以,我们需要一个集中式的、高性能的、能持久化的计数器。Redis,几乎是为此而生的。
它内存操作,速度极快,单机轻松扛住每秒十几万的读写。它支持分布式,所有后端服务器都连同一个Redis集群,计数标准就统一了。数据可以持久化到硬盘,不怕重启。而且,它有一个绝佳的原子操作命令,能让我们的限流实现变得异常简单和可靠。
三、实战:用Redis的INCR和EXPIRE搭个限流闸机
好了,不卖关子,直接上干货。核心就用Redis的两个命令:INCR 和 EXPIRE。
思路是这样的:
- 以“IP地址+时间窗口”为Key(比如
rate_limit:192.168.1.1:minute)。 - 每来一个请求,就用
INCR命令将这个Key的值加1。INCR是原子操作,绝对不会有并发问题,这是精髓。 - 同时,检查这个Key的值。如果是第一次创建(返回值为1),就给它设置一个过期时间(比如60秒)。
- 如果
INCR后的值超过了我们设定的阈值(比如一分钟60次),就直接拒绝这个请求。
下面是一段用Python(Flask框架)写的示例代码,你看一下,其实非常简单:
import redis
from flask import Flask, request, jsonify
app = Flask(__name__)
# 连接Redis,这里假设Redis在本地
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def is_rate_limited(ip, limit=60, window=60):
"""
检查IP是否被限流
:param ip: 客户端IP
:param limit: 时间窗口内允许的请求数
:param window: 时间窗口大小(秒)
:return: True表示被限流,False表示允许通过
"""
key = f"rate_limit:{ip}:{window}"
# 使用管道提升性能,保证原子性
pipe = redis_client.pipeline()
pipe.incr(key) # 计数+1
pipe.expire(key, window) # 设置过期时间(如果key已存在,expire会覆盖)
count, _ = pipe.execute() # 执行管道,返回结果列表
# 如果计数超过限制,则触发限流
if count > limit:
return True
return False
@app.route('/your-api')
def your_api():
client_ip = request.remote_addr
if is_rate_limited(client_ip):
# 返回429状态码:Too Many Requests
return jsonify({'error': '请求过于频繁,请稍后再试'}), 429
# 正常的业务逻辑
return jsonify({'data': '这里是你的正常业务数据'})
if __name__ == '__main__':
app.run()
这段代码里的门道,我给你拆开说说:
pipe = redis_client.pipeline():这行代码创建了一个管道。把incr和expire两个命令塞进管道,然后一次性发给Redis执行。这不仅能减少网络往返时间,更重要的是保证了这两个命令作为一个整体被执行,不会插进其他客户端的命令,确保了“计数”和“设置过期时间”的原子性。这是防并发的关键。expire的妙用:我们只在计数器第一次创建(incr后值为1)时,通过管道设置过期时间。如果Key已存在,expire命令会刷新它的过期时间。这样,这个Key就会在最后一次访问后的第60秒自动被Redis删除,完美实现了“滑动时间窗口”。内存管理干干净净,没有垃圾数据。- 为什么是429? HTTP 429状态码就是为“请求过多”设计的,对搜索引擎友好,也比直接返回403或502更符合标准。
四、别急着上线,这些坑我帮你踩过了
方案看起来很美,对吧?但直接照搬到生产环境,你可能会遇到下面这些问题:
-
问题1:误伤正常用户怎么办? 有些公司出口IP就一个,所有员工共享。如果限流太狠,一个人刷页面快了,全公司都上不去。
- 解法:别只用IP一刀切。可以结合业务,对“登录用户”采用更宽松的
user_id限流,对“未登录用户”才用严格的IP限流。或者,对搜索、提交订单这些核心接口严格限,对静态资源、首页浏览就放宽。
- 解法:别只用IP一刀切。可以结合业务,对“登录用户”采用更宽松的
-
问题2:攻击者换着IP来打怎么办? 这是必然的。
- 解法:Redis限流只是第一道防线。你需要把它和其他手段结合。比如,前面用高防CDN做初步的IP信誉库过滤和频率检查;在Redis限流后,对确认为恶意的IP,用防火墙(如iptables)或云厂商的安全组拉黑更长的时间(比如24小时)。这就形成了“快速反应”和“持久封禁”的组合拳。
-
问题3:Redis挂了不是全完了?
- 解法:没错,所以Redis本身必须高可用。至少要用个主从哨兵模式,有条件就上Redis Cluster集群。同时,在你的限流代码里要加降级策略。比如,try-catch住Redis操作,一旦连接失败,就记录日志并放行请求。宁可临时放开限流,也不能让正常用户因为防护系统故障而无法访问。你的业务连续性,比防住攻击更重要。
-
问题4:阈值到底设多少?
- 解法:没有标准答案。去看你服务器的Nginx/Access日志,用工具分析一下正常用户在最活跃时段,每分钟的请求峰值是多少。在这个峰值上,留出50%-100%的余量,作为你的初始限流阈值。然后,观察、调整、再观察。运维的活儿,一半是配置,一半是看监控图。
五、说点大实话:没有银弹,只有组合拳
最后,我得给你泼点冷水,但也给你指条明路。
用Redis做分布式限流,是一个效果显著、成本低廉的“贴身防护”方案,特别适合应对那些瞄准你应用逻辑的CC攻击。它能帮你扛住大部分低到中强度的、漫无目的的扫描和攻击。
但是,你也别指望它单打独斗就能解决所有问题。面对真正有备而来的、海量IP池的、针对性的攻击,你需要的是一个立体防御体系:
- 最外层:高防IP/高防CDN,扛住大流量,隐藏你的真实服务器IP,这是你的“护城河”。
- 中间层:WAF(Web应用防火墙),配置好针对HTTP/HTTPS协议的攻击规则,过滤掉SQL注入、XSS等常见Web攻击,这是你的“城门守卫”。
- 最内层(也就是今天讲的):应用层自研的防护逻辑,比如Redis限流、验证码、人机识别等。这就像你家里的防盗门和保险柜,是最贴身的一道防线。
防御的本质,就是增加攻击者的成本和难度。 你的网站从“裸奔”到有了Redis限流,攻击成本就高了一大截。如果他还要突破你的高防和WAF,成本就更高了。很多时候,攻击者一看你这儿防守严密,自己就去找更软的柿子捏了。
行了,方案和代码都给你了,坑也提前告诉你了。剩下的,就是根据你自家业务的情况,去配置、去测试、去观察了。防护这事儿,从来不是一劳永逸的,但它值得你花点心思。
毕竟,等真被打趴下了再哭,可就来不及了。

