查看: 1435|回复: 0

[Java学习] springmvc限流拦截器的示例代码

发表于 2018-1-5 08:00:00

限流器算法

目前常用限流器算法为两种:令牌桶算法和漏桶算法,主要区别在于:漏桶算法能够强行限制请求速率,平滑突发请求,而令牌桶算法在限定平均速率的情况下,允许一定量的突发请求

下面是从网上找到的两张算法图示,就很容易区分这两种算法的特性了

漏桶算法

令牌桶算法

针对接口来说,一般会允许处理一定量突发请求,只要求限制平均速率,所以令牌桶算法更加常见。

令牌桶算法工具RateLimiter

目前本人常用的令牌桶算法实现类当属google guava的RateLimiter,guava不仅实现了令牌桶算法,还有缓存、新的集合类、并发工具类、字符串处理类等等。是一个强大的工具集

RateLimiter api可以查看并发编程网guava RateLimiter的介绍

RateLimiter源码分析

RateLimiter默认情况下,最核心的属性有两个nextFreeTicketMicros,下次可获取令牌时间,storedPermits桶内令牌数。

判断是否可获取令牌:

每次获取令牌的时候,根据桶内令牌数计算最快下次能获取令牌的时间nextFreeTicketMicros,判断是否可以获取资源时,只要比较nextFreeTicketMicros和当前时间就可以了,so easy

获取令牌操作:

对于获取令牌,根据nextFreeTicketMicros和当前时间计算出新增的令牌数,写入当前令牌桶令牌数,重新计算nextFreeTicketMicros,桶内还有令牌,则写入当前时间,并减少本次请求获取的令牌数。

如同java的AQS类一样,RateLimiter的核心在tryAcquire方法

  1. public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
  2. //尝试获取资源最多等待时间
  3. long timeoutMicros = max(unit.toMicros(timeout), 0);
  4. //检查获取资源数目是否正确
  5. checkPermits(permits);
  6. long microsToWait;
  7. //加锁
  8. synchronized (mutex()) {
  9. //当前时间
  10. long nowMicros = stopwatch.readMicros();
  11. //判断是否可以在timeout时间内获取资源
  12. if (!canAcquire(nowMicros, timeoutMicros)) {
  13. return false;
  14. } else {
  15. //可获取资源,对资源进行重新计算,并返回当前线程需要休眠时间
  16. microsToWait = reserveAndGetWaitLength(permits, nowMicros);
  17. }
  18. }
  19. //休眠
  20. stopwatch.sleepMicrosUninterruptibly(microsToWait);
  21. return true;
  22. }
复制代码

判断是否可获取令牌:

  1. private boolean canAcquire(long nowMicros, long timeoutMicros) {
  2. //最早可获取资源时间-等待时间<=当前时间 方可获取资源
  3. return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
  4. }
复制代码

RateLimiter默认实现类的queryEarliestAvailable是取成员变量nextFreeTicketMicros

获取令牌并计算需要等待时间操作:

  1. final long reserveAndGetWaitLength(int permits, long nowMicros) {
  2. //获取下次可获取时间
  3. long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
  4. //计算当前线程需要休眠时间
  5. return max(momentAvailable - nowMicros, 0);
  6. }
复制代码
  1. final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  2. //重新计算桶内令牌数storedPermits
  3. resync(nowMicros);
  4. long returnValue = nextFreeTicketMicros;
  5. //本次消耗的令牌数
  6. double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
  7. //重新计算下次可获取时间nextFreeTicketMicros
  8. double freshPermits = requiredPermits - storedPermitsToSpend;
  9. long waitMicros =
  10. storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
  11. + (long) (freshPermits * stableIntervalMicros);
  12. this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
  13. //减少桶内令牌数
  14. this.storedPermits -= storedPermitsToSpend;
  15. return returnValue;
  16. }
复制代码

实现简单的spring mvc限流拦截器

实现一个HandlerInterceptor,在构造方法中创建一个RateLimiter限流器

  1. public SimpleRateLimitInterceptor(int rate) {
  2. if (rate > 0)
  3. globalRateLimiter = RateLimiter.create(rate);
  4. else
  5. throw new RuntimeException("rate must greater than zero");
  6. }
复制代码

在preHandle调用限流器的tryAcquire方法,判断是否已经超过限制速率

  1. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  2. if (!globalRateLimiter.tryAcquire()) {
  3. LoggerUtil.log(request.getRequestURI()+"请求超过限流器速率");
  4. return false;
  5. }
  6. return true;
  7. }
复制代码

在dispatcher-servlet.xml中配置限流拦截器

  1. <mvc:interceptors>
  2. <!--限流拦截器-->
  3. <mvc:interceptor>
  4. <mvc:mapping path="/**"/>
  5. <bean class="limit.SimpleRateLimitInterceptor">
  6. <constructor-arg index="0" value="${totalRate}"/>
  7. </bean>
  8. </mvc:interceptor>
  9. </mvc:interceptors>
复制代码

复杂版本的spring mvc限流拦截器

使用Properties传入拦截的url表达式->速率rate

  1. <mvc:interceptor>
  2. <mvc:mapping path="/**"/>
  3. <bean class="limit.RateLimitInterceptor">
  4. <!--单url限流-->
  5. <property name="urlProperties">
  6. <props>
  7. <prop key="/get/{id}">1</prop>
  8. <prop key="/post">2</prop>
  9. </props>
  10. </property>
  11. </bean>
  12. </mvc:interceptor>
复制代码

为每个url表达式创建一个对应的RateLimiter限流器。url表达式则封装为org.springframework.web.servlet.mvc.condition.PatternsRequestCondition。PatternsRequestCondition是springmvc 的DispatcherServlet中用来匹配请求和Controller的类,可以判断请求是否符合这些url表达式。

在拦截器preHandle方法中

  1. //当前请求路径
  2. String lookupPath = urlPathHelper.getLookupPathForRequest(request);
  3. //迭代所有url表达式对应的PatternsRequestCondition
  4. for (PatternsRequestCondition patternsRequestCondition : urlRateMap.keySet()) {
  5. //进行匹配
  6. List<String> matches = patternsRequestCondition.getMatchingPatterns(lookupPath);
  7. if (!matches.isEmpty()) {
  8. //匹配成功的则获取对应限流器的令牌
  9. if (urlRateMap.get(patternsRequestCondition).tryAcquire()) {
  10. LoggerUtil.log(lookupPath + " 请求匹配到" + Joiner.on(",").join(patternsRequestCondition.getPatterns()) + "限流器");
  11. } else {
  12. //获取令牌失败
  13. LoggerUtil.log(lookupPath + " 请求超过" + Joiner.on(",").join(patternsRequestCondition.getPatterns()) + "限流器速率");
  14. return false;
  15. }
  16. }
  17. }
复制代码

具体的实现类

请见github

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持程序员之家。



回复

使用道具 举报