Spring Cloud限流思路及解决方案

转自: http://blog.csdn.net/zl1zl2zl3/article/details/78683855

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

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

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

代码示例

  1. @Component
  2. public class RateLimitZuulFilter extends ZuulFilter {
  3.    private final RateLimiter rateLimiter = RateLimiter.create(1000.0);
  4.    @Override
  5.    public String filterType() {
  6.        return FilterConstants.PRE_TYPE;
  7.    }
  8.    @Override
  9.    public int filterOrder() {
  10.        return Ordered.HIGHEST_PRECEDENCE;
  11.    }
  12.    @Override
  13.    public boolean shouldFilter() {
  14.        // 这里可以考虑弄个限流开启的开关,开启限流返回true,关闭限流返回false,你懂的。
  15.        return true;
  16.    }
  17.    @Override
  18.    public Object run() {
  19.        try {
  20.            RequestContext currentContext = RequestContext.getCurrentContext();
  21.            HttpServletResponse response = currentContext.getResponse();
  22.            if (!rateLimiter.tryAcquire()) {
  23.                HttpStatus httpStatus = HttpStatus.TOO_MANY_REQUESTS;
  24.                response.setContentType(MediaType.TEXT_PLAIN_VALUE);
  25.                response.setStatus(httpStatus.value());
  26.                response.getWriter().append(httpStatus.getReasonPhrase());
  27.                currentContext.setSendZuulResponse(false);
  28.                throw new ZuulException(
  29.                        httpStatus.getReasonPhrase(),
  30.                        httpStatus.value(),
  31.                        httpStatus.getReasonPhrase()
  32.                );
  33.            }
  34.        } catch (Exception e) {
  35.            ReflectionUtils.rethrowRuntimeException(e);
  36.        }
  37.        return null;
  38.    }
  39. }

如上,我们编写了一个 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

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

分布式场景下的限流

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

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

  1. if(!cacheDao.hasKey(TIME_KEY)) {
  2.    cacheDao.putToValue(TIME_KEY, 0, 1, TimeUnit.SECONDS);
  3. }      
  4. if(cacheDao.hasKey(TIME_KEY) && cacheDao.incrBy(COUNTER_KEY, 1) > 400) {
  5.    // 抛个异常什么的
  6. }

