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童鞋,终于面世通过加入了一家公司.这 ...
随机推荐
- Navicat for mysql 免费破解工具+教程
转载自:https://blog.csdn.net/BigCabbageFy/article/details/79789833 navicat for mysql带给大家这款高性能数据库管理开发工具的 ...
- chrome 浏览器的使用技巧
前端工程师大部分的工作成果都需要在浏览器中查看,使用 chrome 浏览器的频率非常高.更好更优雅地使用 chrome,将 chrome 配置成一款趁手的浏览器,将极大地提升编程效率.本文将详细介绍 ...
- python学习-while
"""while 条件: 条件满足时,干的事情.如果条件一直满足,一直去重复的执行代码.直接条件不满足为止,停止循环."""# salary ...
- [FPGA] Verilog 燃气灶控制器的设计与实现
燃气灶控制器的设计与实现 一.引述 本次实验所用可编程器件型号为MAXII EPM1270T144C5(其引脚表见本人另一博文:可编程实验板EPM1270T144C5使用说明),通过可编程实验板实现一 ...
- Dubbo简介与基本概念
场景 分布式系统的发展演变以及RPC简介: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/103555049 Dubbo简介 Ap ...
- Springboot 项目源码 Activiti6 工作流 vue.js html 跨域 前后分离 websocket即时通讯
特别注意: Springboot 工作流 前后分离 + 跨域 版本 (权限控制到菜单和按钮) 后台框架:springboot2.1.2+ activiti6.0.0+ mybaits+maven+接 ...
- JS---offset系列和scroll系列
元素的样式属性是无法直接通过:对象.style.属性来获取(样式在style属性中设置) offset系列: offsetLeft:距离左边位置的值 offsetTop:距离上面位置的值 offs ...
- Cesium 限制相机进入地下
有时我们在Cesium操作时,点击鼠标中间滚轮可更改视角,有时会使相机进入地下,导致体验很差,网上说了很多中方法,效果都不好或者没效果,下面是我翻了源码找到的方法,亲测有效.如有问题可按照专栏上的联系 ...
- React路由的使用 Redirect默认展示某一个页面 Switch找到停止 BrowserRouter和HashRouter 的区别
引入 Redirect 默认展示某一个页面 Switch 一旦找到 路由 就停止 不会在往下找了 App.js import {Link,Route,NavLink,Redirect,Switch} ...
- 微信 电脑版 HOOK(WeChat PC Hook)- 框架
软件构成:一个主进程exe和一个注入的dll主进程exe:把dll注入到微信,发送指令给dll,接受dll的信息注入的dll:被注入到微信内部,拦截微信的数据,调用微信的功能 接收主进程的指令,执行指 ...