guava ratelimiter current limiter(2)

[Subscribe to the collection of columns , pay attention to the public account, all paid articles of the author can be read (continuously updated)]

  This article is a companion article to "Guava RateLimiter Internet Current Limiting (Part 1)" , which mainly introduces the java implementation of the algorithm. Including counter method , sliding window counting method, funnel bucket algorithm , token bucket algorithm .

Guava ratelimiter project overview

insert image description here

Simple counting method based on redis

Create a new springboot project and introduce dependencies

< properties > 
    < java.version > 1.8 < /java.version > < spring.version > 2.3.1.RELEASE < /spring.version > < / properties > _ _ _ _ _ _ _ _ _ _ _ _ _
    


< dependencies > 
    < dependency > 
        < groupId > org.springframework.boot < / groupId > < artifactId > spring - boot - starter - web < / artifactId > < / dependency > _ _ _ _
        
    

    < dependency > 
        < groupId > org.springframework.data < / groupId > < artifactId > spring - data - redis < / artifactId > < version > $ { spring.version } < / version > < / dependency > _ __ _ _ _
        
        
    

    < dependency > 
        < groupId > org.apache.commons < / groupId > < artifactId > commons - pool2 < / artifactId > < version > 2.8.0 < / version > < / dependency > __ _ _ _
        
        
    

    < dependency > 
        < groupId > io.lettuce < / groupId > < artifactId > lettuce - core < / artifactId > < version > 5.3.2.RELEASE < / version > < / dependency > < / dependencies > _ _ _ _ _
        
        
    

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

Configure application.properties

server . port = 8888 
# Redis database index (default is 0 )
spring.redis.database = 0 
# _ _ _ _ server address
spring.redis.host = 127.0.0.1 
# _ _ _ _ _ _ connection port
spring.redis.port = 6379 
# _ _ _ _ server empty)
spring.redis.password _ _ _ _ =
# The maximum number of connections in the connection pool (use a negative value to indicate no limit)
spring.redis.jedis.pool.max - active = 20 _ _ _ _ _ _ _ _
# Connection pool maximum blocking wait time (use a negative value to indicate no limit)
spring.redis.jedis.pool.max - wait = 1000 _ _ _ _ _ _ _ _
# Maximum idle connections in the connection pool
spring.redis.jedis.pool.max - idle = 10 _ _ _ _ _ _ _ _
# Minimum idle connection in the connection pool
spring.redis.jedis.pool.min - idle = 0 _ _ _ _ _ _ _ _
# Connection timeout (ms)
spring.redis.timeout = 2000 _ _ _ _
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Write RedisCountLimit

Redis-based incr mechanism

import  org.springframework.beans.factory.annotation.Autowired ; import org.springframework.data.redis.core.StringRedisTemplate ; import org.springframework.stereotype.Component ; import java.time.LocalTime ; import java.util.concurrent . _ _ _ _ _ _ _ _ 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
 
 
 TimeUnit ;

/**
 * Counting method current limit
 */ 
@Component 
public  class  RedisCountLimit  { 
    public  static  final  String KEY =  "ratelimit_" ; 
    public  static  final  int LIMIT =  10 ;

    @Autowired 
    StringRedisTemplate redisTemplate ;

    public  boolean  triggerLimit ( String reqPath )  { 
        String redisKey = KEY + reqPath ; 
        Long count = redisTemplate . opsForValue ( ) . increment ( redisKey ,  1 ) ; 
        System . out . println ( LocalTime . now ( )  +  " "  + reqPath +  " "  + count ); 
        if  ( count !=  null  && count ==  1 )  { 
            redisTemplate . expire ( redisKey ,  60 ,  TimeUnit . SECONDS ) ; 
        } 
        // Prevent concurrent operations without setting a timeout, so the key will never expire, which is a risk 
        if  ( redisTemplate . getExpire ( redisKey ,  TimeUnit . SECONDS )  ==  - 1 )  { 
            redisTemplate . expire (redisKey ,  60 ,  TimeUnit . SECONDS ) ; 
        }

        if  ( count > LIMIT )  { 
            System . out . println ( LocalTime . now ( )  +  " "  + reqPath +  " count is:"  + count +  ", trigger current limit" ) ; 
            return  true ; 
        }

        return  false ; 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

Controller layer integration

import  com.bigbird.ratelimit.rediscount.RedisCountLimit ; import org.springframework.beans.factory.annotation.Autowired ; import org.springframework.web.bind.annotation.RequestMapping ; 
import org.springframework.web.bind.annotation . _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
 
 RestController ;

import  javax . servlet . http . HttpServletRequest ; 
import  java . time . LocalDateTime ;

/**
 * Redis-based counter current limiting demo
 */ 
@RestController 
public  class  RedisCountLimitController  { 
    @Autowired 
    RedisCountLimit redisCountLimit ;

    @RequestMapping ( "/rediscount" ) 
    public  String  redisCount ( HttpServletRequest request )  { 
        String servletPath = request . getServletPath ( ) ; 
        boolean triggerLimit = redisCountLimit . triggerLimit ( servletPath ) ; 
        if  ( triggerLimit )  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath+  "The system is busy, try again later" ; 
        }  else  { 
            return  LocalDateTime .now ( ) + " " + servletPath + "Request Succeeded " ; } }    
        
    

    @RequestMapping ( "/rediscount2" ) 
    public  String  redisCount2 ( HttpServletRequest request )  { 
        String servletPath = request . getServletPath ( ) ; 
        boolean triggerLimit = redisCountLimit . triggerLimit ( servletPath ) ; 
        if  ( triggerLimit )  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath+  "System busy, try again later" ; 
        }  else  { 
            return  LocalDateTime .now ( ) + " " + servletPath + "Request Succeeded " ; } } }    
        
    

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

run the test

Start the springboot project to ensure that redis is running, browser access, and f5 refresh several times

  • http://localhost:8888/rediscount
  • http://localhost:8888/rediscount2

Sliding window counting method based on redis

Write RedisSlidingCountLimit

zset data structure through redis

import  org.springframework.beans.factory.annotation.Autowired ; import org.springframework.data.redis.core.StringRedisTemplate ; import org.springframework.stereotype.Component ; _ _ _ _ _ _ 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
 

import  java . time . LocalTime ; 
import  java . util . UUID ;

/**
 * Sliding window counting method current limit
 */ 
@Component 
public  class  RedisSlidingCountLimit  { 
    public  static  final  String KEY =  "slidelimit_" ; 
    public  static  final  int LIMIT =  10 ; 
    //Current limiting interval (seconds) 
    public  static  final  int PERIOD =  60 ;

    @Autowired 
    StringRedisTemplate redisTemplate ;

    public  boolean  triggerLimit ( String reqPath )  { 
        String redisKey = KEY + reqPath ;

        if  ( redisTemplate . hasKey ( redisKey ) )  { 
            Integer count = redisTemplate . opsForZSet ( ) . rangeByScore ( redisKey ,  System . currentTimeMillis ( )  - PERIOD *  1000 ,  System . currentTimeMillis ( ) ) . size ( ) ; 
            System . out . println (count ) ; 
            if  ( count !=  null  && count > LIMIT )  { 
                System . out . println ( LocalTime . now ( )  +  " "  + reqPath +  " count is:"  + count +  ", trigger current limit" ) ; 
                return  true ; 
            } 
        }

        long currentTime =  System . currentTimeMillis ( ) ; 
        redisTemplate . opsForZSet ( ) . add ( redisKey , UUID . randomUUID ( ) . toString ( ) , currentTime ) ; 
        // Clear old access data, for example, when period=60s, the flag is cleared before 60s Record of 
        redisTemplate.opsForZSet ( ) . removeRangeByScore ( redisKey , 0 , System . _  currentTimeMillis ( )  - PERIOD *  1000 ) ; 
        return  false ; 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

Controller layer integration

import  com.bigbird.ratelimit.rediscount.RedisSlidingCountLimit ; import org.springframework.beans.factory.annotation.Autowired ; import org.springframework.web.bind.annotation.RequestMapping ; 
import org.springframework.web.bind.annotation . _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
 
 RestController ;

import  javax . servlet . http . HttpServletRequest ; 
import  java . time . LocalDateTime ;

/**
 * Redis-based sliding window counter current limiting demo
 */ 
@RestController 
public  class  RedisSlidingCountLimitController  { 
    @Autowired 
    RedisSlidingCountLimit redisSlidingCountLimit ;

    @RequestMapping ( "/slidecount" ) 
    public  String  redisCount ( HttpServletRequest request )  { 
        String servletPath = request . getServletPath ( ) ; 
        boolean triggerLimit = redisSlidingCountLimit . triggerLimit ( servletPath ) ; 
        if  ( triggerLimit )  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath+  "The system is busy, try again later" ; 
        }  else  { 
            return  LocalDateTime .now ( ) + " " + servletPath + "Request Succeeded " ; } }    
        
    

    @RequestMapping ( "/slidecount2" ) 
    public  String  redisCount2 ( HttpServletRequest request )  { 
        String servletPath = request . getServletPath ( ) ; 
        boolean triggerLimit = redisSlidingCountLimit . triggerLimit ( servletPath ) ; 
        if  ( triggerLimit )  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath+  "System busy, try again later" ; 
        }  else  { 
            return  LocalDateTime .now ( ) + " " + servletPath + "Request Succeeded " ; } } }    
        
    

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

