限流(rate limiter)

news2025/2/6 7:50:53

项目中业务提出需求,要求对商品的立即购买接口进行限流。

经过百度及调研,决定在拦截器加限流。拦截器相关讲解见上几篇博客springmv中的拦截器

springmvc限流

RateLimiter

最终的限流代码如下(从百度借鉴,已经找不到出处):

/**
 * spring请求限流器
 * 可对全局请求或url表达式请求进行限流
 * 内部使用spring DispatcherServlet的匹配器PatternsRequestCondition
 * 进行url的匹配。
 *
 * @author ValleyChen
 * @version 1.0.0
 * @time 2017/1/21
 */
public class RateLimitInterceptor implements HandlerInterceptor {

    private RateLimiter globalRateLimiter;

    private Properties urlProperties;

    private Map<PatternsRequestCondition, RateLimiter> urlRateMap;

    private UrlPathHelper urlPathHelper;

    private int globalRate;

    private List<URLLimitMapping> limitMappings;

    public RateLimitInterceptor() {
        this(0);
    }

    /**
     * @param globalRate 全局请求限制qps
     */
    public RateLimitInterceptor(int globalRate) {
        urlPathHelper = new UrlPathHelper();
        this.globalRate = globalRate;
        if (globalRate > 0)
            globalRateLimiter = RateLimiter.create(globalRate);
    }

    /**
     * @param globalRate 全局请求限制qps
     */
    public void setGlobalRate(int globalRate) {
        this.globalRate = globalRate;
        if (globalRate > 0)
            globalRateLimiter = RateLimiter.create(globalRate);
    }

    /**
     * url限流
     *
     * @param urlProperties url表达式->qps propertis
     */
    public void setUrlProperties(Properties urlProperties) {
        if (urlRateMap == null)
            urlRateMap = new ConcurrentHashMap();
        fillRateMap(urlProperties, urlRateMap);
        this.urlProperties = urlProperties;
    }

    /**
     * url限流
     *
     * @param limitMappings url表达式 array->qps mapping
     */
    public void setLimitMappings(List<URLLimitMapping> limitMappings) {
        if (urlRateMap == null)
            urlRateMap = new ConcurrentHashMap();
        fillRateMap(limitMappings, urlRateMap);
        this.limitMappings = limitMappings;
    }

    /**
     * 将url表达转换为PatternsRequestCondition,并生成对应RateLimiter
     * 保存
     *
     * @param controllerProperties
     * @param map
     */
    private void fillRateMap(Properties controllerProperties, Map<PatternsRequestCondition, RateLimiter> map) {
        if (controllerProperties != null) {
            for (String key : controllerProperties.stringPropertyNames()) {
                String value = controllerProperties.getProperty(key);
                if (value.matches("[0-9]*[1-9][0-9]*")) {
                    map.put(new PatternsRequestCondition(key), RateLimiter.create(Double.valueOf(value)));
                } else {
                    LoggerUtil.log(key + " 的值" + value + " 不是一个合法的限制");
                }
            }
        }
    }

    /**
     * 将url表达转换为PatternsRequestCondition,并生成对应RateLimiter
     * 保存
     *
     * @param limitMappings
     * @param map
     */
    private void fillRateMap(List<URLLimitMapping> limitMappings, Map<PatternsRequestCondition, RateLimiter> map) {
        if (limitMappings != null) {
            for (URLLimitMapping mapping : limitMappings) {
                map.put(new PatternsRequestCondition(mapping.getUrls()), RateLimiter.create(Double.valueOf(mapping.getRate())));
            }
        }
    }




    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


        if (urlRateMap != null) {
            String lookupPath = urlPathHelper.getLookupPathForRequest(request);
            for (PatternsRequestCondition patternsRequestCondition : urlRateMap.keySet()) {
                //使用spring DispatcherServlet的匹配器PatternsRequestCondition进行匹配
                List<String> matches = patternsRequestCondition.getMatchingPatterns(lookupPath);
                if (!matches.isEmpty()) {
                    if (urlRateMap.get(patternsRequestCondition).tryAcquire()) {
                        LoggerUtil.log(lookupPath + " 请求匹配到" + Joiner.on(",").join(patternsRequestCondition.getPatterns()) + "限流器");
                    } else {
                        LoggerUtil.log(lookupPath + " 请求超过" + Joiner.on(",").join(patternsRequestCondition.getPatterns()) + "限流器速率");
                        //return false;
                        throw new ResponseException(500,"busy");
                    }

                }
            }
        }

