Spring Cloud限流详解

2023-05-16

Spring Cloud限流详解

Spring Cloud Spring Cloud 2017/12/01

在高并发的应用中,限流往往是一个绕不开的话题。本文详细探讨在Spring Cloud中如何实现限流。

Zuul上实现限流是个不错的选择,只需要编写一个过滤器就可以了,关键在于如何实现限流的算法。常见的限流算法有漏桶算法以及令牌桶算法。这个可参考https://www.cnblogs.com/LBSer/p/4083131.html,写得通俗易懂,你值得拥有,我就不拽文了。

Google Guava为我们提供了限流工具类RateLimiter,于是乎,我们可以撸代码了。

代码示例

   @Component
  

  

   public 
   class RateLimitZuulFilter extends ZuulFilter {
  

  

  


   private 
   final RateLimiter rateLimiter = RateLimiter.create(
   1000.0);
  

  

  


   @Override
  

  


   public String filterType() {
  

  

    
   return FilterConstants.PRE\_TYPE;
  

  

       }
  

  

  


   @Override
  

  


   public int filterOrder() {
  

  

    
   return Ordered.HIGHEST\_PRECEDENCE;
  

  

       }
  

  

  


   @Override
  

  


   public boolean shouldFilter() {
  

  

    
   // 这里可以考虑弄个限流开启的开关,开启限流返回true,关闭限流返回false,你懂的。
  

  

    
   return 
   true;
  

  

       }
  

  

  


   @Override
  

  


   public Object run() {
  

  

    
   try {
  

  

               RequestContext currentContext = RequestContext.getCurrentContext();
  

  

               HttpServletResponse response = currentContext.getResponse();
  

  

        
   if (!rateLimiter.
   tryAcquire()) {
  

  

                   HttpStatus httpStatus = HttpStatus.TOO\_MANY\_REQUESTS;
  

  

  

            
   response
   .setContentType(
   MediaType
   .TEXT\_PLAIN\_VALUE);
  

  

            
   response
   .setStatus(
   httpStatus
   .value());
  

  

            
   response
   .getWriter()
   .append(
   httpStatus
   .getReasonPhrase());
  

  

  

                   currentContext.setSendZuulResponse(
   false);
  

  

  

            
   throw 
   new ZuulException(
  

  

                    
   httpStatus
   .getReasonPhrase(),
  

  

                           httpStatus.
   value(),
  

  

                    
   httpStatus
   .getReasonPhrase()
  

  

                   );
  

  

               }
  

  

           } 
   catch (
   Exception e) {
  

  

        
   ReflectionUtils
   .rethrowRuntimeException(e);
  

  

           }
  

  

    
   return 
   null;
  

  

       }
  

  

   }

如上,我们编写了一个pre类型的过滤器。对Zuul过滤器有疑问的可参考我的博客:

  • Spring Cloud内置的Zuul过滤器详解:http://www.itmuch.com/spring-cloud/zuul/zuul-filter-in-spring-cloud

  • Spring Cloud Zuul过滤器详解:http://www.itmuch.com/spring-cloud/zuul/spring-cloud-zuul-filter

在过滤器中,我们使用Guava RateLimiter实现限流,如果已经达到最大流量,就抛异常。

分布式场景下的限流

以上单节点Zuul下的限流,但在生产中,我们往往会有多个Zuul实例。对于这种场景如何限流呢?我们可以借助Redis实现限流。

使用redis实现,存储两个key,一个用于计时,一个用于计数。请求每调用一次,计数器增加1,若在计时器时间内计数器未超过阈值,则可以处理任务

   if
   (!cacheDao.hasKey(TIME\_KEY)) {
  

  


   cacheDao
   .putToValue(
   TIME\_KEY, 
   0, 
   1, 
   TimeUnit
   .SECONDS);
  

  

   }       
  

  

   if
   (cacheDao.hasKey(TIME\_KEY) && cacheDao.incrBy(COUNTER\_KEY, 
   1) > 
   400) {
  

  


   // 抛个异常什么的
  

  

   }

实现微服务级别的限流

一些场景下,我们可能还需要实现微服务粒度的限流。此时可以有两种方案:

方式一:在微服务本身实现限流。

和在Zuul上实现限流类似,只需编写一个过滤器或者拦截器即可,比较简单,不作赘述。个人不太喜欢这种方式,因为每个微服务都得编码,感觉成本很高啊。

