Waarom Rate Limiting? Omdat Delen Zorgzaam is (Maar Niet Te Veel)

Voordat we aan de slag gaan met de code, laten we eens praten over waarom rate limiting de superheld is die jouw API nodig heeft:

  • Voorkomt dat overijverige clients te veel middelen gebruiken
  • Biedt bescherming tegen onbedoelde DoS-aanvallen
  • Zorgt voor eerlijk gebruik voor alle gebruikers
  • Helpt bij het beheren van kosten en schaalbaarheid

Denk eraan als een uitsmijter voor je API-club. Het houdt het feest gaande zonder dat één gast de dansvloer monopoliseert.

Het Dynamische Duo: Spring Boot en Redis

We combineren Spring Boot met Redis voor dit avontuur. Waarom? Omdat ze net als pindakaas en jam zijn—ze werken gewoon goed samen. Spring Boot biedt ons de flexibiliteit van het framework, terwijl Redis de snelheid en gedistribueerde aard brengt die we nodig hebben voor effectieve rate limiting.

De Voorbereiding

Laten we eerst onze afhankelijkheden op orde brengen. Voeg deze toe aan je pom.xml:


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.16.0</version>
    </dependency>
</dependencies>

Nu gaan we Redis configureren in onze application.properties:


spring.redis.host=localhost
spring.redis.port=6379

De Rate Limiter: De Nieuwe Beste Vriend van je API

Tijd om onze rate limiter te maken. We gebruiken een eenvoudig sliding window-algoritme geïmplementeerd met Redis.


import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

@Component
public class RedisRateLimiter {

    private final RedissonClient redissonClient;

    public RedisRateLimiter(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public boolean tryAcquire(String key) {
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
        rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.MINUTES);
        return rateLimiter.tryAcquire();
    }
}

Deze setup staat 10 verzoeken per minuut toe. Pas aan naar behoefte voor jouw gebruikssituatie.

De Poortwachter: Een Aangepaste Annotatie

Laten we een aangepaste annotatie maken om eenvoudig rate limiting toe te passen op onze endpoints:


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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    String key() default "";
}

De Interceptor: Waar de Magie Gebeurt

Nu voor het hoogtepunt—onze interceptor:


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class RateLimitInterceptor {

    private final RedisRateLimiter rateLimiter;

    public RateLimitInterceptor(RedisRateLimiter rateLimiter) {
        this.rateLimiter = rateLimiter;
    }

    @Around("@annotation(rateLimit)")
    public Object interceptRateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String key = rateLimit.key() + ":" + request.getRemoteAddr();

        if (!rateLimiter.tryAcquire(key)) {
            throw new RateLimitExceededException("Rate limit overschreden. Probeer het later opnieuw.");
        }

        return joinPoint.proceed();
    }
}

Alles Samenbrengen

Nu, laten we onze rate limiter in actie zien:


@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/greet")
    @RateLimit(key = "greeting")
    public String greet() {
        return "Hallo, Wereld!";
    }
}

De Vangst: Foutafhandeling

Vergeet niet om die vervelende rate limit-excepties af te handelen:


@ControllerAdvice
public class RateLimitExceptionHandler {

    @ExceptionHandler(RateLimitExceededException.class)
    public ResponseEntity<String> handleRateLimitExceededException(RateLimitExceededException ex) {
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(ex.getMessage());
    }
}

De Nasleep: Monitoren en Aanpassen

Gefeliciteerd! Je hebt zojuist een robuust rate limiting-systeem geïmplementeerd. Maar de reis eindigt hier niet. Houd je logs en statistieken in de gaten. Mogelijk moet je je rate limits aanpassen op basis van gebruikspatronen in de echte wereld.

Protip: Redis Insights

Gebruik Redis Insights om je rate limiting in actie te visualiseren. Het is alsof je röntgenzicht hebt voor je Redis-gegevens!

Afronden: De Kracht van Beheersing

Daar heb je het—een gestroomlijnde, door Redis aangedreven rate limiting-oplossing voor je Spring Boot API. Je hebt je API de kracht gegeven om "Ho, daar, vriend!" te zeggen wanneer het te druk wordt.

Vergeet niet, het doel is niet om je gebruikers te frustreren, maar om ervoor te zorgen dat iedereen een eerlijk deel van de API-taart krijgt. Pas je limieten verstandig aan, en moge je servers altijd koel blijven onder druk!

"Met grote kracht komt grote verantwoordelijkheid" - Oom Ben (en elke API-ontwikkelaar ooit)

Stof tot Nadenken

Als je rate limiting implementeert, overweeg dan deze vragen:

  • Hoe communiceer je rate limits naar je API-gebruikers?
  • Wat is je strategie voor het omgaan met piekverkeer?
  • Hoe zal rate limiting je API's SLA's beïnvloeden?

Veel programmeerplezier, en moge je API's altijd responsief en misbruikvrij zijn!