首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >几种限流、控频策略对比

几种限流、控频策略对比

作者头像
master336
发布2026-06-15 18:44:42
发布2026-06-15 18:44:42
720
举报

这里主要讨论以下几种方式,有更好的建议,欢迎留言

  • 基于数据库(Mysql为例)的统计进行限流
  • 基于redis自增长及过期策略的限流
  • 基于内存(linkedlist为例)的限流
  • 基于木桶算法的限流

本次介绍,主要关注实现策略、控制粒度及时间窗口问题,示例代码中可能存在编码不规范的情况,请忽略

1. 基于数据库的统计进行限流

基于数据库的统计进行限流主要思想是,将每次的信息连同时间写入一条数据库记录,然后根据时间范围统计信息,决策是否需要限流。举例如下:

场景: 密码1分钟内输入错误次数超限,冻结账号。 要点: 1. 登录失败时,将用户的登录名、登录时间写入数据库记录。 2.登录前根据登录名,查询近1分钟登录失败的次数。 3.判断登录次数是否大于设定的超限值,如果大于则进行冻结处理,如果未超过,正常执行登录判断(登录失败时执行第一要点)。 代码要点:

代码语言:javascript
复制
 select count(登录名) from 登录错误记录表 where 登录名='登录名' and 登录时间 > DATE_SUB(NOW(), INTERVAL 1 MINUTE)

分析: 此方案的粒度控制在于代码逻辑的堆砌和数据库的设计,较为灵活,时间窗口为最近一分钟(假定),属于滑动时间窗口;由于时基于数据库的,分布式部署的情况下也能有效,不足之处在于会增加数据库的压力。

2. 基于redis自增长及过期策略的限流

基于redis自增长及过期策略主要思想是:在redis中维护一个key,并设置过期时间,每次控制前读取该key,如果该key对应的值大于了限制值,则被限制,如果不大于,则通过redis的incr对key进行自增长处理,如果为空,则重新维护key,并设置过期时间。如此往复达到限流目的。 场景: 对接口进行会话级别的访问控制 要点 1. 读取key,根据key对应值value的不同情况进行处理: a. value不存在,设置set key 及过期时间 b. value大于限定值,限流 c. valuex不大于限定值,value自增长1 代码要点: 涉及到的redis命令如下:

代码语言:javascript
复制
## 设置key的值5秒过期
set kkk 1 EX 5
### 获取key的值
get kkk
### 自增长
incr kkk
### 手动设置过期时间
expire kkk 5

代码示例

代码语言:javascript
复制
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已经存在了,自增+1
            valueOperations.increment(key);
            current = Long.parseLong(valueOperations.get(key));
        }

        if(current > 20){
            throw new RuntimeException("访问次数过于多");
        }

        return true;
    }

分析: 基于redis同样可以适用分布式环境,时间窗口固定,因redis特性,可适应较大流量冲击,控制粒度在于设计。

3. 基于内存(linkedlist为例)的限流

基于内存的限流主要策略是在内存中维护一个时间窗口数据,基于对数据的统计进行限流。 场景: 对接口调用频度进行控制 要点 1.维护一个以时间正序的列表(不是正序需要扫描全部数据) 2.去除时间窗口之外数据 3.末尾处加入最新时间 代码要点: 以linkedlist为例

代码语言:javascript
复制
 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;
    }

分析: 基于内存的限流,其粒度控制依赖于设计的是否高明,因为实在内存中处理的,需要考虑线程间同步(采用同步的存储如ConcurrentHashMap,可能不需要处理同步问题,但是要注意排序问题)及并发问题。控制粒度为应用级别,能从应用层面控制当前访问的压力,避免负载均衡策略失效或异常情况下带来的大流量冲击。

4. 基于木桶算法的限流

基于木桶算法的限流策略主要是维护一个固定大小的“池”,根据场景消耗“池”的存量,并不断的向内添加,但永远维护“池”中存量不会超过最大值 场景: 应用级别接口请求频度控制 要点 1.增加“池”中存量,存量达到最大值,暂停增加 2.使用“池”中存量,存量小于0限流 3.保证使用“池”不会溢出,也不会透支 代码要点: 基于该策略,以下完整代码描述了整个过程,其中的各频度控制仅为实验效果而定。

代码语言:javascript
复制
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

适用

接口访问控制

内存

滑动

内存

不适用

应用级别访问控制

木桶算法

滑动

内存/Redis/数据库

适用

根据应用位置而定

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 基于数据库的统计进行限流
  • 2. 基于redis自增长及过期策略的限流
  • 3. 基于内存(linkedlist为例)的限流
  • 4. 基于木桶算法的限流
  • 简单总结对比一下
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档