        //全局限流
        if (globalRateLimiter != null) {
            if (!globalRateLimiter.tryAcquire()) {
                return false;
            }

        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}


package com.chen.limit.model;

/**
 * @author ValleyChen
 * @version 1.0.0
 * @time 2017/1/22
 */
public class URLLimitMapping {

    private String[] urls;
    private int rate=0;

    public URLLimitMapping() {
    }

    public URLLimitMapping(String[] urls, int rate) {
        this.urls = urls;
        this.rate = rate;
    }

    public String[] getUrls() {
        return urls;
    }

    public void setUrls(String[] urls) {
        this.urls = urls;
    }

    public int getRate() {
        return rate;
    }

    public void setRate(int rate) {
        this.rate = rate;
    }
}

 

public class RequestLimitInterceptor implements HandlerInterceptor ,BeanPostProcessor{

    private Logger logger = LoggerFactory.getLogger(RequestLimitInterceptor.class);


    private Integer globalRateLimiter = 100;

    private Map<PatternsRequestCondition, RateLimiter> urlRateMap;

    private Properties urlProperties;

    private UrlPathHelper urlPathHelper = new UrlPathHelper();


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (urlRateMap != null) {
            String lookupPath = urlPathHelper.getLookupPathForRequest(request);
            for (PatternsRequestCondition patternsRequestCondition : urlRateMap.keySet()) {
                //使用spring DispatcherServlet的匹配器PatternsRequestCondition进行匹配
                List<String> matches = patternsRequestCondition.getMatchingPatterns(lookupPath);
                if (!matches.isEmpty()) {
                    if (urlRateMap.get(patternsRequestCondition).tryAcquire(1000, TimeUnit.MILLISECONDS)) {
                        logger.info(" 请求'{}'匹配到mathes {} ,成功获取令牌,进入请求。" ,lookupPath ,Joiner.on(",").join(patternsRequestCondition.getPatterns()) );
                    } else {
                        logger.info( " 请求'{}'匹配到mathes {},超过限流速率,获取令牌失败。" ,lookupPath ,Joiner.on(",").join(patternsRequestCondition.getPatterns()));
                        return false;
                    }

                }
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

    /**
     * 限流的 URL与限流值的K/V 值
     *
     * @param urlProperties
     */
    public void setUrlProperties(Properties urlProperties) {
        this.urlProperties = urlProperties;
    }


    public void setGlobalRateLimiter(Integer globalRateLimiter) {
        this.globalRateLimiter = globalRateLimiter;
    }

    @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(urlRateMap==null){
                urlRateMap = new ConcurrentHashMap<>();
            }
            logger.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 rmi : handlerMethods.keySet()) {
                PatternsRequestCondition pc = rmi.getPatternsCondition();
                urlRateMap.put(pc,RateLimiter.create(globalRateLimiter));
            }
            if(urlProperties!=null){
                for(String urlPatterns :urlProperties.stringPropertyNames()){
                    String limit = urlProperties.getProperty(urlPatterns);
                    if(!limit.matches("^-?\\d+$"))
                        logger.error("the value {} for url patterns {} is not a number ,please check it ",limit,urlPatterns);
                    urlRateMap.put(new PatternsRequestCondition(urlPatterns), RateLimiter.create(Integer.parseInt(limit)));
                }
            }
        }
        return bean;
    }
}
应用
拦截器配置,可以统一配置所有请求的上限,也可以单独对某个 url配置,该拦截器是基于 SpringMvcRequestMappingHandlerMapping获取url 进行操作。
<bean id="requestLimitInterceptor" class="cn.fraudmetrix.creditcloud.app.intercepters.RequestLimitInterceptor">
        <property name="globalRateLimiter" value="100" />
        <property name="urlProperties">
            <props>
                <prop key="/creditcloud/test">100</prop>
            </props>
        </property>
    </bean>

    <!--拦截器配置-->
    <mvc:interceptors>
        <ref bean="requestLimitInterceptor" />
    </mvc:interceptors>

