zoukankan      html  css  js  c++  java
  • 使用Guava的RateLimiter做限流

    一、常见的限流算法

    目前常用的限流算法有两个:漏桶算法和令牌桶算法。

    1.漏桶算法

    漏桶算法的原理比较简单,请求进入到漏桶中,漏桶以一定的速率漏水。当请求过多时,水直接溢出。可以看出,漏桶算法可以强制限制数据的传输速度。

    2.令牌桶算法

    令牌桶算法的原理是系统以一定速率向桶中放入令牌,如果有请求时,请求会从桶中取出令牌,如果能取到令牌,则可以继续完成请求,否则等待或者拒绝服务。这种算法可以应对突发程序的请求,因此比漏桶算法好。

    在Wikipedia上,令牌桶算法是这么描述的:

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

    二、RateLimiter

    Guava中开源出来一个令牌桶算法的工具类RateLimiter,可以轻松实现限流的工作。RateLimiter对简单的令牌桶算法做了一些工程上的优化,具体的实现是SmoothBursty。需要注意的是,RateLimiter的另一个实现SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也许是出于简单起见,RateLimiter中的时间窗口能且仅能为1S,如果想搞其他时间单位的限流,只能另外造轮子。

    RateLimiter有一个有趣的特性是[前人挖坑后人跳],也就是说RateLimiter允许某次请求拿走了超出剩余令牌数的令牌,但是下一次请求将为此付出代价,一直等到令牌亏空补上,并且桶中有足够本次请求使用的令牌为止。这里面就涉及到一个权衡,是让前一次请求干等到令牌够用才走掉呢,还是让它走掉后面的请求等一等呢?Guava的设计者选择的是后者,先把眼前的活干了,后面的事后面再说。

    测试代码:

    public class RateLimiterMain {
        public static void main(String[] args) {
            RateLimiter rateLimiter = RateLimiter.create(2);
            System.out.println(rateLimiter.acquire(5));
            System.out.println(rateLimiter.acquire(2));
            System.out.println(rateLimiter.acquire(1));
        }
    }
    

    输出内容:

    0.0
    2.496889
    0.992149
    

    可以看出,令牌桶每秒只能产生2个令牌,我们可以第一次取出5个,但是第二次再去取令牌的时候,需要等2.5s,也就是第一次令牌取完后,需要等2.5s才能取到令牌。同样的,第三次取1个令牌的时候,也需要等待第二次的1s的时间。也就是,取的速率可以超过令牌产生的速率,但是下一次再次去取的时候,需要阻塞等待。

    当然也可以使用tryAcquire来非阻塞的获取,可以实时返回结果。另外tryAcquire也可以传入参数,也就是等待的时间,超时直接返回false。这点等同于常见的lock,tryLock。

    三、并发控制Semapphore

    一般来说,在网关系统中,还有一个参数叫并发控制,就是某一个资源可以被同时访问的个数。这种情况下,我们可以使用Semaphore来控制。

    Semaphore不同于互斥锁。互斥锁是某个资源只能支持同时一个访问,而Semaphore可以支持多个访问,但是加上了总数的控制。

    四、实战

    4.1 在pom中加入guava依赖

    <dependency>
    	<groupId>com.google.guava</groupId>
    	<artifactId>guava</artifactId>
    	<version>18.0</version>
    </dependency>
    

    把限流服务封装到一个类中AccessLimitService,提供tryAcquire()方法,用来尝试获取令牌,返回true表示获取到,如下所示:

    @Service
    public class AccessLimitService {
    
        //每秒只发出5个令牌
        RateLimiter rateLimiter = RateLimiter.create(5.0);
    
        /**
         * 尝试获取令牌
         * @return
         */
        public boolean tryAcquire(){
            return rateLimiter.tryAcquire();
        }
    }
    

    调用方是个普通的controller,每次收到请求的时候都尝试去获取令牌,获取成功和失败打印不同的信息,如下:

    @Controller
    public class HelloController {
    
        private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Autowired
        private AccessLimitService accessLimitService;
    
        @RequestMapping("/access")
        @ResponseBody
        public String access(){
            //尝试获取令牌
            if(accessLimitService.tryAcquire()){
                //模拟业务执行500毫秒
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                return "aceess success [" + sdf.format(new Date()) + "]";
            }else{
                return "aceess limit [" + sdf.format(new Date()) + "]";
            }
        }
    }
  • 相关阅读:
    解决mongod端口占用问题
    MongoDB操作
    ssh 带密码私钥 输入密码
    sequence
    使用plsql导入dmp文件缺少imp*.exe
    oracle查看锁表进程,杀掉锁表进程
    oracle
    常用shell命令
    ORA-03113: end-of-file on & ORA-07445
    ORA-39126: Worker unexpected fatal error in KUPW$WORKER.PUT_DDLS
  • 原文地址:https://www.cnblogs.com/sea520/p/11541789.html
Copyright © 2011-2022 走看看