RateLimiter是guava中concurrent包下的一个限流工具类,使用了令牌桶算法。下面简单的介绍一下。

 

一、RateLimiter API

修饰符

方法

描述

double

acquire()

从RateLimiter获取一个许可,该方法会被阻塞知道获取到请求,阻塞动作,返回的double是从阻塞到获取到令牌等待的时间。

double

acquire(int permits)

从RateLimiter获取指定许可数,该方法会被阻塞知道获取到请求

static

RateLimiter

create(double permitsPerSecond)

根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)

static

RateLimiter

create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)

根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数回平稳的增长知道预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)

double

getRate()

返回RateLimiter配置中的稳定速率,该速率单位是每秒多少许可数

void

setRate(double permitsPerSecond)

更新RateLimite的稳定速率,参数permit是PerSecond由构造RateLimiter的工厂方法提供。

String

toString()

返回对象的字符表现形式

boolean

tryAcquire()

从RateLimiter获取许可,如果该许可在无延迟下的情况下立即获取得到的话

boolean

tryAcquire(int permits)

从RateLimiter获取许可数,如果该许可数可以在无延迟的情况下立即获取得到的话

boolean

tryAcquire(int permits, long timeout, TimeUnit unit)

从RateLimiter获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout过期之前获取得到许可数的话,那么立即返回false(无需等待)

boolean

tryAcquire(long timeout, TimeUnit unit)

从RateLimiter获取许可如果该许可可以不超过timeout的时间内获取得到的话,或者如果无法在timeout过期之前获取得到许可的话,那么立即返回false(无需等待)

 

二、应用示例

需求:我们需要将流量限制在5QPS范围内,为了这个目的我们来看看该怎么弄。

定义一个interface,AccessLimitService:

public interface AccessLimitService {

    boolean tryAcquire();

    double getRate();

}

 

定义实现类AccessLimitServiceImpl:

@Service
public class AccessLimitServiceImpl implements AccessLimitService {

    //每秒发出5个令牌
    private RateLimiter rateLimiter = RateLimiter.create(5.0);

    /**
     * 尝试获取令牌
     * @Return
     */
    public boolean tryAcquire() {
        //这里使用的是tryAcquire而不是acquire方法,快速失败避免耗尽所有资源
        return rateLimiter.tryAcquire(); 
    }

    public double getRate() {
        return rateLimiter.getRate();
    }
}

接口类:


@Slf4j
@RestController
@RequestMapping("/flow/limit")
public class LimitController extends BaseController {
    
    @Autowired
    private AccessLimitService accessLimitService;

    @GetMapping(value = "/get")
    public String getInfo() {

        return "ok";
    }

    @GetMapping(value = "/access")
    public String access() {
        //尝试获取令牌,如果没有请求到立即返回false
        if (accessLimitService.tryAcquire()) {
            return "access success [" + System.currentTimeMillis() + "]";
        } else {
            System.out.println(accessLimitService.getRate());

            return "access limit [" + System.currentTimeMillis() + "]";

        }
    }
}

单元测试:

import com.google.common.util.concurrent.RateLimiter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class FlowLimitTest {

    private RestTemplate restTemplate;

    private String ip_dev = "localhost:8101";

    private RateLimiter rateLimiter;

    ExecutorService fixedThreadPool;


    @Before
    public void setup() {
        restTemplate = new RestTemplate();
        ((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setConnectTimeout(60000);
        ((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setReadTimeout(60000);

    }

    @After
    public void destroy() {
    }

    @Test
    public void flowLimitTest() throws Exception {

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

        /**
         * access success [1557057930408]
         access limit [1557057930399]
         access limit [1557057930399]
         access success [1557057930409]
         access success [1557057930408]
         access success [1557057930409]
         access success [1557057930408]
         access limit [1557057930399]
         access success [1557057930409]
         access limit [1557057930399]
         */
        for (int i = 0; i < 10; i++) {
            fixedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(requestApi());
                }
            });
        }

        fixedThreadPool.shutdown();
        fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
    }

    private String requestApi() {
        StringBuilder url = new StringBuilder();

        url.append("http://").append(ip_dev).append("/flow/limit/access");

        HttpEntity<String> httpEntity = new HttpEntity<String>(null, null);
        ResponseEntity<String> result = null;

        try {
            result = restTemplate.exchange(url.toString(), HttpMethod.GET, httpEntity, String.class);
            if (result.getStatusCode().equals(HttpStatus.OK)) {

                return result.getBody();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

可以看到结果,10个请求,6个通过,4个被限制,通过进一步测试发现create大小为n的流量,但是在并发的过程中实际可以通过n+1个请求,其他的则被限制:

access success [1557057930408]
access limit [1557057930399]
access limit [1557057930399]
access success [1557057930409]
access success [1557057930408]
access success [1557057930409]
access success [1557057930408]
access limit [1557057930399]
access success [1557057930409]
access limit [1557057930399]

    RateLimiter是比较常用的限流的服务,但是在微服务的场景中只有限流是不够的,同时还要保证在下游系统不可用的时,本身系统的可用性,那么就要考虑到熔断和降级了,后面将说到这些。

 

Author:忆之独秀

Email:leaguenew@qq.com

注明出处:https://blog.csdn.net/lavorange/article/details/95742831

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