加班那么多,作为程序猿的我们,应该学会偷懒,这样才可能有时间孝顺父母、抱老婆、逗儿子、遛狗养鸟、聊天打屁、追求人生信仰。好了不扯淡了,看方法二吧。

方法二:在Zuul上实现微服务粒度的限流。

在讲解之前,我们不妨模拟两个路由规则,两种路由规则分别代表Zuul的两种路由方式。

   zuul:
  

  

     routes:
  

  

       microservice-provider-user: 
   /user/\*\*
  

  

       user2:
  

  

         url: 
   http://localhost:8000/
  

  

         path: 
   /user2/\*\*

如配置所示,在这里,我们定义了两个路由规则,microservice-provider-user以及user2,其中microservice-provider-user这个路由规则使用到Ribbon + Hystrix,走的是RibbonRoutingFilter;而user2这个路由用不上Ribbon也用不上Hystrix,走的是SipleRoutingFilter。如果你搞不清楚这点,请参阅我的博客:

  • Spring Cloud内置的Zuul过滤器详解:http://www.itmuch.com/spring-cloud/zuul/zuul-filter-in-spring-cloud
  • Spring Cloud Zuul过滤器详解:http://www.itmuch.com/spring-cloud/zuul/spring-cloud-zuul-filter

搞清楚这点之后,我们就可以撸代码了:

   @Component
  

  

   public 
   class RateLimitZuulFilter extends ZuulFilter {
  

  

  


   private 
   Map<
   String, RateLimiter> map = Maps.newConcurrentMap();
  

  

  


   @Override
  

  


   public String filterType() {
  

  

    
   return FilterConstants.PRE\_TYPE;
  

  

       }
  

  

  


   @Override
  

  


   public int filterOrder() {
  

  

    
   // 这边的order一定要大于org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter的order
  

  

    
   // 也就是要大于5
  

  

    
   // 否则,RequestContext.getCurrentContext()里拿不到serviceId等数据。
  

  

    
   return Ordered.LOWEST\_PRECEDENCE;
  

  

       }
  

  

  


   @Override
  

  


   public boolean shouldFilter() {
  

  

    
   // 这里可以考虑弄个限流开启的开关,开启限流返回true,关闭限流返回false,你懂的。
  

  

    
   return 
   true;
  

  

       }
  

  

  


   @Override
  

  


   public Object run() {
  

  

    
   try {
  

  

               RequestContext context = RequestContext.getCurrentContext();
  

  

               HttpServletResponse response = context.getResponse();
  

  

  

        
   String key = 
   null;
  

  

        
   // 对于service格式的路由,走RibbonRoutingFilter
  

  

        
   String serviceId = (
   String) context.
   get(SERVICE\_ID\_KEY);
  

  

        
   if (serviceId != 
   null) {
  

  

                   key = serviceId;
  

  

            
   map
   .putIfAbsent(
   serviceId, 
   RateLimiter
   .create(
   1000.0));
  

  

               }
  

  

        
   // 如果压根不走RibbonRoutingFilter,则认为是URL格式的路由
  

  

        
   else {
  

  

            
   // 对于URL格式的路由,走SimpleHostRoutingFilter
  

  

                   URL routeHost = context.getRouteHost();
  

  

            
   if (routeHost != 
   null) {
  

  

                
   String url = routeHost.toString();
  

  

                       key = url;
  

  

                
   map
   .putIfAbsent(
   url, 
   RateLimiter
   .create(
   2000.0));
  

  

                   }
  

  

               }
  

  

        
   RateLimiter rateLimiter = 
   map.
   get(key);
  

  

        
   if (!rateLimiter.
   tryAcquire()) {
  

  

                   HttpStatus httpStatus = HttpStatus.TOO\_MANY\_REQUESTS;
  

  

  

            
   response
   .setContentType(
   MediaType
   .TEXT\_PLAIN\_VALUE);
  

  

            
   response
   .setStatus(
   httpStatus
   .value());
  

  

            
   response
   .getWriter()
   .append(
   httpStatus
   .getReasonPhrase());
  

  

  

                   context.setSendZuulResponse(
   false);
  

  

  

            
   throw 
   new ZuulException(
  

  

                    
   httpStatus
   .getReasonPhrase(),
  

  

                           httpStatus.
   value(),
  

  

                    
   httpStatus
   .getReasonPhrase()
  

  

                   );
  

  

               }
  

  

           } 
   catch (
   Exception e) {
  

  

        
   ReflectionUtils
   .rethrowRuntimeException(e);
  

  

           }
  

  

    
   return 
   null;
  

  

       }
  

  

   }

