令牌桶限流总结

一、引入

限流 是对某一时间窗口内的请求数进行限制,保证系统的可用性和稳定性,防止因流量暴增而导致的系统运行慢或宕机。常用的限流算法有令牌桶和漏桶算法,Google的Guava中的RateLimiter使用令牌桶。

开发高并发系统,保护系统手段:缓存、降级和限流。

1、缓存:提升系统访问速度和增大系统处理容量。
2、降级:对非核心服务和页面进行降级,主动关闭部分功能,释放服务器资源,保证核心业务可用。
3、限流:对并发访问/请求限速、给出一个限流个数控制洪峰。

二、令牌桶和漏桶算法区别

令牌桶和漏桶算法区别:
1、内容上:令牌桶算法是按固定速率生成令牌,请求能从桶中拿到令牌就执行,否则执行失败。漏桶算法是任务进桶速率不限,但是出桶的速率是固定的,超出桶大小的的任务丢弃,也就是执行失败。
2、突发流量适应性上:令牌桶是允许一次取出多个token,只要有令牌就可以处理任务,在桶容量上允许处理突发流量。令牌桶限制的是流入的速率且灵活。而漏桶算法出桶的速率固定,有突发流量也只能按流出的速率处理任务。漏桶算法是平滑流入的速率,限制流出速率。

令牌桶算法:
在这里插入图片描述

图示:
1、按固定速率生成令牌。需判断桶满状态。
2、桶:存放token
3、request过来从桶里取token,取到token处理,否则丢弃返回失败。

漏桶算法图示(不展开说)
在这里插入图片描述

三、Guava中RateLimiter用法及源码分析

1、Google的令牌桶RateLimiter用法

单机的限流,是JVM级别的限流,所有的令牌生成都是在内存中)

RateLimiter rateLimiter = RateLimiter.create(2);//每秒生成两个permits(token)
//用给定的吞吐量创建一个RateLimiter,通常是QPS。
//1次尝试获取1个permits,拿不到立即返回false(超时时间0s)
rateLimiter.tryAcquire()

//一次可以拿多个permits,是阻塞的直到一个permit可用。
rateLimiter.acquire(int nums)

与juc中的semaphore有区别,信号量获取acquire之后需要释放release。令牌不需要释放。

2、RateLimiter源码简单分析:

RateLimiter是一个抽象类,限流器有两个实现类:1、SmoothBursty;2、SmoothWarmingUp

SmoothBursty是以稳定的速度生成permit。SmoothWarmingUp是渐进的生成,最终达到最大值趋于稳定。

偿还机制:当前请求的债务(请求的令牌大于限流器存储的令牌数)由下一个请求来偿还(上个请求亏欠的令牌,下个请求需要等待亏欠令牌生产出来以后才能被授权)acquire多个token时生效。

RateLimiter中重要参数:

1、stableIntervalMircos //稳定生成令牌的时间间隔。

2、maxBurstSeconds  //1秒生产的令牌。

3、maxPermits //最大存储令牌数。

4、nextFreeTicketMicros //下个请求可被授权令牌的时间(不管请求多少令牌),实现当前债务由下一个请求来偿还机制关键。

5、storedPermits //已存储的令牌,生产过剩的令牌存储小于等于maxPermits,是应对突发流量的请求的关键。
//从RateLimiter中获取一个permit,阻塞直到请求可以获得为止。
public double acquire(){
	Return acquire(1);
}
//从RateLimiter中获取指定数量的permits,阻塞直到请求可以获得为止
public double acquire(int permits) {
	//计算获得这些数量需等待时间
        long microsToWait = reserve(permits);
		//不可被打断的等待
        stopwatch.sleepMicrosUninterruptibly(microsToWait);
		//单位转换为秒
        return 1.0 * microsToWait / SECONDS.toMicros(1L);
    }
//预订给定数量的permits来使用,计算需要这些数量permits等待时间。
final long reserve(int permits) {
		//校验负数
        checkPermits(permits);
		//抢占锁,这里的锁使用单例模式获得
        synchronized (mutex()) {
			//计算等待时间
            return reserveAndGetWaitLength(permits, stopwatch.readMicros());
        }
}
//具体计算等待时间的逻辑(继承上一次债务,并且透支本次所需要的所有permits)
//注意这里返回的是时间点
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
		
		//	同步时间轴
        resync(nowMicros); 
        //	继承上次债务
        long returnValue = nextFreeTicketMicros;
		//	跟桶内存储量比,本次可以获取到的permit数量,如果存储的permit大于本次需要的permit数量则此处是0,否则是一个正数
        double storedPermitsToSpend = min(requiredPermits, this.storedPermits); 
        
        //	还缺少的permits数量
        double freshPermits = requiredPermits - storedPermitsToSpend;    

        //	计算需要等待的时间(微秒)
        long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long) (freshPermits * stableIntervalMicros);    
		 //	继承上一次债务时间点+这次需要等待的时间,让下一次任务去等待
        this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
        //	减去本次消费的permit数
        this.storedPermits -= storedPermitsToSpend;    
        //	本次只需要等待到上次欠债时间点即可
        return returnValue; 
}

参考:
1、https://www.cnblogs.com/cjsblog/p/9379516.html有令牌桶源码分析
2、https://blog.csdn.net/chen888999/article/details/82254694 有时序图
3、https://www.jianshu.com/p/226c7907905c 帮助以上做理解

最后强调:
1、单机的限流,是JVM级别的限流,所有的令牌生成都是在内存中
2、令牌桶可以应对一定程度突发流量,漏桶则不行。因为一次可取多个storedPermits,取出permits数量不受限。

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值