压测结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

原理分析

逐行拆解Guava限流器RateLimiter

谈一谈我的理解:
采用令牌桶算法,想象有一个固定容量的桶,以稳定的速率生成令牌放在桶中。当有请求时,需要获取令牌才能通过。因为桶的容量固定,令牌满了就不生产了。桶中有充足的令牌时,突发的流量可以直接获取令牌通过。当令牌取光后,桶空了,后面的请求就得等生成令牌后才能通过,这种情况下请求通过的速率就变得稳定了。

关键词:令牌生成速率固定,能取到令牌即可通过。

令牌桶的优点是可用处理突发流量。

作者用令牌桶的方式卖包子的例子进行解释:

包子铺每天早上7点准时开门,师傅开始制作这60屉小笼包【RateLimiter rateLimiter = RateLimiter.create(60)】。顾客蜂拥而至排队,第一个顾客先付一屉小笼包的钱【rateLimiter.acquire()】,等一分钟后小笼包出锅,便可以进屋吃包子了。这时伙计会和顾客说:“您本次等待了1分钟,久等了。”【rateLimiter.acquire()返回的值】。之后是第二个顾客,付钱,等一分钟,进屋吃包子。于是,早上大家就这样一直稳定有序的进屋吃包子。这事来了个火急火燎的上班族,说:“我着急吃饭,不想等,有现成的包子吗?”【rateLimiter.tryAcquire()】伙计看了一眼后厨说:“没有现成的包子,需要等会哦。”一看还要等,第三个顾客就走了。
早高峰过去后,买包子的人少了,师傅做完60个包子后,就休息了。
中午12点,隔壁科技园的程序员们下班了,顾客蜂拥而至。因为已经有了60个库存,大家不用等,付了钱就可以直接进屋吃包子,屋子里一下就涌入了60个顾客。看到小笼包都买光了,后厨师傅又赶紧忙了起来。
这时来了个大胃王,大嗓门喊道:“我想要30屉包子。”【rateLimiter.acquire(30)】伙计一愣,心想,这一下也做不出这么多包子呀。但是本着客户至上的原则,他对大胃王说:“好的先生,不过我们的包子都是现做的,您先吃着,我们蒸好了陆陆续续给您端上来可以吗?”大胃王说:“哈哈,好!好饭不怕晚!”于是交钱进门吃包子了。这时候又来了个顾客A,说我想要一屉包子。伙计心里一算,现在是一点,等刚才那个大胃王要的包子上齐了,才能给这位顾客上包子,所以需要等到1:30才能给这位顾客吃上,好惨一顾客。于是说:“不好意思这位顾客,小笼包现在没有了,您需要等一段时间。”顾客A说:“好吧,我交了钱等着吧。”于是过了半个小时,后厨做出来了第31屉包子,顾客A就满足的进屋吃包子了。

因此,令牌桶算法有两个参数限制:
1.桶大小(最大令牌数限制)
2.补给率(匀速生成速率)
稍后,博主又介绍了:
两种获取令牌的方法–
acquire是直接获取令牌,如果还没有到达接受下一个请求的时间点,就继续等待。而tryAcquire则如其名,我先试试,如果在我允许的时间范围内获取不到就不等了,如果能获取到那我就等着。

两种实现方式–
SmoothBursty:平滑突发限流,以稳定的速率生成令牌。
SmoothWarmingUp:平滑预热限流,随着请求量的增加,令牌生成速率会缓慢提升直到一个稳定的速率。

Dubbo服务端限流

并发控制
限制com.foo.BarService的每个方法,服务端并发执行(或占用线程池线程数)不能超过10个:

<dubbo:service interface="com.foo.BarService" executes="10" />

限制com.foo.BarService的sayHello方法,服务器并发执行(或占用线程池线程数)不能超过10个。

<dubbo:service interface="com.foo.BarService">
    <dubbo:method name="sayHello" executes="10" />
</dubbo:service>

