Spring Cloud(十二):Spring Cloud Zuul 限流详解(附源码)(转)
前面已经介绍了很多zuul的功能,本篇继续介绍它的另一大功能。在高并发的应用中,限流往往是一个绕不开的话题。本文详细探讨在Spring Cloud中如何实现限流。
在 Zuul
上实现限流是个不错的选择,只需要编写一个过滤器就可以了,关键在于如何实现限流的算法。常见的限流算法有漏桶算法以及令牌桶算法。这个可参考 https://www.cnblogs.com/LBSer/p/4083131.html ,写得通俗易懂,你值得拥有,我就不拽文了。
GoogleGuava
为我们提供了限流工具类 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
在过滤器中,我们使用 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
搞清楚这点之后,我们就可以撸代码了:
@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
Spring Cloud(十二):Spring Cloud Zuul 限流详解(附源码)(转)的更多相关文章
- Spring Cloud Zuul 限流详解(附源码)(转)
在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud中如何实现限流. 在 Zuul 上实现限流是个不错的选择,只需要编写一个过滤器就可以了,关键在于如何实现限流的算法. ...
- spring security4.2.2的maven配置+spring-security配置详解+java源码+数据库设计
最近项目需要添加权限拦截,经讨论决定采用spring security4.2.2!废话少说直接上干货! 若有不正之处,请谅解和批评指正,不胜感激!!!!! spring security 4.2.2文 ...
- “全栈2019”Java第六十二章:接口与常量详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- “全栈2019”Java第五十二章:继承与初始化详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Spring Boot启动命令参数详解及源码分析
使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...
- Spring Cloud限流详解
转自:https://blog.csdn.net/tracy38/article/details/78685707 在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud ...
- Spring MVC (二)注解式开发使用详解
MVC注解式开发即处理器基于注解的类开发, 对于每一个定义的处理器, 无需在xml中注册. 只需在代码中通过对类与方法的注解, 即可完成注册. 定义处理器 @Controller: 当前类为处理器 @ ...
- Spring IOC容器启动流程源码解析(一)——容器概念详解及源码初探
目录 1. 前言 1.1 IOC容器到底是什么 1.2 BeanFactory和ApplicationContext的联系以及区别 1.3 解读IOC容器启动流程的意义 1.4 如何有效的阅读源码 2 ...
- Java程序员从笨鸟到菜鸟之(五十二)细谈Hibernate(三)Hibernate常用API详解及源码分析--csdn 曹胜欢
新接触一个框架的目的就是想利用这个框架来为我们做一些工作,或者是让他来简化我们的工作,利用这个框架无非就是要利用这个框架所给我们提供的API去操作我们的数据,所以利用一个框架的好坏很大一部分取决于你对 ...
随机推荐
- IDA 操作记录
IDA 操作记录 1.加载文件 windows 下,用ida加载文件后,会在该文件同目录下生成几个文件,含义如下: .id0 : 二叉树数据库 .id1: 文件包含描述每个程序字节的标记 .na ...
- BZOJ4514 [Sdoi2016]数字配对 【费用流】
题目 有 n 种数字,第 i 种数字是 ai.有 bi 个,权值是 ci. 若两个数字 ai.aj 满足,ai 是 aj 的倍数,且 ai/aj 是一个质数, 那么这两个数字可以配对,并获得 ci×c ...
- 北京集训TEST13——PA(第k小数)
题目: Description [问题描述] 从n个数中选若干(至少1)个数求和,求所有方案中第k小的和(和相同但取法不同的视为不同方案).[输入格式] 第一行输入2个正整数n,k. 第二 ...
- Java防止SQL注入的途径介绍
为了防止SQL注入,最简洁的办法是杜绝SQL拼接,SQL注入攻击能得逞是因为在原有SQL语句中加入了新的逻辑,如果使用PreparedStatement来代替Statement来执行SQL语句,其后只 ...
- 旅行问题(bzoj 2746)
Description yz是Z国的领导人,他规定每个地区的名字只能为26个小写拉丁字母的一个.由于地 区数有可能超过26个,便产生了一个问题,如何辨别名字相同的地区?于是yz规定,一个 地区的描述必 ...
- CodeForces 618D Hamiltonian Spanning Tree
题意:要把所有的节点都访问一次,并且不能重复访问,有两种方式访问,一种是根据树上的路径 走和当前节点连接的下一个节点cost x, 或者可以不走树上边,直接跳到不与当前节点连接的节点,cost y 分 ...
- SQL2005:已更新或删除的行值要么不能使该行成为唯一行,要么改变了多个行.
原文发布时间为:2008-08-04 -- 来源于本人的百度文章 [由搬家工具导入] 最近使用sql server2005,已经不止一次的发生了这样的错误.具体是怎么出来的我不清楚,其表现为:数据库中 ...
- 标准C程序设计七---112
Linux应用 编程深入 语言编程 标准C程序设计七---经典C11程序设计 以下内容为阅读: <标准C程序设计>(第7版) 作者 ...
- Git开发必知必会
比如说你现在准备写一个自己的视频资源网站,在创业初期,你的项目暂时还是测试阶段,没有用户的时候,你可能只有一个人在开发,你每天都以写的内容和时间作为文件名的命名,这样其实是可以满足你对版本控制的基本需 ...
- 正确使用‘trap指令’实现Docker优雅退出
一般应用(比如mariadb)都会有一个退出命令,用户使用类似systemctl stop ****.service方法,停止其服务时,systemd会调用其配置文件注册的退出命令,该命令执行清理资源 ...