简单讲解一下这段代码:

对于microservice-provider-user这个路由,我们可以用context.get(SERVICE_ID_KEY);获取到serviceId,获取出来就是microservice-provider-user

而对于user2这个路由,我们使用context.get(SERVICE_ID_KEY);获得是null,但是呢,可以用context.getRouteHost()获得路由到的地址,获取出来就是http://localhost:8000/。接下来的事情,你们懂的。

改进与提升

实际项目中,除以上实现的限流方式,还可能会:

一、在上文的基础上,增加配置项,控制每个路由的限流指标,并实现动态刷新,从而实现更加灵活的管理

二、基于CPU、内存、数据库等压力限流(感谢平安常浩智)提出。。

下面,笔者借助Spring Boot Actuator提供的Metrics能力进行实现基于内存压力的限流——当可用内存低于某个阈值就开启限流,否则不开启限流。

   @Component
  

  

   public 
   class RateLimitZuulFilter extends ZuulFilter {
  

  


   @Autowired
  

  


   private SystemPublicMetrics systemPublicMetrics;
  

  


   @Override
  

  


   public boolean shouldFilter() {
  

  

    
   // 这里可以考虑弄个限流开启的开关,开启限流返回true,关闭限流返回false,你懂的。
  

  

           Collection<Metric<
   \>> metrics = systemPublicMetrics.metrics();
  

  

           Optional<Metric<
   \>> freeMemoryMetric = metrics.stream()
  

  

            
   .filter(
   t 
   \-\> 
   "mem.free"
   .equals(
   t
   .getName()))
  

  

            
   .findFirst();
  

  

    
   // 如果不存在这个指标,稳妥起见,返回true,开启限流
  

  

    
   if (!freeMemoryMetric.isPresent()) {
  

  

        
   return 
   true;
  

  

           }
  

  

    
   long freeMemory = freeMemoryMetric.
   get()
  

  

            
   .getValue()
  

  

            
   .longValue();
  

  

    
   // 如果可用内存小于1000000KB,开启流控
  

  

    
   return freeMemory < 
   1000000L;
  

  

       }
  

  


   // 省略其他方法
  

  

   }

三、实现不同维度的限流,例如:

  • 对请求的目标URL进行限流(例如:某个URL每分钟只允许调用多少次)
  • 对客户端的访问IP进行限流(例如:某个IP每分钟只允许请求多少次)
  • 对某些特定用户或者用户组进行限流(例如:非VIP用户限制每分钟只允许调用100次某个API等)
  • 多维度混合的限流。此时,就需要实现一些限流规则的编排机制。与、或、非等关系。

参考文档

  • 分布式环境下限流方案的实现:http://blog.csdn.net/Justnow_/article/details/53055299
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Cloud限流详解 的相关文章