actives限流
该限流方式与前两种不同,其可以设置在提供端,也可以设置在消费者端。可以设置为接口级别,也可以设置为方法级别。
根据消费者与提供者建立的连接类型,其意义也不同。
长连接: 表示当前的长连接最多可以处理的请求个数。与长连接的数量没有问题。
短连接:表示当前服务可以同时处理的短连接数量。
类级别

<dubbo:service interface="com.foo.BarService" actives="10" />
<dubbo:reference interface="com.foo.BarService" actives="10" />

方法级别

<dubbo:reference interface="com.foo.BarService">
    <dubbo:method name="sayHello" actives="10" />
</dubbo:service>
<dubbo:reference interface="com.foo.BarService">
    <dubbo:method name="sayHello" actives="10" />
</dubbo:service>

Nginx的API限流(http):

API限流(http)
Nginx
对于Nginx接入层限流可以使用Nginx自带了两个模块:
连接数限流模块ngx_http_limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1336812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python模拟动态星空

前言 今天&#xff0c;我们来用Python做个星空。 一、模拟星空 1,.首先导入所需要的库&#xff1a; from turtle import * from random import random, randint 2.初始画面&#xff1a; screen Screen() width, height 800, 600 screen.setup(width, height) screen.tit…

【HTML】7个你可能不知道的HTML标签

在Web开发的广阔天地里&#xff0c;大部分开发人员像是在探索&#xff0c;需要掌握多种语言来开拓创新。而在这语言丛林中&#xff0c;学习一门语言的全部知识往往是一项巨大的挑战&#xff0c;有时候甚至会发现自己对一些看似普通但实则非常专业的标签知之甚少。 而网页开发中…

python的os模块详解

本章介绍python自带模块os&#xff0c;os为操作系统 operating system 的简写&#xff0c;意为python与电脑的交互。主要学习的函数有 os.getcwd()、os.chdir()、os.path.basename()、os.path.join()、os.path.exists()、os.path.isdir()、os.path.isfile()、os.listdir()、os.…

MyBatis 架构分析

文章目录 三层架构一、基础支撑层1.1 类型转换模块1.2 日志模块1.3 反射工具模块1.4 Binding 模块1.5 数据源模块1.6 缓存模块1.6 解析器模块1.7 事务管理模块 二、核心处理层2.1 配置解析2.2 SQL 解析与 scripting 模块。2.3 MyBatis 中的 scripting 模块就是负责动态生成 SQL…

spring初始化bean之后执行某个方法

这个问题可以分两种解释&#xff1a; 1. 某个bean初始化执行? 2. 所有bean初始化后执行? 第一个问题可以在spring bean的生命周期中找到答案&#xff1a; bean定义-实例化-初始化-销毁。注意&#xff1a; 这里的bean定义是指所有的bean定义完成&#xff0c;然后才继续执…

