There are several different algorithms for rate limiting, each with its own advantages and disadvantages. Some of the most common algorithms include:
Algorithm | Advantages | Disadvantages |
Token Bucket | Simple to implement, accurate rate-limiting | Can be less effective at absorbing bursts of traffic |
Leaky Bucket | Can absorb bursts of traffic, and prevent the bucket from being completely full | Can be less accurate at rate limiting, more complex to implement |
Fixed Window Counter | Simple to implement, efficient | Can be less effective at absorbing bursts of traffic, and can lead to spikes in traffic at the start of each window |
Sliding Window Log | More accurate at rate limiting, can absorb bursts of traffic | More complex to implement, can be less efficient |
Sliding Window Counter | Combines the advantages of the fixed window counter and sliding window log algorithms | Can be more complex to implement than the fixed window counter algorithm |
The best approach to choose depends on your specific needs. If you need to implement a simple rate-limiting strategy that applies to all requests to your microservices, then implementing rate limiting at the reverse proxy layer is a good option. If you need to implement a more complex rate-limiting strategy, or if you need to implement rate limiting at the granular level of specific resources or endpoints, then implementing rate limiting at the microservice level is a better option.
Here are some additional factors to consider when choosing between the two approaches:
Here are some examples of when you might choose to implement rate limiting at the reverse proxy layer or at the microservice level:
Ultimately, the best way to decide which approach to choose is to carefully consider your specific needs and requirements.
const approuter = require("@sap/approuter");
const { rateLimiter } = require("./rate-limiter");
const ar = approuter();
ar.beforeRequestHandler.use("/", rateLimiter);
ar.start();
const rateLimit = require("express-rate-limit");
const WHITELISTED_PATH = "/skipThisPath";
const rateLimiter = rateLimit({
windowMs: 1*10*1000, // 10 seconds
max:30, // Limit each User ID to 10 requests per 10 seconds
standardHeaders: false,
legacyHeaders:false,
keyGenerator:(req) => {
return req.user.name;
},
skip: (req) => {
let requestURL = req.url;
if (requestURL.includes(WHITELISTED_PATH)) {
console.log("whitelisted path called");
return true;
} else {
return false;
}
},
handler: (req, res, next, options) => {
let timeInMss = new Date();
let timeDiff = req.rateLimit.resetTime.getTime() - timeInMss.getTime();
let seconds = Math.round(timeDiff / 1000);
res.writeHead(429, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
error: {
code: "429",
time: {
value: seconds,
},
},
})
);
},
});
module.exports = { rateLimiter };
The following are some common rate limiter libraries for Java Spring Boot apps:
Which rate limiter library you choose will depend on your specific needs and requirements. If you need a simple and lightweight library, then Guava RateLimiter is a good option. If you need a more feature-rich library, then Resilience4j RateLimiter or Bucket4j are good options.
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
package *;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import java.time.Duration;
@Configuration
public class RateLimiterConfiguration {
@Autowired
RateLimiterConfigProperties config;
@Bean
public RateLimiterConfig rateLimiterConfig() {
return RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofSeconds(config.getRefreshPeriod())) // Limit refresh period
.limitForPeriod(config.getLimitForPeriod()) // Number of requests allowed in the limit refresh period
.timeoutDuration(Duration.ofMillis(config.getTimeoutDuration())) // Timeout duration for acquiring a permission
.build();
}
@Bean
public RateLimiterRegistry rateLimiterRegistry(){
return RateLimiterRegistry.of(rateLimiterConfig());
}
}
import com.sap.cds.services.request.UserInfo;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.*;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Component
@Order(1)
public class RateLimiterFilter extends OncePerRequestFilter {
private final RateLimiterRegistry rateLimiterRegistry;
private final Set<String> filteredPaths = new HashSet<>();
@Autowired
UserInfo userInfo;
private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterFilter.class);
@Autowired
public RateLimiterFilter(RateLimiterRegistry rateLimiterRegistry) {
this.rateLimiterRegistry = rateLimiterRegistry;
this.filteredPaths.addAll(Arrays.asList("/samplePath1","/samplePath2"));
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
UserInfo userid=userInfo.getId();
//check whitelisted paths here
if(request.getRequestURI()!=null && filteredPaths.contains(request.getRequestURI())){
filterChain.doFilter(request, response);
return;
}
RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter(userid);
//acqurire request permission for the user based on the rateLimiter, return if succeeds else send 429 error
try {
if(rateLimiter.acquirePermission()){
filterChain.doFilter(request, response);
return;
}
response.sendError(429,"Rate limit exceeded");
} catch (RequestNotPermitted ex) {
response.sendError(429,"Rate limit exceeded");
}
}
}
Let's discuss two popular ways to handle caching:
Which approach should you choose?
The best approach for storing rate limiter user info depends on your specific needs. If you need a scalable and reliable solution, then Redis is a good choice. If you need a simpler solution, then caching on the application layer may be sufficient.
Here is a table that summarizes the pros and cons of each approach:
Approach | Pros | Cons |
---|---|---|
Redis | Scalable, reliable | More complex to implement |
Caching on the application layer | Simpler to implement | Not as scalable or reliable as Redis |
Recommendation
If you are expecting a large volume of traffic or need a highly reliable solution, then I recommend using Redis to store rate limiter user info. However, if you are on a tight budget or need a simpler solution, then caching on the application layer may be sufficient.
Ultimately, the best way to decide which approach is right for you is to experiment and see what works best for your application
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
22 | |
11 | |
10 | |
9 | |
8 | |
6 | |
6 | |
6 | |
5 | |
5 |