API rate limiting use Zuul and Redis
Yes, this is just another blog about API rate limiting. There are already tons of articles about this topic all over the Internet [1] [2], so this blog is not about the benefit and algorithm of API rate limiting but some practical problems when we implement API rate limiting in our API Gateway platform.
Problems
- Various rate limiting criteria.
Besides the normal "my endpoint can only be accessed X times in Y minutes/hours" situation, we need the rule of API rate limiting to be more flexible and configurable. For example, you can protect your API with a certain threshold, however, if there is an attacker who brutes force this API, this threshold will be easily reached and the “good” users will be blocked
Sometimes we need rate limit API based on client IP and sometimes we need based on user ID or user token.
- Be agile when facing incidents.
Incidents and outrage are normal. Instead of trying to eliminate them we should be agile when they happen. We need to change the rate limiting rule in real time, restart the application to load some new value from an external place is not an option.- Non-invasive integration with the service.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public HttpResponse api(HttpRequest request){ | |
String uri = request.getUri(); | |
RateLimiter limiter = new RateLimiter(uri, threaHold, timeRange); | |
if( limiter.limit()){ | |
return Http.TOO_MANY_REQUESTS; | |
}else{ | |
... | |
some business code | |
... | |
rethurn Http.OK; | |
} | |
} |
Solutions
After rounds of discussion, we came up a configuration like this
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name":"pay_api_rate_limiting", | |
"uriPattern":"/v{apiVersion}/pay", | |
"rules":[ | |
{ | |
"duration":1, | |
"timeUnit":"MINUTES", | |
"limit":50 | |
}, | |
{ | |
"duration":1, | |
"timeUnit":"HOURS", | |
"limit":200 | |
} | |
], | |
"keys":[ | |
"IP" | |
] | |
} | |
Other details:
- We use the RateLimitJ as our rate limiting SDK which provides both Redis and memory based repository.
- We extend the Netflix Archaius, we store the configuration as a JSON string in Dynamodb and in the application we use the JSON string as a dynamic object. Every time the Dynamodb has a new value, the dynamic object will refresh automatically.
- A new custom Zuul filter is added, when a request is limited, Zuul will return it immediately without routing.
Performance
We did some stress tests about API rate limiting, the environment set is:
- Zuul: EC2 c5.2xlarge, with 10GB JVM, G1 garbage collector
- Redis: cache.r4.large
- Downstream service: a cluster with 10 instance
Jmeter set up: 50 threads, each thread makes 4000 requests.
Here are the results of 3 round tests:
![]() |
Fig. 3. With rate limiting of 100000 requests/hour
|
- The Redis based rate limiting will introduce 3 to 5 milliseconds latency, which is also verified by our debug log.
- After the API is rate limited, Zuul will return "TOO_MANY_REQUESTS" without routing request to downstream service. In production, this will reduce the average response time because the p50 response time is around 300 milliseconds.
0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home