• 学习使用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来保存所有机器的“许可”。

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

      

      

  • 相关阅读:
    Netty源码解析 -- 内存对齐类SizeClasses
    Netty源码解析 -- 零拷贝机制与ByteBuf
    Netty源码解析 -- ChannelOutboundBuffer实现与Flush过程
    Netty源码解析 -- ChannelPipeline机制与读写过程
    Oracle体系结构概述与SQL解析剖析
    SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理|前后端分离(下)----筑基后期
    SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理(上)----筑基中期
    shiro入门学习--授权(Authorization)|筑基初期
    shiro入门学习--使用MD5和salt进行加密|练气后期
    Shiro入门学习---使用自定义Realm完成认证|练气中期
  • 原文地址:https://www.cnblogs.com/-beyond/p/12287201.html
Copyright © 2020-2023  润新知