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