学习使用Guava RateLimiter

目录

   一、引入

  二、快速上手

    2.1、导入依赖

    2.2、第一个示例

  三、获取许可

    3.1、非阻塞式获取

    3.2、阻塞式获取

  四、存在的问题

    4.1、集群限流

  

 

 

 

 

 

一、引入

  在程序中,我们经常会用到限流,比如接口调用的频率限制。

  server端提供api给clients进行调用,如果某个client调用api的频率过高,造成server端的负载升高,超过server端的上限,那么很有可能导致server端不可用,从而影响所有的调用方。

  限制频率,可以在client端做,也可以在server端做,但是目前一般都是在server端做,同时client一般也会调整调用频率。

  至于怎么限流,网上很多的资料,这里就不阐述了,主要介绍一下使用Guava RateLimiter来实现限流。

 

二、快速上手

2.1、导入依赖

  Guava RateLimiter是Guava的一部分,所以直接导入Guava的依赖即可。

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>28.0-jre</version>
</dependency>

  

2.2、第一个示例

  简单看一下下面的用法

package cn.ganlixin.guava;

import com.google.common.util.concurrent.RateLimiter;
import org.junit.Test;

import java.time.LocalTime;

public class UserRateLimiter {

    @Test
    public void testSimple() throws InterruptedException {
        // 创建一个限流器(每秒限制流量为5个)
        RateLimiter rateLimiter = RateLimiter.create(5.0);

        for (int i = 0; i < 10; i++) {
            if (rateLimiter.tryAcquire()) {
                System.out.println(LocalTime.now() + " 通过");
            } else {
                System.out.println(LocalTime.now() + " 被限流");
            }
            Thread.sleep(100L);
        }
    }
}

  上面的示例代码中,创建了一个限流器,每秒最多允许5个流量,这个流量有个专业的称呼,叫“许可”(准许调用),也就是5个许可。

  调用tryAcquire()会尝试获取1个许可,如果获取到了,返回true,表示未被限流;否则返回false,表示没有获取到许可,被限流了。

  如此就可以实现流量控制了。

 

三、获取许可

  获取许可,有非阻塞和阻塞式获取。

if (获得许可) {
    执行代码块 1
} else {
    执行代码块 2
}

  非阻塞式:尝试获取许可,如果获取到许可,则执行代码块1,如果没有获取到就认为被限流,则执行代码块2;

  阻塞式:尝试获取许可,获取到则执行代码块1,没有获取到,则阻塞,等待获取到许可后,执行代码块1,注意不会执行代码块2;

    

3.1、非阻塞获取许可

  前面使用介绍了,可以使用RateLimiter类可以使用create方法,创建多个许可,每个许可就是一个令牌,拿到令牌,才可以执行操作(通过),否则就是被限流(阻止)。

  使用tryAcquire()方法,是不阻塞的尝试获取令牌,但是他有多个重载方法,有不同的参数。

// 尝试获取1个许可,如果获取到,则返回true,否则返回false
boolean tryAcquire();

// 尝试获取多个许可,如果获取到,则返回true,否则返回false
boolean tryAcquire(int permits)
// 示例tryAcquire(3)

// 在timeout时间内,尝试获取1个许可,如果获取到,则返回true,否则返回false
tryAcquire(Duration timeout);
// 示例:tryAcquire(Duration.ofSeconds(3))
tryAcquire(long timeout, TimeUnit unit);
// 示例:tryAcquire(3, TimeUnit.SECONDS);

// 在timeout时间内,尝试获取多个许可,如果获取到,则返回true,否则返回false
tryAcquire(int permits, Duration timeout)
tryAcquire(int permits, long timeout, TimeUnit unit)

  

3.2、阻塞式获取许可

  阻塞式获取,调用的方法是acquire

package cn.ganlixin.guava;

import com.google.common.util.concurrent.RateLimiter;
import org.junit.Test;

import java.time.LocalTime;

public class UserRateLimiter {

    @Test
    public void testAcquire() {
        RateLimiter rateLimiter = RateLimiter.create(2);

        for (int i = 0; i < 10; i++) {
            double sleep = rateLimiter.acquire();
            System.out.println("now: " + LocalTime.now() + "  sleep: " + sleep);
        }
    }
}

  运行输出如下:

now: 16:59:51.879  sleep: 0.0
now: 16:59:52.289  sleep: 0.403329
now: 16:59:52.784  sleep: 0.492976
now: 16:59:53.285  sleep: 0.499409
now: 16:59:53.789  sleep: 0.498335
now: 16:59:54.285  sleep: 0.494628
now: 16:59:54.786  sleep: 0.49857
now: 16:59:55.289  sleep: 0.496816
now: 16:59:55.784  sleep: 0.494352
now: 16:59:56.288  sleep: 0.499635

  

  acquire也有重载方法:

// 尝试阻塞获取多个许可
acquire(int permits)

  

四、存在的问题

  Guava解决的问题,一般都是单机的,以限流为例,使用guava限流,只做到了单机限流,但是我们的服务一般都会由多台机器(集群),虽然我们可以通过计算单机和集群的比例,来设置限流数量,但是有几个问题:

  1、机器增减时,要保证总流量保持不变,就需要修改每一台机器的流量限制,这个不是很方便;

  2、Guava的限流器,并不是公平的,至于什么是公平和非公平,可以参考:

4.1 集群限流

  guava的RateLimiter,限流是通过获取“许可”来控制流量的,只不过是单机管理自己的许可。

  如果将所有机器的“许可”汇集到一个地方,所有机器都从这个地方获取许可,不就可以实现集群限流吗?

  可以使用redis来保存所有机器的“许可”。

  这样做,可以实现集群限流,但不能保证单机的流量限制,其实对于现在的微服务来说,请求被平均分给所有机器,是服务平台的问题,可以不用关心这个问题。

 

 

 

  

 

  

posted @ 2020-02-09 15:01  寻觅beyond  阅读(2770)  评论(0编辑  收藏  举报
返回顶部