实现微服务级别的限流

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

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

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

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

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

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

  1. zuul:
  2.  routes:
  3.    microservice-provider-user: /user/**
  4.    user2:
  5.      url: http://localhost:8000/
  6.      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

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

  1. @Component
  2. public class RateLimitZuulFilter extends ZuulFilter {
  3.    private Map<String, RateLimiter> map = Maps.newConcurrentMap();
  4.    @Override
  5.    public String filterType() {
  6.        return FilterConstants.PRE_TYPE;
  7.    }
  8.    @Override
  9.    public int filterOrder() {
  10.        // 这边的order一定要大于org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter的order
  11.        // 也就是要大于5
  12.        // 否则,RequestContext.getCurrentContext()里拿不到serviceId等数据。
  13.        return Ordered.LOWEST_PRECEDENCE;
  14.    }
  15.    @Override
  16.    public boolean shouldFilter() {
  17.        // 这里可以考虑弄个限流开启的开关,开启限流返回true,关闭限流返回false,你懂的。
  18.        return true;
  19.    }
  20.    @Override
  21.    public Object run() {
  22.        try {
  23.            RequestContext context = RequestContext.getCurrentContext();
  24.            HttpServletResponse response = context.getResponse();
  25.            String key = null;
  26.            // 对于service格式的路由,走RibbonRoutingFilter
  27.            String serviceId = (String) context.get(SERVICE_ID_KEY);
  28.            if (serviceId != null) {
  29.                key = serviceId;
  30.                map.putIfAbsent(serviceId, RateLimiter.create(1000.0));
  31.            }
  32.            // 如果压根不走RibbonRoutingFilter,则认为是URL格式的路由
  33.            else {
  34.                // 对于URL格式的路由,走SimpleHostRoutingFilter
  35.                URL routeHost = context.getRouteHost();
  36.                if (routeHost != null) {
  37.                    String url = routeHost.toString();
  38.                    key = url;
  39.                    map.putIfAbsent(url, RateLimiter.create(2000.0));
  40.                }
  41.            }
  42.            RateLimiter rateLimiter = map.get(key);
  43.            if (!rateLimiter.tryAcquire()) {
  44.                HttpStatus httpStatus = HttpStatus.TOO_MANY_REQUESTS;
  45.                response.setContentType(MediaType.TEXT_PLAIN_VALUE);
  46.                response.setStatus(httpStatus.value());
  47.                response.getWriter().append(httpStatus.getReasonPhrase());
  48.                context.setSendZuulResponse(false);
  49.                throw new ZuulException(
  50.                        httpStatus.getReasonPhrase(),
  51.                        httpStatus.value(),
  52.                        httpStatus.getReasonPhrase()
  53.                );
  54.            }
  55.        } catch (Exception e) {
  56.            ReflectionUtils.rethrowRuntimeException(e);
  57.        }
  58.        return null;
  59.    }
  60. }

简单讲解一下这段代码:

对于 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 能力进行实现基于内存压力的限流——当可用内存低于某个阈值就开启限流,否则不开启限流。

  1. @Component
  2. public class RateLimitZuulFilter extends ZuulFilter {
  3.    @Autowired
  4.    private SystemPublicMetrics systemPublicMetrics;
  5.    @Override
  6.    public boolean shouldFilter() {
  7.        // 这里可以考虑弄个限流开启的开关,开启限流返回true,关闭限流返回false,你懂的。
  8.        Collection<Metric<?>> metrics = systemPublicMetrics.metrics();
  9.        Optional<Metric<?>> freeMemoryMetric = metrics.stream()
  10.                .filter(t -> "mem.free".equals(t.getName()))
  11.                .findFirst();
  12.        // 如果不存在这个指标,稳妥起见,返回true,开启限流
  13.        if (!freeMemoryMetric.isPresent()) {
  14.            return true;
  15.        }
  16.        long freeMemory = freeMemoryMetric.get()
  17.                .getValue()
  18.                .longValue();
  19.        // 如果可用内存小于1000000KB,开启流控
  20.        return freeMemory < 1000000L;
  21.    }
  22.    // 省略其他方法
  23. }

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

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

参考文档

  • 分布式环境下限流方案的实现:http://blog.csdn.net/Justnow_/article/details/53055299

原文地址:https://www.cnblogs.com/xifenglou/p/8519700.html

时间: 2024-10-04 22:23:43

Spring Cloud限流思路及解决方案的相关文章

Spring Cloud Alibaba 新一代微服务解决方案

本篇是「跟我学 Spring Cloud Alibaba」系列的第一篇, 每期文章会在公众号「架构进化论」进行首发更新,欢迎关注. 1.Spring Cloud Alibaba 是什么 Spring Cloud Alibaba 是阿里巴巴提供的微服务开发一站式解决方案,是阿里巴巴开源中间件与 Spring Cloud 体系的融合. 马老师左手双十一,右手阿里开源组件,不仅占据了程序员的购物车,还要攻占大家的开发工具. 先说说 Spring Cloud 提起微服务,不得不提 Spring Clou

spring cloud微服务快速教程之(九) Spring Cloud Alibaba--sentinel-限流、熔断降级

0.前言 sentinel的限流.降级功能强大,可以在控制面板中任意制定规则,然后推送到微服务中: 可以根据URL单独制定规则,也可以根据资源名批量制定规则: 需要注意的地方是:1.GITHUB文件在国外的亚马逊服务器已经彻底被墙,无法下载,只能想法找国内的分享,自求多福 2.控制面板制定的规则只保存在内存中,重启就会消失,需要配合其他方式实现持久化存储规则,这点在生产环境中需要注意 1.运行sentinel sentinel只是一个JAR包,下载下来后,直接命令运行该JAR就行,默认的端口是8

限流实现与解决方案

https://blog.csdn.net/qq_32447301/article/details/86659474 一.限流操作: 为什么限流,是防止用户恶意刷新接口,因为部署在外部服务器,并且我们采用websocket的接口实现的,公司没有对硬件升级,导致程序时长崩溃,为了解决这个问题,请教公司的大佬,提出一个方案,限流操作.但是最后找到原因所在,解决了,吞吐量1万6左右,用的测试服务器,进行测试的,我开发的笔记本进行压测,工具是Jmeter,结果我的电脑未响应,卡了,服务器还没有挂.限流那

Spring Cloud Alibaba 之 Sentinel 限流规则和控制台实例

这一节我们通过一个简单的实例,学习Sentinel的基本应用. 一.Sentinel 限流核心概念 在学习Sentinel的具体应用之前,我们先来了解一下Sentinel中两个核心的概念,资源和规则. 资源 资源 是 Sentinel 中的核心概念之一.既然是限流,或者系统保护,那么是针对什么做限流?保护的是什么?就是我们所说的资源. 其实 Sentinel 对资源的定义,和并发编程中 Synchronized的使用很类似,这里的资源,可以是服务里的方法,也可以是一段代码. 规则 定义了资源之后

断路器Hystrix与Turbine集群监控-Spring Cloud学习第三天

文章大纲 一.Hystrix基础介绍二.断路器Hystrix简单使用三.自定义Hystrix请求命令四.Hystrix的服务降级与异常处理五.Hystrix的请求缓存与请求合并六.Hystrix仪表盘与Turbine集群监控七.项目源码与参考资料下载八.参考文章 一.Hystrix基础介绍 1. Hystrix简介   一个用户管理项目,里边就三个功能:用户注册.用户登录.用户详情浏览.按照传统的软件开发方式直接创建一个Web项目,分分钟就把这三个功能开发出来了,但是我现在想使用微服务+服务治理

spring cloud alilibaba 介绍和整合

spring cloud alibaba 微服务一站式解决方案基于spring cloud 功能 翻译 选择 Distributed/versioned configuration  分布式/版本化的配置管理 Spring Cloud Config.Consul.Nacos.Zookeeper Service registration and discovery 服务注册和发现 Eureka.Consul.Nacos.Zookeeper Routing 路由 Zuul.Spring Cloud

Spring Cloud 之 Eureka.

一.微服务概述 1. 什么是微服务 ?简单地说, 微服务是系统架构上的一种设计风格, 它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间基于 RPC 进行通信协作. 被拆分成的每一个小型服务都围绕着系统中的某一项或一些耦合度较高的业务功能进行构建, 并且每个服务都维护着自身的数据存储(划重点,每个微服务都有自己的数据库实例). 业务开发.自动化测试案例以及独立部署机制. 2. 微服务的特性 服务组件化:一个独立的系统拆成多个小型服务. 以业务划分服务

关于Spring Cloud微服务架构

微服务架构 Spring Cloud解决的第一个问题就是:服务与服务之间的解耦.很多公司在业务高速发展的时候,服务组件也会相应的不断增加.服务和服务之间有着复杂的相互调用关系,经常有服务A调用服务B,服务B调用服务C和服务D ...,随着服务化组件的不断增多,服务之间的调用关系成指数级别的增长,这样最容易导致的情况就是牵一发而动全身.经常出现由于某个服务更新而没有通知到其它服务,导致上线后惨案频发.这时候就应该进行服务治理,将服务之间的直接依赖转化为服务对服务中心的依赖.Spring Cloud

关于Spring Cloud微服务架构核心组件

微服务架构 Spring Cloud解决的第一个问题就是:服务与服务之间的解耦.很多公司在业务高速发展的时候,服务组件也会相应的不断增加.服务和服务之间有着复杂的相互调用关系,经常有服务A调用服务B,服务B调用服务C和服务D ...,随着服务化组件的不断增多,服务之间的调用关系成指数级别的增长,这样最容易导致的情况就是牵一发而动全身.经常出现由于某个服务更新而没有通知到其它服务,导致上线后惨案频发.这时候就应该进行服务治理,将服务之间的直接依赖转化为服务对服务中心的依赖.Spring Cloud