• Guava-RateLimiter详解


    简介: 常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取令牌,只有取到令牌的请求才会被成功响应,获取的方式有两种:阻塞等待令牌或者取不到立即返回失败,下图来自网上: ratelimite原理图 本次实战,我们用的是guava的RateLimiter,场景是spring mvc在处理请求时候,从桶中申请令牌,申请到了就成功响应,申请不到时直接返回失败。

    常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取令牌,只有取到令牌的请求才会被成功响应,获取的方式有两种:阻塞等待令牌或者取不到立即返回失败,下图来自网上:

    img_6066ccb709d7c047dbbd866dd40295e9.png

    ratelimite原理图

    本次实战,我们用的是guava的RateLimiter,场景是spring mvc在处理请求时候,从桶中申请令牌,申请到了就成功响应,申请不到时直接返回失败。

    实例

    1、添加guava jar包

    1. <dependency>
    2. <groupId>com.google.guava</groupId>
    3. <artifactId>guava</artifactId>
    4. <version>18.0</version>
    5. </dependency>

    2、AccessLimitService.java 限流服务封装到一个类中AccessLimitService,提供tryAcquire()方法,用来尝试获取令牌,返回true表示获取到

    1. @Service
    2. public class AccessLimitService {
    3. //每秒只发出5个令牌
    4. RateLimiter rateLimiter = RateLimiter.create(5.0);
    5. /**
    6. * 尝试获取令牌
    7. * @return
    8. */
    9. public boolean tryAcquire(){
    10. return rateLimiter.tryAcquire();
    11. }
    12. }

    3、Controller层每次收到请求的时候都尝试去获取令牌,获取成功和失败打印不同的信息

    1. @Controller
    2. public class HelloController {
    3. private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    4. @Autowired
    5. private AccessLimitService accessLimitService;
    6. @RequestMapping("/access")
    7. @ResponseBody
    8. public String access(){
    9. //尝试获取令牌
    10. if(accessLimitService.tryAcquire()){
    11. //模拟业务执行500毫秒
    12. try {
    13. Thread.sleep(500);
    14. }catch (InterruptedException e){
    15. e.printStackTrace();
    16. }
    17. return "aceess success [" + sdf.format(new Date()) + "]";
    18. }else{
    19. return "aceess limit [" + sdf.format(new Date()) + "]";
    20. }
    21. }
    22. }

    4、测试:十个线程并发访问接口

    1. public class AccessClient {
    2. ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
    3. /**
    4. * get请求
    5. * @param realUrl
    6. * @return
    7. */
    8. public static String sendGet(URL realUrl) {
    9. String result = "";
    10. BufferedReader in = null;
    11. try {
    12. // 打开和URL之间的连接
    13. URLConnection connection = realUrl.openConnection();
    14. // 设置通用的请求属性
    15. connection.setRequestProperty("accept", "*/*");
    16. connection.setRequestProperty("connection", "Keep-Alive");
    17. connection.setRequestProperty("user-agent",
    18. "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
    19. // 建立实际的连接
    20. connection.connect();
    21. // 定义 BufferedReader输入流来读取URL的响应
    22. in = new BufferedReader(new InputStreamReader(
    23. connection.getInputStream()));
    24. String line;
    25. while ((line = in.readLine()) != null) {
    26. result += line;
    27. }
    28. } catch (Exception e) {
    29. System.out.println("发送GET请求出现异常!" + e);
    30. e.printStackTrace();
    31. }
    32. // 使用finally块来关闭输入流
    33. finally {
    34. try {
    35. if (in != null) {
    36. in.close();
    37. }
    38. } catch (Exception e2) {
    39. e2.printStackTrace();
    40. }
    41. }
    42. return result;
    43. }
    44. public void access() throws Exception{
    45. final URL url = new URL("http://localhost:8080/guavalimitdemo/access");
    46. for(int i=0;i<10;i++) {
    47. fixedThreadPool.submit(new Runnable() {
    48. public void run() {
    49. System.out.println(sendGet(url));
    50. }
    51. });
    52. }
    53. fixedThreadPool.shutdown();
    54. fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
    55. }
    56. public static void main(String[] args) throws Exception{
    57. AccessClient accessClient = new AccessClient();
    58. accessClient.access();
    59. }
    60. }

    部分请求由于获取的令牌可以成功执行,其余请求没有拿到令牌,我们可以根据实际业务来做区分处理。还有一点要注意,我们通过RateLimiter.create(5.0)配置的是每一秒5枚令牌,但是限流的时候发出的是6枚,改用其他值验证,也是实际的比配置的大1。

    以上就是快速实现限流的实战过程,此处仅是单进程服务的限流,而实际的分布式服务中会考虑更多因素,会复杂很多。


    RateLimiter方法摘要

    修饰符和类型方法和描述
    doubleacquire() 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求
    doubleacquire(int permits)从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求
    static RateLimitercreate(double permitsPerSecond)根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)
    static RateLimitercreate(double permitsPerSecond, long warmupPeriod, TimeUnit unit)根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
    doublegetRate()返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数
    voidsetRate(double permitsPerSecond)更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。
    StringtoString()返回对象的字符表现形式
    booleantryAcquire()从RateLimiter 获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话
    booleantryAcquire(int permits)从RateLimiter 获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话
    booleantryAcquire(int permits, long timeout, TimeUnit unit)从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)
    booleantryAcquire(long timeout, TimeUnit unit)从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)
    • 举例来说明如何使用RateLimiter,想象下我们需要处理一个任务列表,但我们不希望每秒的任务提交超过两个:
    1. //速率是每秒两个许可
    2. final RateLimiter rateLimiter = RateLimiter.create(2.0);
    3. void submitTasks(List tasks, Executor executor) {
    4. for (Runnable task : tasks) {
    5. rateLimiter.acquire(); // 也许需要等待
    6. executor.execute(task);
    7. }
    8. }

    官方文档:http://ifeve.com/guava-ratelimiter

  • 相关阅读:
    Java中的ORM框架——myBatis
    微信公众号推文怎么做?
    内置第三方apk总结
    【后端】Python中的Web开发框架Flask入门hello world;几个案例让你迅速入门Python Flask
    帮助命令---学习Linux命令的第一步
    带看123456
    npm i 报错:Cannot read properties of null (reading ‘refs‘)
    【沐风老师】3DMAX路径拖尾光线刀光效果插件GhostTrails教程
    c++ 中头文件
    chales 重写/断点/映射/手机代理/其他主机代理
  • 原文地址:https://blog.csdn.net/qq_32907195/article/details/133931740