限流控频策略排行榜:精选对比与推荐

2026-06-16阅读 0热度 0
其他

限流是保障系统稳定的关键手段,以下四种方案在实现逻辑、粒度控制和时间窗口设计上各有侧重。示例代码侧重演示核心思路,生产环境需结合实际调整。

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/数据库 支持 通用限流,可配置平均速率与突发
免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策