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童鞋,终于面世通过加入了一家公司.这 ...
 
随机推荐
- Python3 常用的几个内置方法
			
目录 max()/min() filter() 过滤 map() 映射 sorted筛选 reduce()减少 max()/min() 传入一个参数 (可迭代对象), 返回这个可迭代对象中最大的元素 ...
 - Java_百钱买百鸡
			
题目:公鸡3文钱,母鸡2文钱,3只小鸡1文钱,百钱买百鸡,求多少公鸡,母鸡,小鸡? public class Work6{ public static void main(String[] args) ...
 - 手动启动Oracle服务的.bat文件
			
Oracle数据库的基本服务会占用很大的内存,有的程序员会在不用的时候Oracle服务关闭来减少对电脑内存资源的占用. 我在这准备了一个可以开启/关闭Oracle服务的bat文件,希望被采纳!!! 新 ...
 - LCT能干啥???
			
LCT能干啥 模板: 维护可加的树链信息:询问都是一条链上的信息:维护方式和线段树差不多: 增加一条边: 删除一条边: 修改一个点权: 修改一条路径上的所有点的点权: 整体来说 ...
 - 《Java基础知识》Java抽象类,接口的概念和使用
			
1.抽象类 在自上而下的继承层次结构中,位于上层的类更具有通用性,甚至可能更加抽象.从某种角度看,祖先类更加通用,它只包含一些最基本的成员,人们只将它作为派生其他类的基类,而不会用来创建对象.甚至,你 ...
 - 《Java基础知识》Java IO流详解
			
Java IO概念 1. 用于设备之间的数据传输. 2. Java 将操作数据流的功能封装到了IO包中. 3. 数据流流向分:输入流和输出流,操作对象为文件. 4. 流按照操作数据分:字节流(通用)和 ...
 - css应用视觉设计
			
应用视觉设计:创建一个 CSS 线性渐变 HTML元素的背景色并不局限于单色.css还提供了颜色过渡,也就是渐变.可以通过background里面的linear-gradient()来实现线性渐变,下 ...
 - 比较typeof与instanceof
			
相同点: JavaScript中typeof和instanceof常用来判断一个变量是否为空,或者是什么类型的. 不同点: typeof的定义和用法: 返回值是一个字符串,用来说明变量的数据类型. 细 ...
 - Android.mk语法说明
			
版权申明: 本文原创首发于以下网站,您可以自由转载,但必须加入完整的版权声明 博客园:https://www.cnblogs.com/MogooStudio/ csdn博客:https://blog. ...
 - mysql优化:覆盖索引(延迟关联)
			
前言 上周新系统改版上线,上线第二天就出现了较多的线上慢sql查询,紧接着dba 给出了定位及解决方案,这里较多的是使用延迟关联去优化. 而我对于这个延迟关联也是第一次听说(o(╥﹏╥)o),所以今天 ...