run the test

Start the springboot project, make sure that redis is running, access

  • http://localhost:8888/slidecount
  • http://localhost:8888/slidecount2

Funnel Bucket Algorithm

Writing LeakyBucket

import  java . time . LocalTime ;

/**
 * Funnel bucket algorithm current limiting
 */ 
public  class  LeakyBucket  { 
    /**
     * Processing quantity per second (water output rate)
     */ 
    private  int rate ;

    /**
     * Bucket capacity
     */ 
    private  int capacity ;

    /**
     * Current water volume
     */ 
    private  int water ;

    /**
     * Last refresh time
     */ 
    private  long refreshTime ;

    public  LeakyBucket ( int rate ,  int capacity )  { 
        this . capacity = capacity ; 
        this . rate = rate ; 
    }

    private  void  refreshWater ( )  { 
        long now =  System . currentTimeMillis ( ) ; 
        water =  ( int )  Math . max ( 0 , water -  ( now - refreshTime )  /  1000  * rate ) ; 
        refreshTime = now ; 
    }

    public  synchronized  boolean  triggerLimit ( String reqPath )  { 
        refreshWater ( ) ; 
        if  ( water < capacity )  { 
            water ++ ; 
            System . out . println ( LocalTime . now ( )  +  " "  + reqPath +  " current capacity is:"  +  ( capacity - water )  +  ",water is:"  +water +  ", the request succeeded" ) ; 
            return  false ; 
        }  else  { 
            System . out . println ( LocalTime . now ( )  +  " "  + reqPath +  " current capacity is:"  +  ( capacity - water )  +  ", water is: "  + water +  ", trigger current limit" ) ; 
            return  true ; 
        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

Controller layer integration

import  com . bigbird . ratelimit . leakybucket . LeakyBucket ; 
import  org . springframework . web . bind . annotation . RequestMapping ; 
import  org . springframework . web . bind . annotation . RestController ;

import  javax . servlet . http . HttpServletRequest ; 
import  java . time . LocalDateTime ;

/**
 * Funnel bucket algorithm current limiting demo
 */ 
@RestController 
public  class  LeakyBucketLimitController  { 
    LeakyBucket bucket1 =  new  LeakyBucket ( 2 ,  10 ) ; 
    LeakyBucket bucket2 =  new  LeakyBucket ( 2 ,  20 ) ;

    @RequestMapping ( "/leakyBucket1" ) 
    public  String  leakyBucket1 ( HttpServletRequest request )  { 
        String servletPath = request . getServletPath ( ) ; 
        boolean triggerLimit = bucket1 . triggerLimit ( servletPath ) ; 
        if  ( triggerLimit )  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath + "The system is busy, try again later" ; 
        }  else  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath +  "Request succeeded" ; 
        } 
    }

    @RequestMapping ( "/leakyBucket2" ) 
    public  String  leakyBucket2 ( HttpServletRequest request )  { 
        String servletPath = request . getServletPath ( ) ; 
        boolean triggerLimit = bucket2 . triggerLimit ( servletPath ) ; 
        if  ( triggerLimit )  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath + "The system is busy, try again later" ; 
        }  else  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath +  "Request succeeded" ; 
        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

run the test

Start the springboot project, visit the following address in the browser, and refresh the test continuously with f5

  • http://localhost:8888/leakyBucket1
  • http://localhost:8888/leakyBucket2

Token Bucket Algorithm (RateLimiter)

Implementation based on Guava RateLimiter

import dependencies

< dependency > 
    < groupId > com.google.guava < / groupId > < artifactId > guava < / artifactId > < version > 29.0 - jre < / version > < / dependency > _ _ _ _
    
    

  • 1
  • 2
  • 3
  • 4
  • 5

Write TokenBucket

import  com . google . common . util . concurrent . RateLimiter ; 
import  java . time . LocalTime ; 
import  java . util . concurrent . TimeUnit ;

/**
 * Token bucket algorithm current limit
 */ 
public  class  TokenBucket  { 
    /**
     * qps, the number of processing per second
     */ 
    private  int rate ; 
    private  RateLimiter rateLimiter ;

    public  TokenBucket ( int rate )  { 
        this . rate = rate ; 
        this . rateLimiter =  RateLimiter . create ( rate ) ; 
        //In actual business development, generally an individual in a current-limiting scenario corresponds to a RateLimiter instance 
        //For example, for customers End IP current limit, a static volatile Map <Ip,RateLimiter> will be created to save the current limiter of each IP 
        //For example, for url current limit, a static volatile Map <Url,RateLimiter> will be created to save the current limit of each Url //For example, for merchant current limiter 
        , a static volatile Map <MerchantId,RateLimiter> will be created to save the current limiter of each merchant 
        //Static volatile modified variables can ensure global visibility and unified configuration, and immediately after modifying the current limit configuration in real time effective 
    }

    public  boolean  triggerLimit ( String reqPath )  { 
        boolean acquireRes = rateLimiter . tryAcquire ( 500 ,  TimeUnit . MILLISECONDS ) ; 
        if  ( acquireRes )  { 
            System . out . println ( LocalTime . now ( )  +  " "  + reqPath +  ", the request succeeded" ) ; 
            return  false ; 
        } else  { 
            System . out . println ( LocalTime . now ( )  +  " "  + reqPath +  ", trigger current limit" ) ; 
            return  true ; 
        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

About volatile keyword refer to "volatile keyword analysis and practice"

Controller layer integration

import  com . bigbird . ratelimit . tokenbucket . TokenBucket ; 
import  org . springframework . web . bind . annotation . RequestMapping ; 
import  org . springframework . web . bind . annotation . RestController ;

import  javax . servlet . http . HttpServletRequest ; 
import  java . time . LocalDateTime ;

/**
 * Token bucket current limiting algorithm demo
 */ 
@RestController 
public  class  TokenBucketLimitController  { 
    /**
     * Speed ​​limit 1 per second
     */ 
    TokenBucket bucket1 =  new  TokenBucket ( 1 ) ; 
    /**
     * Speed ​​limit 2 per second
     */ 
    TokenBucket bucket2 =  new  TokenBucket ( 2 ) ;

    @RequestMapping ( "/tokenBucket1" ) 
    public  String  leakyBucket1 ( HttpServletRequest request )  { 
        String servletPath = request . getServletPath ( ) ; 
        boolean triggerLimit = bucket1 . triggerLimit ( servletPath ) ; 
        if  ( triggerLimit )  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath + "The system is busy, try again later" ; 
        }  else  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath +  "Request succeeded" ; 
        } 
    }

    @RequestMapping ( "/tokenBucket2" ) 
    public  String  leakyBucket2 ( HttpServletRequest request )  { 
        String servletPath = request . getServletPath ( ) ; 
        boolean triggerLimit = bucket2 . triggerLimit ( servletPath ) ; 
        if  ( triggerLimit )  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath + "The system is busy, try again later" ; 
        }  else  { 
            return  LocalDateTime . now ( )  +  " "  + servletPath +  "Request succeeded" ; 
        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

run the test

Start the springboot project, visit the following address in the browser, and refresh the test continuously with f5

  • http://localhost:8888/tokenBucket1
  • http://localhost:8888/tokenBucket2

In actual business development, generally an individual in a current-limiting scenario corresponds to a RateLimiter instance

For example, for client IP current limiting, a static volatile Map <Ip, RateLimiter> will be created to save the current limiter of each IP

For example, for the current limit of the interface url, a static volatile Map <Url, RateLimiter> will be created to save the current limiter of each Url

For example, for merchant current limiter, a static volatile Map <MerchantId,RateLimiter> will be created to save the current limiter of each merchant

Variables modified by static volatile can ensure global uniform configuration, and take effect immediately after real-time modification of the current limiting configuration

Custom annotations, aop package current limiting

  The above implementation method is simple and rude. In practical applications, custom annotations can be encapsulated , and the controller layer interface can be automatically limited and intercepted through aop. Without further ado, let's get to the code. The example below is based on the RateLimiter token bucket. Readers of other algorithms can refer to this example to encapsulate it by themselves.

import dependencies

< dependency > 
    < groupId > org.springframework.boot < / groupId > < artifactId > spring - boot - starter - aop < / artifactId > < / dependency > _ _ _ _
    

  • 1
  • 2
  • 3
  • 4

Write custom annotations

import  java . lang . annotation . ElementType ; 
import  java . lang . annotation . Retention ; 
import  java . lang . annotation . RetentionPolicy ; 
import  java . lang . annotation . Target ;

@Target ( value =  ElementType . METHOD ) 
@Retention ( RetentionPolicy . RUNTIME ) 
public  @interface  ExtRateLimiter  { 
    double  permitsPerSecond ( ) ;

    long  timeout ( ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Write aop aspects

import  com . bigbird . ratelimit . annotation . ExtRateLimiter ; 
import  com . google . common . util . concurrent . RateLimiter ; 
import  org . aspectj . lang . ProceedingJoinPoint ; 
import  org . aspectj . lang . annotation . Around ; 
import  org . aspectj .lang . annotation . Aspect ; 
import  org . aspectj . lang . annotation . Pointcut ; 
import  org . aspectj . lang . reflect . MethodSignature ; 
import  org . springframework . stereotype . Component ; 
import  org . springframework . web . context . request . org.springframework.web.context.request.RequestContextHolder ;
 import  org.springframework.web.context.request.ServletRequestAttributes ; _ _ _ _ _ _ _ _ _ _

import  javax . servlet . http . HttpServletResponse ; 
import  java . io . IOException ; 
import  java . io . PrintWriter ; 
import  java . lang . reflect . Method ; 
import  java . time . LocalTime ; 
import  java . util . concurrent . ConcurrentHashMap ; 
_  _.util.concurrent.TimeUnit ; _ _ _ _ _

/**
 * Encapsulate the current limiting annotation based on RateLimiter
 */ 
@Component 
@Aspect 
public  class  RateLimiterAop  {

    /**
     * Save the corresponding relationship between the interface path and the current limiter
     */ 
    private  ConcurrentHashMap < String ,  RateLimiter > rateLimiters =  new  ConcurrentHashMap ( ) ;

    @Pointcut ( "execution(public * com.bigbird.ratelimit.controller.*.*(..))" ) 
    public  void  rateLimiterAop ( )  { 
    }

    /**
     * Use wraparound notifications to intercept all Controller requests
     *
     * @param proceedingJoinPoint
     * @return
     */ 
    @Around ( "rateLimiterAop()" ) 
    public  Object  doBefore ( ProceedingJoinPoint proceedingJoinPoint )  throws  Throwable  { 
        MethodSignature signature =  ( MethodSignature ) proceedingJoinPoint . getSignature ( ) ; 
        Method method = signature . getMethod ( ) ; 
        if  ( method ==  null )  { 
            return  null ;
        }

        ExtRateLimiter extRateLimiter = method . getDeclaredAnnotation ( ExtRateLimiter . class ) ; 
        if  ( extRateLimiter ==  null )  { 
            return proceedingJoinPoint . proceed ( ) ; 
        }

        double permitsPerSecond = extRateLimiter . permitsPerSecond ( ) ; 
        long timeout = extRateLimiter . timeout ( ) ;

        ServletRequestAttributes requestAttributes =  ( ServletRequestAttributes )  RequestContextHolder . getRequestAttributes ( ) ; 
        String requestURI = requestAttributes . getRequest ( ) . getRequestURI ( ) ; 
        RateLimiter rateLimiter = rateLimiters . get ( requestURI ) ; 
        if  ( rateLimiter ==  null )  { 
            rateLimiter = RateLimiter . create ( permitsPerSecond ) ; 
            RateLimiter rateLimiterPrevious = rateLimiters . putIfAbsent ( requestURI , rateLimiter ) ; 
            if  ( rateLimiterPrevious !=  null )  { 
                rateLimiter = rateLimiterPrevious ; 
            } 
        }

        boolean tryAcquire = rateLimiter . tryAcquire ( timeout ,  TimeUnit . MILLISECONDS ) ; 
        if  ( ! tryAcquire )  { 
            System . out . println ( LocalTime . now ( )  +  " "  + requestURI +  "trigger current limit" ) ; 
            doFallback ( ) ; 
            return  null ; 
        }

        System . out . println ( LocalTime . now ( )  +  " "  + requestURI +  "Request Succeeded" ) ; 
        return proceedingJoinPoint . proceed ( ) ; 
    }

    private  void  doFallback ( )  { 
        ServletRequestAttributes requestAttributes =  ( ServletRequestAttributes )  RequestContextHolder . getRequestAttributes ( ) ; 
        HttpServletResponse response = requestAttributes . getResponse ( ) ; 
        response . setContentType ( "text/html;charset=UTF-8" ) ; 
        PrintWriter writer =  null ; 
        try  {  
            writer= response . getWriter ( ) ; 
            writer . println ( "The system is busy, please try again later!" ) ; 
        }  catch  ( IOException e )  { 
            e . printStackTrace ( ) ; 
        }  finally  { 
            writer . close ( ) ; 
        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

Controller layer integration

Add ExtRateLimiter annotation settings to the interface to be limited

import  com.bigbird.ratelimit.annotation.ExtRateLimiter ; import org.springframework.web.bind.annotation.RequestMapping ; import org.springframework.web.bind.annotation.RestController ; _ _ _ _ _ _ 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
 

import  javax . servlet . http . HttpServletRequest ; 
import  java . time . LocalTime ;

/**
 * Customize the annotation to identify the interface for current limiting
 */ 
@RestController 
public  class  ExtRateLimiterController  { 
    @RequestMapping ( "/extRate1" ) 
    @ExtRateLimiter ( permitsPerSecond =  0.5 , timeout =  500 ) 
    public  String  extRate1 ( HttpServletRequest request )  { 
        return  LocalTime . now ( )  +  " "  + request . getRequestURI ( )  +  "Request Succeeded" ; 
    }

    @RequestMapping ( "/extRate2" ) 
    @ExtRateLimiter ( permitsPerSecond =  2 , timeout =  500 ) 
    public  String  extRate2 ( HttpServletRequest request )  { 
        return  LocalTime . now ( )  +  " "  + request . getRequestURI ( )  +  "Request successful" ; 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

run the test

Start the springboot project, visit the following address in the browser, and refresh the test continuously with f5

  • http://localhost:8888/extRate1
  • http://localhost:8888/extRate2

summary

This article introduces the concepts and algorithms related to Internet current limiting in an   easy-to-understand manner , and implements it with Java code . Including counter method , sliding window counting method, funnel bucket algorithm , token bucket algorithm . Finally, a custom current limiting annotation and aop interception interface current limiting are encapsulated.

Code download address: https://github.com/bigbirditedu/learn-ratelimit

Related: guava ratelimiter current limiter(2)