Skip to content

poshjosh/rate-limiter-annotation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

94 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rate limiter - annotation

Distributed rate limiting simplified using annotations

We believe that rate limiting should be as simple as:

// All methods collectively limited to 10 permits per second
@Rate(10)
class RateLimitedResource {

    // 99 permits per second
    @Rate(99)
    public String smile() {
        return ":)";
    }

    // 2 permits per second only when system available memory is less than 1GB
    @Rate(permits = 2, condition = "jvm.memory.available<1GB") 
    public String greet(String who) {
        return "Hello " + who;
    }
}

Based on rate-limiter.

Please first read the rate-limiter documentation.

For flexibility, this library offers a robust support for annotations.

If the target is web applications, consider using any of:

To add a dependency on rate-limiter-annotation using Maven, use the following:

        <dependency>
            <groupId>io.github.poshjosh</groupId>
            <artifactId>rate-limiter-annotation</artifactId>
            <version>0.7.0</version> 
        </dependency>

Concept

The idea is to be able to rate limit multiple resources fluently and dynamically

class DynamicRateLimiting {

    @Rate(id="resource-a", permits=1)
    static class ResourceA{}
    
    // Will be rate limited when system elapsed time is greater than 59 seconds
    @Rate(id="resource-b", permits=5, condition="sys.time.elapsed>PT59S")
    static class ResourceB{}

    public static void main(String... args) {

        RateLimiterFactory<String> rateLimiterFactory = RateLimiterFactory
                .of(ResourceA.class, ResourceB.class);

        rateLimiterFactory.getLimiter("resource-a").tryAcquire(); // true
        rateLimiterFactory.getLimiter("resource-a").tryAcquire(); // false

        rateLimiterFactory.getLimiter("resource-b").tryAcquire(); // false
    }
}

Sample Usage

import io.github.poshjosh.ratelimiter.RateLimiter;
import io.github.poshjosh.ratelimiter.RateLimiterFactory;
import io.github.poshjosh.ratelimiter.annotations.Rate;

public class SampleUsage {

    static class RateLimitedResource {

        RateLimiter rateLimiter = RateLimiterFactory.getLimiter(RateLimitedResource.class, "smile");

        // Limited to 3 invocations every second
        @Rate(id = "smile", permits = 3) String smile() {
            if (!rateLimiter.tryAcquire()) {
                throw new RuntimeException("Limit exceeded");
            }
            return ":)";
        }
    }

    public static void main(String... args) {

        RateLimitedResource rateLimitedResource = new RateLimitedResource();

        int i = 0;
        for (; i < 3; i++) {

            System.out.println("Invocation " + i + " of 3 should succeed");
            rateLimitedResource.smile();
        }

        System.out.println("Invocation " + i + " of 3 should fail");
        rateLimitedResource.smile();
    }
}

Annotation Specification

Please read the annotation specs. It is concise.

Bandwidth store

You could use a distributed cache to store Bandwidths. First implement BandwidthStore. The example implementation below uses spring-boot-starter-data-redis

import org.springframework.data.redis.core.RedisTemplate;

public class RedisBandwidthStore implements BandwidthsStore<String> {
    private final RedisTemplate<String, Bandwidth> redisTemplate;
    
    public RedisBandwidthStore(RedisTemplate<String, Bandwidth> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    @Override 
    public Bandwidth get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    @Override 
    public void put(String key, Bandwidth bandwidth) {
        redisTemplate.opsForValue().set(key, bandwidth);
    }
}

Then use the BandwidthStore as shown below:

public class WithCustomBandwidthStore {
    
    public RateLimiter getRateLimiter(BandwidthsStore store) {
        RateLimiterContext context = RateLimiterContext.builder()
                .classes(MyRateLimitedClass.class)
                .store(store)
                .build();
        return RateLimiterFactory.of(context).getRateLimiter("ID");
    }
}

Dependents

The following depend on this library: