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限流详解 的相关文章

随机推荐

  • Docker镜像之不同服务器间迁移大法

    背景需求 有个需求 xff1a 需要将一台服务器A上的docker镜像迁移到另一台服务器B上 xff0c 并可以在服务器B上 xff0c 通过命令docker images可以查看到对应的镜像 xff08 这个镜像一般是你自己打包的镜像 x
  • CSDN云IDE浅试之体验与建议

    云IDE产品介绍 云IDE使用教程 免费使用地址 xff1a 点击 云IDE xff0c 即可开始创建工作空间啦 个人认为任何好的软件都是第一印象比较重要 从几个方面说下个人比较赞同的点 一 新建项目变的简单 体现在几个方面 xff1a 1
  • K8S异常之Unable to connect to the server: x509: certificate has expired or is not yet valid

    一 问题 xff1a k8s证书过期 root 64 nb001 kubectl get node Unable to connect to the server x509 certificate has expired or is not
  • Debian远程控制SSH和putty

    转自 http blog sina com cn s blog 6ffafb910100omeu html SSH是一种在远程传输数据时可加密的传输手段 作为远程控制的首选服务 xff0c 给数据的传输安全带来的很大的好处 在debian
  • K8S异常之Harbor证书过期处理(完整版流程)

    1 背景 如图所示 xff0c 在登录harbor仓库时 xff0c 提示证书已经过期 2 更新证书 2 1 停止harbor docker compose down span class token punctuation span ro
  • jar命令之替换已有jar包依赖库的版本

    背景 由于项目使用的是nacos1 1 4 xff0c 其依赖的fastjson的版本为1 2 58 xff0c 包含了AVD 2022 1243027和AVD 2022 1243027的 fastjson lt 61 1 2 80 反序列
  • Java项目热部署方案之IDEA-HotSwapAgent和DCEVM大法

    一 环境准备 HotSwapAgent xff08 http hotswapagent org xff09 依赖 DCEVM 而 DCEVM要求jdk版本必须对应 xff0c 如果你用的 jdk1 8 xff0c 首先需要确认安装的是jdk
  • 使用rclone将本机文件或文件夹导入minIO

    一 背景 minio大量文件上传 xff0c 通过console xff0c web端控制台上传的话是可以的 xff0c 但是文件量太大 xff0c 效率很低 xff0c 就想办法上传服务器 xff0c 然后读取服务器文件的方式进行 二 过
  • 记录一次docker容器引起的时间相差8h的问题

    一 背景 系统打印日志时间小8h xff0c 部分插入mysql的日期却大8h xff0c 简直诡异 测试时间是上午10 05 经过排查 xff0c mysql设置的时区 xff0c 链接url设置的时区都是ok的 而且有其他服务时间正常
  • c++常见面试题30道

    转自 xff1a http blog csdn net shihui512 article details 9092439 xff1b 1 new delete malloc free 关系 delete会调用对象的析构函数 和 new 对
  • __attribute__ 机制详解

    attribute 语法的来源 GNU C 的一大特色就是 attribute 机制 attribute 可以设置函数属性 xff08 Function Attribute xff09 变量属性 xff08 Variable Attribu
  • 进程和线程的理解

    一 进程和线程的概念 xff08 一句话概述 xff09 进程 xff1a 我们运行的程序通常会对应一个或多个进程 xff0c 进程是操作系统分配内存的基本单位 线程 xff1a 一个进程通常会包含一个或多个线程 xff0c 线程是操作系统
  • ubuntu中sudo apt-get install 出现 failed to fetch

    在ubuntu中 安装samba时 xff08 sudo apt get install samba xff09 出现 failed to fetch 通过以下方式成功解决了 xff1a 我直接更新了下就解决了 通过sudo apt get
  • Docker删除镜像和容器

    一 删除容器 首先需要停止所有的容器 xff08 只停止单个时把后面的变量改为image id即可 xff09 docker stop docker ps a q 删除所有的容器 xff08 只删除单个时把后面的变量改为image id即可
  • 【读书】只读经典

    本文摘自豆瓣上 八百步 的收集的资料 xff1a http book douban com review 5289501 Ch 1 xff1a 管理 1 弗雷德里克 温斯洛 泰勒著 xff0c 科学管理的基本原理 xff0c 1911年出版
  • 卸载Docker

    一 准备工作 xff1a 1 杀死docker有关的容器 xff1a docker kill docker ps a q 2 删除所有docker容器 xff1a docker rm docker ps a q 3 删除所有docker镜像
  • IDEA2020启动Tomcat控制台中文乱码解决

    IDEA2020启动Tomcat控制台中文乱码解决 1 中文乱码原因 基本上大家安装的windows系统本地语言都是选择中文 xff08 不会有人选择英文吧 xff1f 不会吧 xff1f 不会吧 xff1f xff09 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上实现限流