限流控频策略排行榜:精选对比与推荐
限流是保障系统稳定的关键手段,以下四种方案在实现逻辑、粒度控制和时间窗口设计上各有侧重。示例代码侧重演示核心思路,生产环境需结合实际调整。
1. 基于数据库计数的限流方案
数据库限流的核心逻辑:每次请求向表中插入一条带时间戳的记录,通过SQL统计指定时间窗口内的操作次数,与阈值比较后决定是否放行。以下场景可直观说明。
适用场景:登录模块需要防止暴力破解,如1分钟内密码错误超过5次则临时锁定账号。
实施要点:
- 每次登录失败,将用户名与当前时间写入错误记录表。
- 登录验证前,基于用户名统计过去1分钟内的失败次数。
- 若次数超过阈值(如5次),直接拒绝登录并触发锁定;否则放行,若本次登录失败则继续记录。
示例SQL:
SELECT COUNT(login_name) FROM login_failure_log WHERE login_name = 'admin' AND login_time > DATE_SUB(NOW(), INTERVAL 1 MINUTE)
方案评价:
该方案通过自定义SQL条件实现滑动时间窗口,粒度灵活可控,且天然支持分布式部署。主要瓶颈在于数据库写入与查询并发量,当QPS较高时,数据库易成为性能短板,通常适合低频场景(如登录错误重试限制)。
2. 基于Redis计数器与过期时间的限流实现
Redis限流的核心思路:利用带过期时间的Key维护访问计数,每次请求前读取该Key的值,若超出阈值则拒绝,否则通过INCR自增并重置过期时间。这样可在高并发下高效控制流量。
适用场景:对接口做会话级别的访问频率控制,如每用户每秒最多20次请求。
实施要点:
- 按会话ID构造唯一Key,读取其当前值。
- 若Key不存在,设初值1并指定过期时间;若存在且值小于阈值,自增1;若大于等于阈值,触发限流。
Redis命令示例:
# 设置key值为1,5秒后过期
set kkk 1 EX 5
# 获取key的当前值
get kkk
# 原子自增
incr kkk
# 手动更新过期时间
expire kkk 5
代码片段:
private boolean accessCheck(HttpSession session) {
String key = "ACCESS" + session.getId();
long current = 1;
// key不存在,进行初始化
if( valueOperations.get(key) == null){
// 一步到位
//valueOperations.set(key,1,5,TimeUnit.SECONDS);
// 也可分两步完成
valueOperations.increment(key);
valueOperations.getOperations().expire(key,5,TimeUnit.SECONDS);
} else {
// key已存在,自增
valueOperations.increment(key);
current = Long.parseLong(valueOperations.get(key));
}
if(current > 20){
throw new RuntimeException("访问过于频繁,请求已被限流");
}
return true;
}
方案评价:
Redis方案依赖固定时间窗口(过期时间),在高并发下性能优异,且天然支持分布式环境。粒度取决于Key的设计,可精细到用户、IP甚至URL级别。缺点是固定窗口在窗口边界会有短时突刺,可通过结合滑动窗口或令牌桶优化。
3. 基于内存数据结构(LinkedList)的限流策略
内存限流通过在JVM中维护一个有序时间戳集合,每次请求时剔除超出时间窗口的旧记录,统计窗口内请求数,实现滑动窗口精准控制。适用于单机应用级别。
适用场景:对接口调用频率做应用内限制,如单机每5秒最多5次。
实施要点:
- 使用按时间正序排列的列表(例如LinkedList),避免全量遍历。
- 每次请求先移除所有超过时间窗口的旧节点。
- 将当前时间戳追加到列表末尾,然后判断列表大小是否超过阈值。
代码示例(基于LinkedList):
private boolean accessCheckByLinkedList(){
synchronized(accessList) {
Date data = null;
Date now = new Date();
if(accessList.size() > 0){
data = accessList.getFirst();
while (data != null && now.getTime() - data.getTime() > 5000) {
accessList.removeFirst();
if(accessList.size() > 0){
data = accessList.getFirst();
} else {
data = null;
}
}
if (accessList.size() > 5) {
throw new RuntimeException("接口调用过于频繁");
}
}
accessList.add(new Date());
}
return true;
}
方案评价:
内存限流粒度最精细,可精确实现滑动时间窗口,且无网络开销。缺点在于需自行处理线程安全(如上例使用synchronized),高并发下锁竞争可能影响吞吐。由于数据仅存于单机内存,分布式场景需额外同步机制,通常仅用于单体架构或边缘防护层。
4. 基于令牌桶算法的平滑限流方案
令牌桶算法维护一个固定容量的“桶”,以恒定速率向桶中添加令牌,每次请求消耗一个令牌。桶满时令牌溢出,桶空时请求被拒绝。该算法可平滑突发流量,适用于需要控制平均速率同时允许一定突发的场景。
适用场景:应用级别API请求频度控制,允许短时突发但长期平均速率受限。
实施要点:
- 设置桶容量(最大突发量)和令牌生成速率。
- 定时任务按速率向桶中投放令牌,到达容量上限时停止。
- 每次请求尝试从桶中取出令牌,取不到则触发限流。
代码示例:
public class BucketAlgorithmTest {
public static void main(String[] args) throws InterruptedException {
// 模拟令牌定时流入
new Thread(()-> {
while (true){
Bucket.add();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(200);
// 假设桶初始已满,这里可省略初始填充;模拟消费
new Thread(()->{
int errorcout = 0;
while (errorcout < 10) {
try {
Bucket.get();
Thread.sleep(40);
} catch (Exception e) {
errorcout++;
e.printStackTrace();
try {
Thread.sleep(100);
}catch (Exception ee){}
}
}
}).start();
}
}
class Bucket{
/**
* 当前桶中令牌数量
*/
private static Integer currentSize = 0;
/**
* 桶容量
*/
private final static int totalSize = 20;
/**
* 添加令牌(定时调用)
*/
public static void add(){
synchronized (currentSize) {
if(currentSize < totalSize) {
currentSize++;
System.out.println("[添加令牌]当前令牌数:" + Bucket.getCurrentSize());
}
}
}
/**
* 消费令牌(每次请求调用)
*/
public static void get(){
synchronized (currentSize) {
if(currentSize > 0) {
currentSize--;
System.out.println("[使用令牌]剩余:" + Bucket.getCurrentSize());
}else {
throw new RuntimeException("无可用令牌,请求被限流");
}
}
}
public static Integer getCurrentSize(){
return currentSize;
}
}
运行效果示意(截取自控制台):
方案评价:
令牌桶算法通过调节生成速率与桶容量,可灵活控制平均流量并容忍短暂突发。其存储可置于内存、Redis或数据库,因此既可单机部署也能支持分布式。缺点是高并发下需要对令牌操作加锁,且恢复时间受桶大小及生成速率影响。实际应用中常配合漏桶(固定流出)或作为负载均衡策略的补充。
方案特征对比
| 限流方案 | 时间窗口类型 | 存储依赖 | 分布式支持 | 典型场景 |
|---|---|---|---|---|
| 数据库计数 | 滑动 | 数据库 | 支持 | 登录失败锁定、慢速接口限制 |
| Redis计数器 | 固定 | Redis | 支持 | 接口调用频率、API限流 |
| 内存数据结构 | 滑动 | 单机内存 | 不支持 | 单体应用接口控制、边缘限流 |
| 令牌桶算法 | 滑动流量 | 内存/Redis/数据库 | 支持 | 通用限流,可配置平均速率与突发 |
