SpringMVC 简单限流方案设计
一、概念
限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。
常用的限流算法有两种:漏桶算法和令牌桶算法:
漏桶算法的思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

二、应用
Google 开源工具包 Guava 提供了限流工具类 RateLimiter,该类基于令牌桶算法来完成限流,非常易于使用。RateLimiter api 可以查看并发编程网 Guava RateLimiter 的介绍。
我们用 MVC 的拦截器 + Guava RateLimiter 实现我们的限流方案:
@Slf4j
public class RequestLimitInterceptor extends HandlerInterceptorAdapter implements BeanPostProcessor {
private static final Integer GLOBAL_RATE_LIMITER = 10;
private static Map<PatternsRequestCondition, RateLimiter> URL_RATE_MAP;
private Properties urlProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (URL_RATE_MAP != null) {
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
for (PatternsRequestCondition patternsRequestCondition : URL_RATE_MAP.keySet()) {
//使用spring DispatcherServlet的匹配器PatternsRequestCondition进行匹配
//spring 3.x 版本
//Set<String> matches = patternsRequestCondition.getMatchingCondition(request).getPatterns();
//spring 4.x 版本
List<String> matches = patternsRequestCondition.getMatchingPatterns(lookupPath);
if (CollectionUtils.isEmpty(matches)){
continue;
}
//尝试获取令牌
if (!URL_RATE_MAP.get(patternsRequestCondition).tryAcquire(1000, TimeUnit.MILLISECONDS)) {
log.info(" 请求'{}'匹配到 mathes {},超过限流速率,获取令牌失败。", lookupPath, Joiner.on(",").join(patternsRequestCondition.getPatterns()));
return false;
}
log.info(" 请求'{}'匹配到 mathes {} ,成功获取令牌,进入请求。", lookupPath, Joiner.on(",").join(patternsRequestCondition.getPatterns()));
}
}
return super.preHandle(request, response, handler);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (RequestMappingHandlerMapping.class.isAssignableFrom(bean.getClass())) {
if (URL_RATE_MAP == null) {
URL_RATE_MAP = new ConcurrentHashMap<>(16);
}
log.info("we get all the controllers's methods and assign it to urlRateMap");
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) bean;
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
for (RequestMappingInfo mappingInfo : handlerMethods.keySet()) {
PatternsRequestCondition requestCondition = mappingInfo.getPatternsCondition();
// 默认的 url 限流方案设定
URL_RATE_MAP.put(requestCondition, RateLimiter.create(GLOBAL_RATE_LIMITER));
}
// 自定义的限流方案设定
if (urlProperties != null) {
for (String urlPatterns : urlProperties.stringPropertyNames()) {
String limit = urlProperties.getProperty(urlPatterns);
if (!limit.matches("^-?\\d+$")){
log.error("the value {} for url patterns {} is not a number ,please check it ", limit, urlPatterns);
}
URL_RATE_MAP.put(new PatternsRequestCondition(urlPatterns), RateLimiter.create(Integer.parseInt(limit)));
}
}
}
return bean;
}
/**
* 限流的 URL与限流值的 K/V 值
*
* @param urlProperties
*/
public void setUrlProperties(Properties urlProperties) {
this.urlProperties = urlProperties;
}
}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public RequestLimitInterceptor requestLimitInterceptor(){
RequestLimitInterceptor limitInterceptor = new RequestLimitInterceptor();
// 设置自定义的 url 限流方案
Properties properties = new Properties();
properties.setProperty("/admin/**", "10");
limitInterceptor.setUrlProperties(properties);
return limitInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 限流方案
registry.addInterceptor(requestLimitInterceptor());
}
}
tips: 这边自定义限流列表 urlProperties 的方案不太合理,可以考虑放在配置中心(Nacos、Spring Cloud Config 等)去动态的更新需要限流的 url。
参考博文:
- https://blog.csdn.net/Lili429/article/details/79236819
- https://blog.csdn.net/valleychen1111/article/details/78038366
SpringMVC 简单限流方案设计的更多相关文章
- 令牌桶、漏斗、冷启动限流在sentinel的应用
分布式系统为了保证系统稳定性,在服务治理的限流中会根据不同场景进行限流操作,常见的限流算法有: 令牌桶:可容忍一定突发流量的速率的限流,令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶 ...
- Redis解读(4):Redis中HyperLongLog、布隆过滤器、限流、Geo、及Scan等进阶应用
Redis中的HyperLogLog 一般我们评估一个网站的访问量,有几个主要的参数: pv,Page View,网页的浏览量 uv,User View,访问的用户 一般来说,pv 或者 uv 的统计 ...
- 业务限流场景简单实现方案:RateLimiter
前因:因为本系统中,有大数据高并发的场景.在向下游系统发送请求的时候,需要限流.否则会造成下游系统的堵塞. 实现方案1: Thread.sleep(ms). 优点:简单粗暴,一行代码搞定 缺点:有点l ...
- nginx 、springMvc(非分布式)相应的限流、消峰
互联网服务赖以生存的根本是流量, 产品和运营会经常通过各种方式来为应用倒流,比如淘宝的双十一等,如何让系统在处理高并发的同时还是保证自身系统的稳定, 通常在最短时间内提高并发的做法就是加机器, 但是如 ...
- 使用RateLimiter完成简单的大流量限流,抢购秒杀限流
RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率. 通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位时 ...
- java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能
这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能 ...
- 快速体验 Sentinel 集群限流功能,只需简单几步
️ Pic by Alibaba Tech on Facebook 集群限流 可以限制某个资源调用在集群内的总 QPS,并且可以解决单机流量不均导致总的流控效果不佳的问题,是保障服务稳定性的利器. S ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(七)——一步一步教你如何撸Dapr之服务限流
在一般的互联网应用中限流是一个比较常见的场景,也有很多常见的方式可以实现对应用的限流比如通过令牌桶通过滑动窗口等等方式都可以实现,也可以在整个请求流程中进行限流比如客户端限流就是在客户端通过随机数直接 ...
- 小白也能看懂的Redis教学基础篇——做一个时间窗限流就是这么简单
不知道ZSet(有序集合)的看官们,可以翻阅我的上一篇文章: 小白也能看懂的REDIS教学基础篇--朋友面试被SKIPLIST跳跃表拦住了 书接上回,话说我朋友小A童鞋,终于面世通过加入了一家公司.这 ...
随机推荐
- 《java面试十八式》第一式 --冈本零点零一
第一式 [冈本零点零一] 冈本零点零一:将简历包装于无形,博得人事的芳心,用过的人都说好 . 要想有更多的面试机会,简历是不可缺少的,机会都没有何谈面:所以这也是我们的第一步. 首先是简历模板: 模板 ...
- 大数据学习笔记——Hadoop高可用完全分布式模式完整部署教程(包含zookeeper)
高可用模式下的Hadoop集群搭建 本篇博客将会在之前写过的Linux的完整部署的基础上进行,暂时不会涉及到伪分布式或者完全分布式模式搭建,由于HA模式涉及到的配置文件较多,维护起来也较为复杂,相信学 ...
- 【ES5 ES6】使用学习
[ES5 ES6]使用学习 转载: ============================================================= 1.Promise 2.下划线转驼峰,驼 ...
- 11条MySQL规范,你知道的有几个?
一.数据库命令规范 · 所有数据库对象名称必须使用小写字母并用下划线分割 · 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) · 数据库对象的命名 ...
- 牛客练习赛32B Xor Path (树形dp)
时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言524288K 64bit IO Format: %lld 题目描述 给定一棵n个点的树,每个点有权值.定义表示 ...
- 《Dotnet9》系列-开源C# WPF控件库2《Panuon.UI.Silver》强力推荐
时间如流水,只能流去不流回! 点赞再看,养成习惯,这是您给我创作的动力! 本文 Dotnet9 https://dotnet9.com 已收录,站长乐于分享dotnet相关技术,比如Winform.W ...
- Sql Server数据库常用Transact-SQL脚本
数据库 1.创建数据库 USE master ; GO CREATE DATABASE Sales ON ( NAME = Sales_dat, FILENAME = 'C:\Program File ...
- Linux-3.14.12内存管理笔记【伙伴管理算法(4)】
此处承接前面未深入分析的页面释放部分,主要详细分析伙伴管理算法中页面释放的实现.页面释放的函数入口是__free_page(),其实则是一个宏定义. 具体实现: [file:/include/linu ...
- android6.0系统Healthd深入分析
概述 Healthd是android4.4之后提出来的一种中介模型,该模型向下监听来自底层的电池事件,向上传递电池数据信息给Framework层的BatteryService用以计算电池电量相关状态信 ...
- ubuntu虚拟环境的操作
查看已经安装的虚拟环境:workon 退出虚拟环境,home目录下执行:deactivate 进入虚拟环境:workon 虚拟环境名