前面已经介绍了很多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;
} // 省略其他方法
}

三、实现不同维度的限流

例如:

  1. 对请求的目标URL进行限流(例如:某个URL每分钟只允许调用多少次)

  2. 对客户端的访问IP进行限流(例如:某个IP每分钟只允许请求多少次)

  3. 对某些特定用户或者用户组进行限流(例如:非VIP用户限制每分钟只允许调用100次某个API等)

  4. 多维度混合的限流。此时,就需要实现一些限流规则的编排机制。与、或、非等关系。

参考文档

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

Spring Cloud Zuul 限流详解(附源码)(转)的更多相关文章

  1. Spring Cloud(十二):Spring Cloud Zuul 限流详解(附源码)(转)

    前面已经介绍了很多zuul的功能,本篇继续介绍它的另一大功能.在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud中如何实现限流. 在 Zuul 上实现限流是个不错的选 ...

  2. Spring Boot启动命令参数详解及源码分析

    使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...

  3. Spring Cloud Zuul之ZuulFilter详解

    简介 Spring Cloud Zuul网关在整个微服务体系中肩负对外开放接口.请求拦截.路由转发等作用,其核心处理则是ZuulFilter ZuulFilter部分源码 Zuul Filter全部继 ...

  4. 转载—— android 瀑布流的实现详解,附源码

    介绍 参考自:https://github.com/dodola/android_waterfall,因为原来的代码封装不好,所以,我根据源码的思路,重新写了一遍,所以有了现在这个项目:https:/ ...

  5. struts2内置拦截器和自定义拦截器详解(附源码)

    一.Struts2内置拦截器 Struts2中内置类许多的拦截器,它们提供了许多Struts2的核心功能和可选的高级特 性.这些内置的拦截器在struts-default.xml中配置.只有配置了拦截 ...

  6. C# Socket-TCP异步编程原理详解附源码

    目录 目录异步原理主要方法源码Server源码:Client源码实验效果(广播为例)参考博客 TOC 异步原理 套接字编程原理:延续文件作用思想,打开-读写-关闭的模式. C/S编程模式如下: Ø 服 ...

  7. Spring Cloud 微服务五:Spring cloud gateway限流

    前言:在互联网应用中,特别是电商,高并发的场景非常多,比如:秒杀.抢购.双11等,在开始时间点会使流量爆发式地涌入,如果对网络流量不加控制很有可能造成后台实例资源耗尽.限流是指通过指定的策略削减流量, ...

  8. Spring Cloud Gateway限流实战

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. Java SPI机制实战详解及源码分析

    背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...

随机推荐

  1. UML之活动图

    活动图,她的英文名字叫Activity Diagram,是一种说明业务用例实现的工作流程,活动图是UML大家族中用于对系统的动态方面建模的无中图之一. 举个简单的例子,以建房的工作流为例,首先,我们要 ...

  2. Linux 内核配置机制(make menuconfig、Kconfig、makefile)讲解

    前面我们介绍模块编程的时候介绍了驱动进入内核有两种方式:模块和直接编译进内核,并介绍了模块的一种编译方式--在一个独立的文件夹通过makefile配合内核源码路径完成 那么如何将驱动直接编译进内核呢? ...

  3. SpriteBuilder物理对象的父子关系

    注意:打开物理使能(Physics-enabled)的节点忽略他们的父节点关系. 这意味着,一个物理使能的子节点不会随着其父节点移动. 物理引擎对于节点父子关系这个概念毫不知情,因此单独的对待所有物理 ...

  4. 【ROM修改教程】添加高级电源重启菜单(安卓4.0.4官方ROM)

    准备工作: 电脑上安装好JDK.下载smali和baksmali.下载apktools.要修改的ROM.adb工具(可选) 注:由于本教程面向的对象为有一定ROM修改基础的兄弟,所以对于如何使用电脑, ...

  5. LESS学习笔记 —— 入门

    今天在网上完成了LESS的基础学习,下面是我的学习笔记.总共有三个文件:index.html.main.less.mian.css,其中 mian.css 是 main.less 经过Koala编译之 ...

  6. Android开源经典项目

    目前包括: Android开源项目第一篇--个性化控件(View)篇   包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView. ...

  7. Mac电脑配置Apache服务器详细说明

    Mac电脑服务器配置过程,无论是个人学习,还是公司测试都非常实用,流程精简易懂,用于让Mac电脑做服务器方便做网络数据请求的测试. 第一步:定位到 Apache2 目录 $ cd /etc/Apach ...

  8. Django(三)runserver 命令源码分析

    应用环境 windows7 pycharm2018 profession python3.6 django2.0 我们在pycharm 启动django项目时,常常有这么一个命令操作: python ...

  9. dsd

    nodeName属性 nodeName属性规定节点的名称 nodeName是只读的 元素节点的nodeName与属性名相同 属性节点的nodeName与属性名相同 文本节点nodeName始终是#te ...

  10. 如何成为一个优秀的DBA

    今天在群上跟技术友们聊天.发现有几个大学生,他们很担心自己找不到工作.我问他们理由,他们说我们没有经验,公司不要怎么办?这些孩子们的担心,让我感觉到奇怪. 如果我是一个公司的老总,我会有这两种考虑:要 ...