概念

限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理

常用限流算法

常用的限流算法有两种:漏桶算法令牌桶算法

漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

控制并发数量

信号量Semaphore

Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。

简单的说:Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。

控制访问速率

限流工具类RateLimiter

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法来完成限流,非常易于使用。

RateLimiter源码分析

调用create接口时,实际实例化的为SmoothBursty类

  static final class SmoothBursty extends SmoothRateLimiter {/** The work (permits) of how many seconds can be saved up if this RateLimiter is unused? */final double maxBurstSeconds;/*** The currently stored permits.* 当前存储令牌数*/double storedPermits;/*** The maximum number of stored permits.* 最大存储令牌数*/double maxPermits;/*** The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits* per second has a stable interval of 200ms.* 添加令牌时间间隔,可以理解成生成一个令牌需要的时间,这里是微秒单位*/double stableIntervalMicros;.......}

RateLimiter 创建

public static RateLimiter create(double permitsPerSecond) {return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);rateLimiter.setRate(permitsPerSecond);return rateLimiter;
}

acquire()方法

public double acquire(int permits) {//计算获取这些请求需要让线程等待多长时间long microsToWait = reserve(permits);//让线程阻塞microTowait微秒长的时间stopwatch.sleepMicrosUninterruptibly(microsToWait);//返回阻塞的时间return 1.0 * microsToWait / SECONDS.toMicros(1L);}

reserve()方法

  final long reserve(int permits) {//检查permits是否合法checkPermits(permits);//保证线程安全synchronized (mutex()) {return reserveAndGetWaitLength(permits, stopwatch.readMicros());}}final long reserveAndGetWaitLength(int permits, long nowMicros) {long momentAvailable = reserveEarliestAvailable(permits, nowMicros);return max(momentAvailable - nowMicros, 0);}

reserveEarliestAvailable()

storedPermitsToSpend为桶中可以消费的令牌数,freshPermits为还需要的(需要补充的)令牌数,根据该值计算需要等待的时间,追加并更新到nextFreeTicketMicros


//获取requiredPermits个令牌,并返回需要等待到的时间点
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {resync(nowMicros);long returnValue = nextFreeTicketMicros;double storedPermitsToSpend = min(requiredPermits, this.storedPermits);double freshPermits = requiredPermits - storedPermitsToSpend;//当 requiredPermits>storedPermits才会有实际意义,这段代码允许我们提前获取令牌,但是这种情况会造成下一次令牌生成的时间推迟。有种预支工资的意思long waitMicros =storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)+ (long) (freshPermits * stableIntervalMicros);this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);//更新可消费的令牌this.storedPermits -= storedPermitsToSpend;return returnValue;}```#### resync()若当前时间晚于nextFreeTicketMicros,则计算该段时间内可以生成多少令牌,将生成的令牌加入令牌桶中并更新数据```void resync(long nowMicros) {// if nextFreeTicket is in the past, resync to nowif (nowMicros > nextFreeTicketMicros) {//时间间隔内生成的新令牌double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();//更新最大令牌数量storedPermits = min(maxPermits, storedPermits + newPermits);//下次可以获取的时间nextFreeTicketMicros = nowMicros;}}

tryAcquire函数可以尝试在timeout时间内获取令牌,如果可以则挂起等待相应时间并返回true,否则立即返回false
canAcquire用于判断timeout时间内是否可以获取令牌。

应用

拦截器配置,可以统一配置所有请求的上限,也可以单独对某个 url配置,该拦截器是基于 SpringMvc 的RequestMappingHandlerMapping获取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>

RequestLimitInterceptor 拦截器

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();@Overridepublic 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;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic 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;}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic 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;}
}

总结

RateLimiter通常用于限制访问某些物理或逻辑资源的速率。这与jdk并发包中的Semaphore相反,它限制并发访问的数量而不是速率(注意,并发和速率是密切相关的)。

参考:https://segmentfault.com/a/1190000012875897

