限流、消峰的三种办法

互联网服务赖以生存的根本是流量, 产品和运营会经常通过各种方式来为应用倒流,比如淘宝的双十一等,如何让系统在处理高并发的同时还是保证自身系统的稳定,通常在最短时间内提高并发的做法就是加机器,但是如果机器不够怎么办?那就需要做业务降级或系统限流。

流量控制中用的比较多的三个算法就是令牌桶、漏桶、计数器。

1.令牌桶限流(TokenBucket)
令牌桶算法的基本过程如下:

  1. 每秒会有 r 个令牌放入桶中,或者说,每过 1/r 秒桶中增加一个令牌。
  2. 桶中最多存放 b 个令牌,如果桶满了,新放入的令牌会被丢弃。
  3. 当一个 n 字节的数据包到达时,消耗 n 个令牌,然后发送该数据包。
  4. 如果桶中可用令牌小于 n,则该数据包将被缓存或丢弃。

2.漏桶限流(LeakBucket)
漏桶算法强制一个常量的输出速率而不管输入数据流的突发性,当输入空闲时,该算法不执行任何动作.就像用一个底部开了个洞的漏桶接水一样,水进入到漏桶里,桶里的水通过下面的孔以固定的速率流出,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率.如下图所示:

漏桶和令牌桶比较
“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的上限,因此它适合于具有突发特性的流量。

计数器限流
有时我们还会使用计数器来进行限流,主要用来限制一定时间内的总并发数。计数器限流方法可以通过缓存实现计数器,假如以秒为单位进行限流,过期时间为1秒,每次请求计数加1,超过每秒允许的最大请求数请求数阀值将被丢弃。

RateLimiter
我们可以使用 Guava 的 RateLimiter 来实现基于令牌桶的流量控制。RateLimiter 令牌桶算法的单桶实现,RateLimiter 对简单的令牌桶算法做了一些工程上的优化,具体的实现是 SmoothBursty。需要注意的是,RateLimiter 的另一个实现 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。

SmoothBursty 有一个可以放 N 个时间窗口产生的令牌的桶,系统空闲的时候令牌就一直攒着,最好情况下可以扛 N 倍于限流值的高峰而不影响后续请求,就像三峡大坝一样能扛千年一遇的洪水.

4.redis

local key = KEYS[1] --限流KEY(一秒一个)
local limit = tonumber(ARGV[1])        --限流大小
local current = tonumber(redis.call(‘get‘, key) or "0")
if current + 1 > limit then --如果超出限流大小
    redis.call("INCRBY", key,"1") -- 如果不需要统计真是访问量可以不加这行
    return 0
else  --请求数+1,并设置2秒过期
    redis.call("INCRBY", key,"1")
    if tonumber(ARGV[2]) > -1 then
        redis.call("expire", key,tonumber(ARGV[2])) --时间窗口最大时间后销毁键
    end
    return 1
end

lua脚本返回值比较奇怪,用java客户端接受返回值,只能使用Long,没有去深究。这个脚本只需要传入key(url+时间戳/预设时间窗口大小),便可以实现限流。

java调用lua脚本

/**
 * Created by xujingfeng on 2017/3/13.
 * <p>
 * 基于redis lua脚本的线程安全的计数器限流方案
 * </p>
 */
public class RedisRateLimiter {

    /**
     * 限流访问的url
     */
    private String url;

    /**
     * 单位时间的大小,最大值为 Long.MAX_VALUE - 1,以秒为单位
     */
    final Long timeUnit;

    /**
     * 单位时间窗口内允许的访问次数
     */
    final Integer limit;

    /**
     * 需要传入一个lua script,莫名其妙redisTemplate返回值永远是个Long
     */
    private RedisScript<Long> redisScript;

    private RedisTemplate redisTemplate;

    /**
     * 配置键是否会过期,
     * true:可以用来做接口流量统计,用定时器去删除
     * false:过期自动删除,时间窗口过小的话会导致键过多
     */
    private boolean isDurable = false;

    public void setRedisScript(RedisScript<Long> redisScript) {
        this.redisScript = redisScript;
    }

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public boolean isDurable() {
        return isDurable;
    }

    public void setDurable(boolean durable) {
        isDurable = durable;
    }

    public RedisRateLimiter(Integer limit, Long timeUnit) {
        this.timeUnit = timeUnit;
        Assert.isTrue(timeUnit < Long.MAX_VALUE - 1);
        this.limit = limit;
    }

    public RedisRateLimiter(Integer limit, Long timeUnit, boolean isDurable) {
        this(limit, timeUnit);
        this.isDurable = isDurable;
    }

    public boolean acquire() {
        return this.acquire(this.url);
    }

