The purpose of traffic limiting is to protect the system by limiting the rate of concurrent access/requests or requests within a time window, and to deny service once the rate reaches the limit.

A few days ago in DD’s public account, I read an article about using guava to achieve single-application flow limiting scheme — “original text, reference” Redis in Action “to achieve a Jedis version, all belong to the business level restrictions. Common traffic limiting policies in actual scenarios:

  • Nginx traffic limiting at the access layer limits traffic at the Nginx layer according to certain rules, such as accounts, IP addresses, and system call logic

  • Service application system traffic limiting Traffic controlled by service code This traffic can be called a semaphore, which can be understood as a lock. It limits how many processes can access a resource at the same time.

Code implementation

import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; import redis.clients.jedis.ZParams; import java.util.List; import java.util.UUID; /** * @email [email protected] * @data 2017-08 */ public class RedisRateLimiter { private static final String BUCKET =  "BUCKET"; private static final String BUCKET_COUNT = "BUCKET_COUNT"; private static final String BUCKET_MONITOR = "BUCKET_MONITOR"; static String acquireTokenFromBucket( Jedis jedis, int limit, long timeout) { String identifier = UUID.randomUUID().toString(); long now = System.currentTimeMillis(); Transaction transaction = jedis.multi(); / / delete the semaphore transaction. ZremrangeByScore (BUCKET_MONITOR. GetBytes (), "- inf." getBytes (), String.valueOf(now - timeout).getBytes()); ZParams params = new ZParams(); Params. WeightsByDouble (1.0, 0.0); transaction.zinterstore(BUCKET, params, BUCKET, BUCKET_MONITOR); // Counter increment.incr (BUCKET_COUNT); List<Object> results = transaction.exec(); long counter = (Long) results.get(results.size() - 1); transaction = jedis.multi(); transaction.zadd(BUCKET_MONITOR, now, identifier); transaction.zadd(BUCKET, counter, identifier); transaction.zrank(BUCKET, identifier); results = transaction.exec(); Long rank = (long) results.get(results.size() -1); if (rank < limit) { return identifier; } else {transaction = jedis.multi();} else {transaction = jedis.multi(); transaction.zrem(BUCKET_MONITOR, identifier); transaction.zrem(BUCKET, identifier); transaction.exec(); } return null; }}Copy the code

call

Test interface call @getMapping ("/") public void index(HttpServletResponse Response) throws IOException {Jedis Jedis = jedisPool.getResource(); String token = RedisRateLimiter.acquireTokenFromBucket(jedis, LIMIT, TIMEOUT); if (token == null) { response.sendError(500); }else{//TODO your business logic} jedispool.returnResource (jedis); }Copy the code

To optimize the

Optimize code with interceptors + annotations

The interceptor

@Configuration
static class WebMvcConfigurer extends WebMvcConfigurerAdapter {
    private Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class);
    @Autowired
    private JedisPool jedisPool;

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HandlerInterceptorAdapter() {
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                                     Object handler) throws Exception {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                Method method = handlerMethod.getMethod();
                RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);

                if (rateLimiter != null){
                    int limit = rateLimiter.limit();
                    int timeout = rateLimiter.timeout();
                    Jedis jedis = jedisPool.getResource();
                    String token = RedisRateLimiter.acquireTokenFromBucket(jedis, limit, timeout);
                    if (token == null) {
                        response.sendError(500);
                        return false;
                    }
                    logger.debug("token -> {}",token);
                    jedis.close();
                }
                return true;
            }
        }).addPathPatterns("/*");
    }
}
Copy the code

Custom annotation

/** * @email [email protected] * @data 2017-08 * stream limiting annotations */ @target (elementType.method) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimiter { int limit() default 5; int timeout() default 1000; }Copy the code

use

@RateLimiter(limit = 2, timeout = 5000)
@GetMapping("/test")
public void test() {
}
Copy the code

Concurrent test

Tools: apache – jmeter – 3.2

Note: The interface that did not get the semaphore returns 500 and status is red; the interface that got the semaphore returns 200 and status is green.

When the limit request semaphore is 2, concurrent 5 threads:

When the request semaphore limit is 5 and 10 threads are concurrent:

data

Implementation based on REids + Lua

Kaitao Zhang – Talk about limiting traffic effects in high concurrency systems -1

conclusion

  1. For semaphore operations, use transaction operations.
  2. Do not use timestamps as sorting scores for semaphores, because in distributed environments, unequal semaphores can occur due to time differences between nodes.
  3. It would be convenient to extract this code as an @ratelimiter annotation and then use it on methods
  4. Different interface flow control, can refer to the source code inside RedisRateLimiterPlus, is nothing more than each interface to generate a monitoring parameter
  5. Source git.oschina.net/boding1/pig…