随机推荐

  • Android——RuntimePermission介绍

    1 介绍 androidM版本上 xff0c 对permission的管理做了部分改动 xff0c 针对dangerous permission xff0c 不在安装的时候给予权限 xff0c 而是在运行过程中咨询用户是否给予app响应的权
  • Android中launcherMode="singleTask"详解【android源码解析六】

    android中launcherMode有4中属性 xff1a standard 默认 xff0c singleTop xff0c singleTask和 singleInstance xff1b 网上有好多例子讲解这四种关系的 xff1a
  • Android闹钟最终版【android源码闹钟解析】

    我以前写了个复杂闹钟的demo xff0c 参见 Android闹钟 复杂版 大明进化十五 但是里面的bug有一些 xff0c 好多人留言 xff0c 所以我就看看源码 xff0c 找找原因 xff1f 顺便把源码代码整理出来 xff0c
  • Smali--Dalvik虚拟机指令语言-->【android_smali语法学习一】

    最近一周在研究rom移植 xff0c 所以就对Smali语言学习了一下 xff0c Smali语言其实就是Davlik的寄存器语言 xff1b Smali语言就是android的应用程序 apk通过apktool反编译出来的都有一个smal
  • android4.0自定义锁屏总结【android锁屏研究一】

    最近搬家了 xff0c 从北京 gt 深圳 xff0c 除了天气有点不同外 xff0c 其他的都差不多 xff0c 工作性质和以前也类似 xff01 纪念一下自己的迁移 题外话 转载请表明出处 xff1a http blog csdn ne
  • Android(6.0、7.0、8.0) popupWindow弹窗位置错乱解决方案

    问题描述 xff1a 项目中做一个底部弹窗选择地址的功能 xff0c 直接用的PopupWindow xff0c 简单方便 xff0c 但是却不慎入坑 要求效果 xff0c 如下图 xff1a 就是这么一个简单的底部弹窗 xff0c 点击家
  • android系统锁屏详解【android锁屏解析二】

    谷歌的代码写的确实不错 xff0c 我很幸运 xff0c 一开始接触代码就赶上了谷歌这个开源的系统 xff0c 让我的视野开阔了很多 xff0c 也让我看到了优秀的代码工程师写到的代码 心怀感恩之心 题记 我的有篇文章说了这个锁屏 xff0
  • <转载>创建第一个Android项目

    Android 如何创建项目 Android Hai的博客 CSDN博客 android新建项目 目录一 创建流程二 Android Studio 主窗口三 链接一 创建流程1 安装最新版 Android Studio 2 第一次打开And
  • kotlin---使用注释处理的 Android 框架

    在日常 Android 开发中 xff0c 流行着数以千计的框架帮助我们提升开发效率 使用 Kotlin 开发时仍然可以沿用这些框架 xff0c 而且和使用 Java 同样简单 本章教程将提供相关示例并重点介绍配置的差异 教程以 Dagge
  • 集合的使用——超市购物小票案例

    集合的使用 超市购物小票案例 1 定义GoodsItem类 名称 货号 单价 数量 计价单位 金额 span class token comment 定义GoodsItem类 名称 货号 单价 数量 计价单位 金额 span span cl
  • 在Eclipse中添加sun.misc.Launcher类

    研究类加载器 xff0c 用到sun misc Launcher类 xff0c 如何在Eclipse中添加呢 xff1f 启动类加载器的加载路径 URL ruls 61 sun misc Launcher getBootstrapClass
  • IllegalMonitorStateException异常 | 生产者消费者模式

    文章目录 结论先行 xff1a 生产者消费者 synchronized版生产者消费者 Lock版 结论先行 xff1a 这是JDK对这异常的定义 就是说线程没有拿到对应对象的监视器 xff0c 也就不能在监视器上完成wait或者notify
  • Warning Stopping docker.service, but it can still be activated by docker.socket

    执行 systemctl stop docker 后提示 Warning Stopping docker service but it can still be activated by docker socket 解释 xff1a 这是d
  • docker desktop stopping 问题解决

    搜了下网上一些解决问题好像比较多的是docker desktop stopped xff0c 他们是说换低版本的docker desktop解决的 xff0c 但是我的是通过更新WSL xff08 Windows Subsystem for
  • Hadoop启动

    博主用的是Cent0S XShell7使用Hadoop 1 启动Hadoop start all sh 这里会给你报出不赞同的问题 xff0c 但是也是可以使用的 xff0c 最好是采用分别运行HDFS YARN的方式来启动Hadoop 2
  • IDEA2020启动Tomcat控制台中文乱码解决

    IDEA2020启动Tomcat控制台中文乱码解决 1 中文乱码原因 基本上大家安装的windows系统本地语言都是选择中文 xff08 不会有人选择英文吧 xff1f 不会吧 xff1f 不会吧 xff1f xff09 xff0c 也就是
  • log4j和logback的比较

    一 slf4j slf4j是一系列的日志接口 xff0c 而log4j logback等则是具体实现了的日志框架 因为是接口 xff0c 所以在项目中如果你不引用log4j logback或者其它日志框架你会发现 xff0c 控制台的输出是
  • MyEclipse配置Tomcat 7

    1 打开步骤 xff1a 窗口 gt 首选项 gt MyEclipse gt Servers gt Tomcat gt Tomcat 7 x 2 配置自己本地的Tomcat 7版本 3 关闭MyEclipse自带的Tomcat服务器 4 启
  • mysql之模糊查询的方法

    Mysql模糊查询正常情况下在数据量小的时候 xff0c 速度还是可以的 xff0c 但是不容易看出查询的效率 xff0c 在数据量达到百万级 xff0c 千万级的甚至亿级时mysql查询的效率是很关键的 xff0c 也是很重要的 一 一般
  • Spring Cloud限流详解

    Spring Cloud限流详解 Spring Cloud Spring Cloud 2017 12 01 在高并发的应用中 xff0c 限流往往是一个绕不开的话题 本文详细探讨在Spring Cloud中如何实现限流 在Zuul上实现限流