SpringMvc 限流之 RateLimiter相关推荐

  1. springmvc限流解决方案

    本文采用3中限流方案: 1,谷歌的guava框架 2,使用redis技术 3,使用lua + redis 技术 限流方案类型 1,令牌桶限流(guava) 2,计数器限流(redis) 各位看官可根据 ...

  2. Eureka的限流算法类RateLimiter源码解读

    Eureka的限流算法类RateLimiter是基于令牌桶算法来实现的,下面看一看令牌桶算法的原理: 对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输.这时候漏 ...

  3. 【Flink】Flink 消费 kafka 实现 限流处理 RateLimiter

    文章目录 1.概述 2.案例 2.1 案例1 纪念一波,九师兄博客热门订阅专栏时常名列前茅,我飘了,哈哈哈哈,得意的笑 1.概述 首先看看 [java]高并发之限流 RateLimiter使用 这个去 ...

  4. 超详细的Guava RateLimiter限流原理解析

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 限流是保护高并发系统的三把利器之一,另外两个是缓存和降级.限流在很多场景中用来限制并发和请求量,比 ...

  5. Guava RateLimiter限流原理解析

    来源:https://zhuanlan.zhihu.com/p/60979444 限流是保护高并发系统的三把利器之一,另外两个是缓存和降级.限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自 ...

  6. 【java】高并发之限流 RateLimiter使用

    1.概述 转载原文:高并发之限流 你可能知道高并发系统需要限流这个东西,但具体是限制的什么,该如何去做,还是模凌两可.我们接下来系统性的给它归个小类,希望对你有所帮助. google guava中提供 ...

  7. RateLimiter高并发访问限流

    使用RateLimiter完成简单的大流量限流,抢购秒杀限流. RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的 ...

  8. Guava系列之限流RateLimiter

    在互联网高并发场景下,限流是用来保证系统稳定性的一种手段,当系统遭遇瞬时流量激增时,可能会由于系统资源耗尽导致宕机.而限流可以把一小部分流量拒绝掉,保证大部分流量可以正常访问,从而保证系统只接收承受范 ...

  9. 限流-RateLimiter

    限流-RateLimiter 工作中对外提供的API 接口设计都要考虑限流,如果不考虑限流,会成系统的连锁反应,轻者响应缓慢,重者系统宕机,整个业务线崩溃,如何应对这种情况呢,我们可以对请求进行引流或 ...

最新文章

  1. SSH免密登录(内含批量配置脚本)
  2. CUDA Study ---- Hardware Architecture
  3. java 处理byte_java - 文件到Java中的byte [] - 堆栈内存溢出
  4. CG-CTF-Web-/x00
  5. iOS - 富文本AttributedString
  6. java时间日期格式器_JAVA基础类库(二)-----日期、时间类和格式器
  7. java 中的doit(n)_CoreJava测试题(含答案).docx
  8. SEO能给独立站系统带来巨大的搜索流量吗?
  9. 【汇编语言与计算机系统结构笔记17】MIPS 汇编初步
  10. iframe嵌套微信网页,图片无法显示问题
  11. Python推箱子小游戏源代码
  12. 【名牌电脑制作隐藏分区与释放隐藏分区的方法】
  13. php公众号开发 点菜,微信公众号点餐系统怎么弄 微信点餐系统怎么开发
  14. iphone5s已停用连接itunes怎么办?苹果5s已停用连接itunes解决方法
  15. 单例模式、Single
  16. 尝鲜——Windows11 安装教程 (无视TMP2.0)
  17. EXCEL 制作三维散点图
  18. 无法启动此程序因为计算机丢失dtlui,用360重装大师重装系统后开机提示计算机中丢失DTLUI.dll? 爱问知识人...
  19. 使用函数输出指定范围内的Fibonacci数
  20. Can‘t resolve ‘redux‘ in

热门文章

  1. 为什么不直接调用函数而要使用函数指针?
  2. html 语音识别输入法,从游戏语音输入说起:语音识别如何引领输入法变局
  3. 中国认证认可服务行业“十四五”发展规划及经营模式分析报告2022~2028年
  4. 计算机游戏的应用领域,达龙云电脑:云游戏即将开启游戏领域新篇章
  5. 题8.16:输入一个字符串, 内有数字和非数字字符, 例如:A123x456 17960 ? , 302tab5876,将其中连续的数字作为一个整数, 依次存放到一数组a中。例如, 123 放在a[0
  6. 以太坊Truffle框架构建Dapp
  7. C++ asm关键字
  8. Python练习——L1-060 心理阴影面积 (5分)
  9. 奇异值分解(SVD)与PCA(主成分分析)
  10. 【转帖】windows 服务大全