Plantuml之活动图语法介绍(二十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

MYSQL一一函数一一字符串函数

嘿嘿大家好我回来啦&#xff0c;今天我们要学习的是MYSQL中的函数&#xff0c;函数呢我们又分为字符串函数&#xff0c;数值函数&#xff0c;日期函数&#xff0c;流程函数来介绍&#xff0c;今天重点介绍字符串函数(从小题到案例方便你们更加深入的理解) 函数指的是一段可以直…

【unity学习笔记】4.场景切换

创建空物体→创建脚本挂载在空物体上→打开脚本 1.创建所需要的场景 assets中点击创建场景 2.文件→生成设置 3.将需要的场景拖入 4.场景跳转 创建空对象&#xff0c;将脚本放在空对象上。 注意两个类&#xff1a;场景类、场景管理类 void Start(){//场景跳转SceneManager.Lo…

论文阅读<CF-YOLO: Cross Fusion YOLO for Object Detection in Adverse Weather.....>

论文链接&#xff1a;https://arxiv.org/pdf/2309.08152.pdfhttps://arxiv.org/pdf/2206.01381.pdfhttps://arxiv.org/pdf/2309.08152.pdf 代码链接&#xff1a;https://github.com/DiffPrompter/diff-prompter 目前没有完整代码放出。 恶劣天气下的目标检测主要有以下三种解…

WIZnet Ethernet HAT评估版介绍

文章目录 1 简介2 硬件资源2.1 硬件规格2.2 引脚定义 3 参考资料3.1 Datasheet3.2 原理图3.3 尺寸图&#xff08;尺寸&#xff1a;mm&#xff09;3.4 参考例程 4 硬件协议栈优势 1 简介 WIZnet Ethernet HAT是一款可直接硬件附在树莓派RP2040Pico开发板上的兼容版&#xff0c;利…

格密码基础:垂直子空间与子格,q-ary垂直格

目录 一.写在前面 二.子空间垂直 2.1 理论解释 2.2 举例分析 三. 零空间 3.1 零空间与q-ary垂直格 3.2 零空间与行/列空间 四. 格密码相关 一.写在前面 格密码中的很多基础原语都来自于线性代数的基本概念&#xff0c;比如举几个例子&#xff1a; 格密码中的非满秩格…

差速器壳体形位误差测量系统圆柱度同轴度自动测量-CASAIM全自动尺寸测量仪

差速器壳体是汽车、拖拉机驱动系统的重要零件&#xff0c;其加工精度直接影响汽车、拖拉机等行走机构的使用寿命和行驶的安全性&#xff0c;因此&#xff0c;不仅对安装孔本身的尺寸精度要求高&#xff0c;而且对孔与孔的同轴度、孔与外圆的同轴度以及孔与端面的垂直度等位置公…

C#MVC项目---登录

目录 1、创建登录类 2、添加控制器-视图 3、修改View视图 4、添加action登录方法 1、创建登录类 public class LoginModel { [Required, StringLength(maximumLength: 20, ErrorMessage "请输入2-20个字符", MinimumLength 2)] public s…

脱壳后多dex文件合并进apk反编译

我们遇到加固后的apk&#xff0c;在脱壳后有很多dex文件&#xff0c;有时候我们只反编译有关键代码的dex会存在一些上下文代码找不到的情况&#xff0c;这时候我们需要多dex一起反编译&#xff0c;并且需要同步看看资源文件怎么办&#xff1f;&#xff1a; 我们可以把多dex塞回…

Vue3使用的Compostion Api和Vue2使用的Options Api有什么不同?

我们介绍Compostion Api和Options Api的区别之前&#xff0c;先来说一下为什么会推出来Composition Api&#xff0c;解决了什么问题&#xff1f; Vue2开发项目使用Options Api存在的问题 代码的可读性和维护性随着组件的变大业务的增多而变得差代码的共享和重用性存在缺点不支…

Halcon颜色提取,基于MLP自动颜色提取功能

1.前言 在实际的图像处理中&#xff0c;经常会遇到彩色图像&#xff0c;使用彩色图像往往跟颜色识别有关系。但是使用RGB进行调参时又很难达到所需要的效果&#xff08;异常区域过多不好处理&#xff09;。 在Halcon中&#xff0c;halcon对颜色提取采用MLP&#xff08;多层感知…

swagger1.2 apiPost工具测试接口没有问题,换成swagger 接口调测时报错 Required request body is missing

把 请求方法由get换成post GetMapping换成 PostMapping 原因apiPost自动把请求json参数封装到请求体里了&#xff0c; 但swagger没有封装&#xff0c;通过networker可以看到载荷里并没有任何东西

nginx记录配置文件

查询当前域名配置所在的nginx文件路径 1&#xff1a;nginx -t 2&#xff1a;cd /usr/local/nginx/conf (如果没看到conf文件&#xff0c;那就根据不同公司定制的规则&#xff0c;这里是才conf下的vhost/) 3:cat xxx.conf 能看到 包应该要放的位置 4&#xff1a;把包解压到…

W6100-EVB-Pico评估版介绍

文章目录 1 简介2 硬件资源2.1 硬件规格2.2 引脚定义2.3 工作条件 3 参考资料3.1 Datasheet3.2 原理图3.3 尺寸图&#xff08;尺寸&#xff1a;mm&#xff09;3.4 参考例程 4 硬件协议栈优势 1 简介 W6100-EVB-Pico是一款基于树莓派RP2040和全硬件TCP/IP协议栈以太网芯片W6100的…

Android studio 多界面的跳转和返回

一、新建一个Empty Activity项目&#xff1a; 二、修改activity_main.xml布局文件&#xff1a; <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/a…