    public boolean acquire(String url) {
        StringBuffer key = new StringBuffer();
        key.append("rateLimiter").append(":")
                .append(url).append(":")
                .append(System.currentTimeMillis() / 1000 / timeUnit);
        Integer expire = limit + 1;
        String convertExpire = isDurable ? "-1" : expire.toString();
        return redisTemplate.execute(redisScript, Arrays.asList(key.toString()), limit.toString(), convertExpire).equals(1l);
    }

}

5.spring mvc

作者:Lewe
链接:http://www.jianshu.com/p/7170edcd9239
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

时间: 2024-08-11 22:14:56

限流、消峰的三种办法的相关文章

PHP修改memory_limit的三种办法

PHP修改memory_limit的三种办法 2010-06-11 10:57:11 分类: 可能是分词程序的问题.只要搜索的字段达到十个汉字以上,就会出现诸如以下的错误 Fatal error: Allowed memory size of 2345643 bytes exhausted 上网找了方法.有3种办法. 1.直接修改PHP.INI memory_limit = 16M  ; 但是我修改了没有用.据说是要重启服务器的.但是很显然.我的 是虚拟主机.所以有独立主机的可以这样修改. 2.

c#程序以管理员身份运行(三种办法)

三种办法: 一.设置程序本身的属性:勾选"以管理员身份运行此程序",必要时设置"更改所有用户设置-以管理员身份运行此程序",当然这种办法是被动的,也不是最实际的办法: 二.代码法: static void Main(string[] Args) { /** * 当前用户是管理员的时候,直接启动应用程序 * 如果不是管理员,则使用启动对象启动程序,以确保使用管理员身份运行 */ //获得当前登录的Windows用户标示 System.Security.Principa

Java基础知识强化之IO流笔记62:三种方式实现键盘录入

1. 三种方式实现键盘录入     System.in 标准输入流.是从键盘获取数据的 键盘录入数据三种方式:  A:main方法的args接收参数.  java HelloWorld hello world java  B:Scanner(JDK5以后的)  Scanner sc = new Scanner(System.in);  String s = sc.nextLine();  int x = sc.nextInt()  C:通过字符缓冲流包装标准输入流实现  BufferedRead

Oracle用户解锁的三种办法及默认的用户与密码

ORA-28000: the account is locked-的解决办法 2009-11-11 18:51 ORA-28000: the account is locked 第1步:使用PL/SQL,登录名为system,数据库名称不变,选择类型的时候把Normal修改为Sysdba; 第2步:选择myjob,查看users; 第3步:选择system,右击点击“编辑”: 第4步:修改密码,把“帐户被锁住”的勾去掉: 第5步:点击“应用”再点击“关闭”: 第6步:重新登录就可以通过验证了:

部署vc2008开发的程序(三种办法,但是我觉得这种办法最不好)

如果你编译了一个VC2008的默认的CRT/MFC的应用程序,如果目标部署电脑上没有安装相应的VC2008的动态库,当运行你的程序的时 个,会出现如下错误信息. 这是因为程序使用了基于VC2008的CRT/MFC的动态库版本. 解决这个问题,有三种方法: 1.使用静态链接库编译(缺点,生成的exe的程序过于庞大) 2.使用vcredist_x86.exe / vcredist_x64.exe 将VC2008的发行版的DLL安装在你的系统上.(缺点,只能支持发行版,调试版程序不能支持) 3.将你的

WEB项目会话集群的三种办法

web集群时session同步的3种方法 在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上, 如果session不同步的话,一个登录用户,一会是登录状态,一会又不是登录状态.所以本文就根据这种情况给出三种不同的方法来解决这个问题: 一,利用数据库同步session 在做多服务器session同步时我没有用这种方法,如果非要用这种方法的话,我想过二种方法: 1,用一个低端电脑建个数据库专门存放web服务器的sessio

HTML添加样式三种办法

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <meta http-equiv="X-UA-Compatib

Qt如何去掉按钮等控件的虚线框(焦点框)(三种办法)

方法1:可以通过代码ui->pushButton->setFocusPolicy(Qt::NoFocus)或在Qt Creator的属性列表中设置. 方法2:如果在嵌入式设备中需要通过按键切换控件,最简单的方法就是通过控件的focus来实现,就不能使用方法1          了.此时可以通过qss样式表来去掉虚线框,代码如下所示. [cpp] view plain copy ui->pushButton->setStyleSheet("outline: none&quo

第三种办法

using System;   using System.Collections.Generic;   using System.Linq;   using System.Text;   using System.Threading.Tasks;        namespace Bll   {       public class Class1       {       }   }   using System; using System.Collections.Generic; using