Spring Cloud限流思路及解决方案
转自: http://blog.csdn.net/zl1zl2zl3/article/details/78683855
在高并发的应用中,限流往往是一个绕不开的话题。本文详细探讨在Spring Cloud中如何实现限流。
在 Zuul 上实现限流是个不错的选择,只需要编写一个过滤器就可以了,关键在于如何实现限流的算法。常见的限流算法有漏桶算法以及令牌桶算法。这个可参考 https://www.cnblogs.com/LBSer/p/4083131.html ,写得通俗易懂,你值得拥有,我就不拽文了。
GoogleGuava 为我们提供了限流工具类 RateLimiter ,于是乎,我们可以撸代码了。
代码示例
@Componentpublic class RateLimitZuulFilter extends ZuulFilter {private final RateLimiter rateLimiter = RateLimiter.create(1000.0);@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {return Ordered.HIGHEST_PRECEDENCE;}@Overridepublic boolean shouldFilter() {// 这里可以考虑弄个限流开启的开关,开启限流返回true,关闭限流返回false,你懂的。return true;}@Overridepublic 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
在过滤器中,我们使用 GuavaRateLimiter 实现限流,如果已经达到最大流量,就抛异常。
分布式场景下的限流
以上单节点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
搞清楚这点之后,我们就可以撸代码了:
@Componentpublic class RateLimitZuulFilter extends ZuulFilter {private Map<String, RateLimiter> map = Maps.newConcurrentMap();@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {// 这边的order一定要大于org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter的order// 也就是要大于5// 否则,RequestContext.getCurrentContext()里拿不到serviceId等数据。return Ordered.LOWEST_PRECEDENCE;}@Overridepublic boolean shouldFilter() {// 这里可以考虑弄个限流开启的开关,开启限流返回true,关闭限流返回false,你懂的。return true;}@Overridepublic Object run() {try {RequestContext context = RequestContext.getCurrentContext();HttpServletResponse response = context.getResponse();String key = null;// 对于service格式的路由,走RibbonRoutingFilterString serviceId = (String) context.get(SERVICE_ID_KEY);if (serviceId != null) {key = serviceId;map.putIfAbsent(serviceId, RateLimiter.create(1000.0));}// 如果压根不走RibbonRoutingFilter,则认为是URL格式的路由else {// 对于URL格式的路由,走SimpleHostRoutingFilterURL 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 能力进行实现基于内存压力的限流——当可用内存低于某个阈值就开启限流,否则不开启限流。
@Componentpublic class RateLimitZuulFilter extends ZuulFilter {@Autowiredprivate SystemPublicMetrics systemPublicMetrics;@Overridepublic 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
Spring Cloud限流思路及解决方案的更多相关文章
- Spring Cloud限流详解
转自:https://blog.csdn.net/tracy38/article/details/78685707 在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud ...
- Spring Cloud Alibaba 新一代微服务解决方案
本篇是「跟我学 Spring Cloud Alibaba」系列的第一篇, 每期文章会在公众号「架构进化论」进行首发更新,欢迎关注. 1.Spring Cloud Alibaba 是什么 Spring ...
- Spring Cloud Alibaba微服务一站式解决方案-开篇v2.2.1.RELEASE
学习路线 **本人博客网站 **IT小神 www.itxiaoshen.com 生态概述 架构演进 什么是微服务 https://martinfowler.com/microservices/ Mic ...
- spring cloud微服务快速教程之(九) Spring Cloud Alibaba--sentinel-限流、熔断降级
0.前言 sentinel的限流.降级功能强大,可以在控制面板中任意制定规则,然后推送到微服务中: 可以根据URL单独制定规则,也可以根据资源名批量制定规则: 需要注意的地方是:1.GITHUB文件在 ...
- 令牌桶限流思路分享(PHP+Redis实现机制)
一 .场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval ...
- Spring Cloud之Hystrix雪崩效应解决方案
基于Hystris解决雪崩效应: 1.服务降级: 防止用户一直等待,使用降级方式,调用FallBack(返回友好提示,不会去处理请求) 案例: 当前请求人数过多,请稍后重试 2.服务熔断:(和服 ...
- 通知: Spring Cloud Alibaba 仓库迁移
最近,Spring Cloud 官方修改了各个第三方项目的发布策略,第三方 spring-cloud 项目需要自身维护.基于此策略,Spring-Cloud-Alibaba 项目迁移到了 alibab ...
- Spring Cloud Alibaba 之 Sentinel 限流规则和控制台实例
这一节我们通过一个简单的实例,学习Sentinel的基本应用. 一.Sentinel 限流核心概念 在学习Sentinel的具体应用之前,我们先来了解一下Sentinel中两个核心的概念,资源和规则. ...
- Spring Cloud Gateway 扩展支持动态限流
之前分享过 一篇 <Spring Cloud Gateway 原生的接口限流该怎么玩>, 核心是依赖Spring Cloud Gateway 默认提供的限流过滤器来实现 原生Request ...
随机推荐
- USB耳机声卡-音频输入/输出控制器:DP108替代兼容CM108
DP108是一款完全替代CM108的高度集成的单芯片USB音频解决方案芯片.方便的USB即插即用的兼容性,用户可以快速创建易用性,高质量和便携式USB音频产品基于高度集成的单芯片解决方案.所有重要的模 ...
- 【题解】洛谷P1169 [ZJOI2007] 棋盘制作(坐标DP+悬线法)
次元传送门:洛谷P1169 思路 浙江省选果然不一般 用到一个从来没有听过的算法 悬线法: 所谓悬线法 就是用一条线(长度任意)在矩阵中判断这条线能到达的最左边和最右边及这条线的长度 即可得到这个矩阵 ...
- HDU 1698 Just a Hook(线段树模板之区间替换更新,区间求和查询)
Just a Hook Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- oracle12c创建用户指定表空间
--1.创建临时空间 create temporary tablespace zyj_temp tempfile 'D:\app2\user\virtual\oradata\orcl\zyj_temp ...
- dpkg安装失败解决过程
终于好了.搞到转钟3点都没搞定,耽误不少时间. 执行sudo port install dpkg 报错如下Error: org.macports.build for port gmp return ...
- Delphi的idhttp报IOHandler value is not valid错误的原因
出现这种问题的原因是由于访问的 URL地址为https或存在其跳转地址为https. 首先单纯使用idhttp是只能访问http,而https则需要搭配IdSSLIOHandlerSocketOpen ...
- 白帽子讲web安全——访问控制
上一章说的认证与会话管理,这章是访问控制,刚看访问控制这章的时候,感觉跟上章的“授权”没什么区别,第一感受就是“授权”. 之后看了才进一步了解,“授权”是好比屋子的主人进来了,那么他可以坐在客厅,也可 ...
- python基础之import模块导入和包的调用
模块概念 在Python中,一个.py文件就称之为一个模块(Module).使用模块组织代码,最大的好处是大大提高了代码的可维护性 模块一共三种:python标准库.第三方模块.应用程序自定义模块. ...
- JavaWeb总结(六)
获取原始表单数据 - POST请求不仅可以传输文本信息还可以传输二进制数据 - 如果想得到请求中参数的原始字节数据,可以使用HttpServletRequest对象提供的getInputSteam() ...
- 5 数据结构、栈、队列、链表、list、dict、迷宫问题
1.什么是数据结构 2.栈:后进先出 1.什么是栈 栈(Stack)是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表. 2.栈的Python实现 stack = [] stack.ap ...