Java rate limiting library based on token/leaky-bucket algorithm.

Overview

Java rate-limiting library based on token-bucket algorithm.

Join the chat at https://gitter.im/vladimir-bukhtoyarov/bucket4j Licence Donate

Advantages of Bucket4j

  • Implemented on top of ideas of well known algorithm, which are by de-facto standard for rate limiting in the IT industry.
  • Effective lock-free implementation, Bucket4j is good scalable for multi-threading case.
  • Absolutely non-compromise precision, Bucket4j does not operate with floats or doubles, all calculation are performed in the integer arithmetic, this feature protects end users from calculation errors involved by rounding.
  • Ability to switch from one JVM to cluster in two lines of code. Using Bucket4j you are able to limiting something in the cluster of JVMs. Since release 1.2 the Bucket4j supports any GRID solution which compatible with JCache API (JSR 107) specification. Just use your favorite grid including Hazelcast, Ignite, Coherence, Infinispan or any other.
  • Ability to specify multiple bandwidths per bucket. For example you can limit 1000 events per hours but not often then 100 events per minute.
  • Both synchronous and asynchronous API.
  • Pluggable listener API that allows to implement monitoring and logging.
  • Ability to use bucket as a scheduler, see examples.

Supported back-ends

As mentioned above in addition to local in-memory buckets, the Bucket4j supports clustered usage scenario on top of following back-ends:

Back-end Documentation page Async supported Optimized serialization
JCache API (JSR 107) bucket4j-jcache No No
Hazelcast bucket4j-hazelcast Yes Yes
Apache Ignite bucket4j-ignite Yes n/a
Inifinispan bucket4j-infinspan Yes Yes
Oracle Coherence bucket4j-coherence Yes Yes

General documentation

Basics:

Examples:

Production checklist

  • Common production checklist - Mandatory points that need to be understood before using the Bucket4j in production, independently of local or clustered usage scenarios.
  • JCache production checklist - Mandatory points that need to be understood before using the Bucket4j over JCache cluster.

Archive:

Third-party integrations:

Third-party demos and articles:

Get Bucket4j library

You can add Bucket4j to your project as maven dependency

The Bucket4j is distributed through Maven Central:

<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>6.0.2</version>
</dependency>

You can build Bucket4j from sources

git clone https://github.com/vladimir-bukhtoyarov/bucket4j.git
cd bucket4j
mvn clean install

Have a question?

Feel free to ask via:

License

Copyright 2015-2020 Vladimir Bukhtoyarov Licensed under the Apache Software License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0.

Comments
  • enhancement request

    enhancement request

    would it be possible to enhance Bucket interface (or at least GridBucket impl) to add a method that tries (no waiting) to consume up to N tokens at once and returns:

    1. number of tokens actually consumed AND
    2. the expiration date associated with these tokens. Thanks I can explain my use case if needed. Greg
    question 
    opened by bayareagreg 27
  • Add support for fixed interval refill

    Add support for fixed interval refill

    In opposite to smooth refill which adds token as soon as possible, interval refill should regenerate tokens periodically.

    Proposed API:

    public static Refill fixedInterval(long tokens, Duration period) {
        // ...
    }
    
    public static Refill fixedInterval(Instant timeOfFirstRefill, long tokens, Duration period) {
         // ...
    }
    
    feature 
    opened by vladimir-bukhtoyarov 16
  • Blocking bucket creation

    Blocking bucket creation

    Hi,

    I am creating Bucket in Single Hazelcast instance serving two Application data centers (DC) and having custom waiting code to consumer the token as follows.

    Creation:

    Bandwidth limit = Bandwidth.classic(bucketSize, Refill.intervally(bucketSize, period));
    Bucket bucket = Bucket4j.extension(Hazelcast.class).builder().addLimit(limit)
    					.build(hazelCastService.getBucketMap(), bucketId, RecoveryStrategy.RECONSTRUCT);
    

    Consumption:

    while (true) {
    	probe = getBucket().tryConsumeAndReturnRemaining(bucketBean.getTokenSize());
    	if (probe.isConsumed()) {
    		break;
    	} else {
    		Thread.sleep((long) (probe.getNanosToWaitForRefill() / 1000000.0));
    	}
    }
    

    But the issue is first data centers consumes token faster like three times higher than other one. So there is no equal distribution of tokens.

    When application threads from both DCs sleeps till the refill and wakes-up, threads from first DC consumes faster than other one.

    I am thinking it might be because custom handling of sleep and consume. Is this can be solved using "bucket.tryConsume(1, MAX_WAIT_NANOS, BlockingStrategy.PARKING)", so that I dont need to handle sleep. And token might being served in the order when thread arrives.

    Please suggest if this works..

    When it is, then I am not finding method from Bucket object, its part of BlockingBucket object, but I cannot able to instantiate that object. please help.

    question 
    opened by sarathbabur 14
  • HazelcastSerializationException with Hazelcast 4.2 and bucket4j 6.2.0

    HazelcastSerializationException with Hazelcast 4.2 and bucket4j 6.2.0

    I have upgraded my Hazelcast and bucket 4j. And now I have error com.hazelcast.nio.serialization.HazelcastSerializationException: java.lang.ClassNotFoundException: io.github.bucket4j.grid.CommandResult

    What is strange - this error happens only when I run my application on Kubernetes. When I run it on virtual machine or on my latop everything works OK

    My application is using Spring Boot 2.2.1 and Spring Cloud Gateway and react

    What I am doing wrong ?

    My gradle dependencies

      implementation "com.github.vladimir-bukhtoyarov:bucket4j-core:6.2.0"
       implementation "com.github.vladimir-bukhtoyarov:bucket4j-jcache:6.2.0"
       implementation "com.github.vladimir-bukhtoyarov:bucket4j-hazelcast:6.2.0"
       implementation "javax.cache:cache-api:1.0.0"
       implementation "com.hazelcast:hazelcast:4.2"
       implementation "com.hazelcast:hazelcast-eureka-one:2.0.1"
    

    My Hazelcast configuration code

     ```
        Config config = new Config();
       config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true);
            config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
            config.getNetworkConfig().getJoin().getAwsConfig().setEnabled(false);
            config.getNetworkConfig().getJoin().getEurekaConfig().setEnabled(false);
            config.getNetworkConfig().getJoin().getKubernetesConfig().setEnabled(false);
    
            if (eurekaClientOption.isPresent()) {
                EurekaClient eurekaClient = eurekaClientOption.get();
                String appName = eurekaClient.getApplicationInfoManager().getEurekaInstanceConfig().getAppname();
                logger.info("hazelcast appName :" + appName);
                Application application = eurekaClient.getApplication(appName);
                if (application != null) {
                    application.getInstancesAsIsFromEureka().forEach(
                            instanceInfo -> {
                                config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(instanceInfo.getIPAddr() + ":" + port);
                                logger.info("hazelcast TcpIpConfig : {}",config.getNetworkConfig().getJoin().getTcpIpConfig());
                            }
                    );
                } else {
                    logger.error("hazelcast empty eureka application [{}]",appName);
                }
            } else {
                logger.error("hazelcast empty eurekaClient");
            }
            config.getNetworkConfig().setPort(port).setPortAutoIncrement(false);
            config.setLiteMember(false);
            logger.info("hazelcast config {}",config);
        config.setProperty("hazelcast.phone.home.enabled", "false");
        config.setProperty("hazelcast.initial.min.cluster.size","1");
        config.setProperty("hazelcast.socket.server.bind.any", "false");
       HazelcastInstance hazelcastInstance = new HazelcastInstanceFactory(config).getHazelcastInstance();
    
    
    My bucket4j code
    
        ```
        if (hazelcastInstance != null) {
                IMap map = hazelcastInstance.getMap("bucket");
                int initialCapacity = (throttlingRule.getBandwith() + throttlingRule.getOverdraftBandwith());
                this.bucket = Bucket4j.extension(Hazelcast.class)
                        .builder()
                        .addLimit(Bandwidth.classic(initialCapacity, Refill.smooth(throttlingRule.getBandwith(), Duration.of(throttlingRule.getDuration(), throttlingRule.getDurationUnit()))))
                        .build(map, key, RecoveryStrategy.RECONSTRUCT);
    
                VerboseResult<EstimationProbe> verboseResult = bucket.asVerbose().estimateAbilityToConsume(1);
                BucketConfiguration bucketConfiguration = verboseResult.getConfiguration();
                logger.info("bucket for rule [{}] and key [{}] configuration [{}] availableTokens [{}]",throttlingRule,key,bucketConfiguration,bucket.getAvailableTokens());
            } else  {
                logger.warn("hazelcastInstance == null");
            }
    
      public ConsumptionProbe throttleProbe() {
           assert bucket != null;
           CompletableFuture<ConsumptionProbe> probe = bucket.asAsync().tryConsumeAndReturnRemaining(1);
           return probe.join();
       }
    

    Exception

    j.l.ClassNotFoundException: io.github.bucket4j.grid.CommandResult
    	at j.i.l.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
    	at j.i.l.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    	at j.lang.ClassLoader.loadClass(ClassLoader.java:522)
    	at c.h.i.n.ClassLoaderUtil.tryLoadClass(ClassLoaderUtil.java:289)
    	at c.h.i.n.ClassLoaderUtil.loadClass(ClassLoaderUtil.java:249)
    	at c.h.i.n.IOUtil$ClassLoaderAwareObjectInputStream.resolveClass(IOUtil.java:910)
    	at j.i.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1995)
    	at j.i.ObjectInputStream.readClassDesc(ObjectInputStream.java:1862)
    	at j.i.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2169)
    	at j.i.ObjectInputStream.readObject0(ObjectInputStream.java:1679)
    	at j.i.ObjectInputStream.readObject(ObjectInputStream.java:493)
    	at j.i.ObjectInputStream.readObject(ObjectInputStream.java:451)
    	at c.h.i.s.i.d.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:86)
    	... 11 common frames omitted
    Wrapped by: c.h.n.s.HazelcastSerializationException: java.lang.ClassNotFoundException: io.github.bucket4j.grid.CommandResult
    	at c.h.i.s.i.d.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:90)
    	at c.h.i.s.i.d.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:79)
    	at c.h.i.s.i.StreamSerializerAdapter.read(StreamSerializerAdapter.java:44)
    	at c.h.i.s.i.AbstractSerializationService.toObject(AbstractSerializationService.java:208)
    	at c.h.s.i.DelegatingCompletableFuture$DeserializingFunction.apply(DelegatingCompletableFuture.java:518)
    	at c.h.s.i.AbstractInvocationFuture.lambda$unblockCompose$5(AbstractInvocationFuture.java:953)
    	at j.u.c.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1426)
    	at j.u.c.ForkJoinTask.doExec(ForkJoinTask.java:290)
    	at j.u.c.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    	at j.u.c.ForkJoinPool.scan(ForkJoinPool.java:1656)
    	at j.u.c.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    	at j.u.c.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
    Wrapped by: j.u.c.CompletionException: com.hazelcast.nio.serialization.HazelcastSerializationException: java.lang.ClassNotFoundException: io.github.bucket4j.grid.CommandResult
    	at j.u.c.CompletableFuture.encodeThrowable(CompletableFuture.java:331)
    	at j.u.c.CompletableFuture.completeThrowable(CompletableFuture.java:346)
    	at j.u.c.CompletableFuture$UniApply.tryFire(CompletableFuture.java:632)
    	at j.u.c.CompletableFuture.unipush(CompletableFuture.java:589)
    	at j.u.c.CompletableFuture.uniApplyStage(CompletableFuture.java:660)
    	at j.u.c.CompletableFuture.thenApplyAsync(CompletableFuture.java:2104)
    	at c.h.s.i.InternalCompletableFuture.thenApply(InternalCompletableFuture.java:78)
    	at i.g.b.AbstractBucket$1.tryConsumeAndReturnRemaining(AbstractBucket.java:147)
    	at p.b.g.t.ThrottlingRuleBucket.throttleProbe(ThrottlingRuleBucket.java:64)
    
    opened by Jacuo 13
  • I can request over maximum rate limit

    I can request over maximum rate limit

    I set rate limit 5 request / 1 min

    but sometime I can sent request over maximum rate.

    I log available tokens of bucket and I look something wrong. Sometime (I wait 5 sec before 1 request) when pass 30sec avaliable token reset to 5 request but I fix

    Refill.intervally(5, Duration.ofSeconds(60)) (wait for timeout reset avaliable token)
    Bandwidth.classic(5,Refill.intervally(5, Duration.ofSeconds(60)))
    

    Issue is how can I fix this issue because avaliable token of bucket should be reset when pass 1 min.

    I use bucket-core and bucket4j-jcache version 4.0.1

    This is some part of my log. I start first request is 15:11:39 avaliable is 5 and the last request is 15.12.08 (pass after first request 29 sec) but avaliable token is 5.

    RateLimitingFilter  : available: 5 time: 15:11:39.563
    RateLimitingFilter  : ########################################
    RateLimitingFilter  : available: 4 time: 15:11:46.840
    RateLimitingFilter  : ########################################
    RateLimitingFilter  : available: 3 time: 15:11:53.820
    RateLimitingFilter  : ########################################
    RateLimitingFilter  : available: 2 time: 15:12:01.482
    RateLimitingFilter  : ########################################
    RateLimitingFilter  : available: 5 time: 15:12:08.235
    RateLimitingFilter  : ########################################
    
    question 
    opened by maetinee 13
  • Add monitoring feature

    Add monitoring feature

    Following points should be exposed:

    • Token consumption.
    • Token rejection.
    • Amount of time spent in waiting to bucket refill.
    • Count of InterruptedExceptions happens during waiting.

    Design considerations:

    • Monitoring must be pluggable, most likely statistic should be implemented as listener with methods onConsumed, onRejected, onWait, onInterrupt, it should be responsibility of client to decide how to this data should be accumulated, aggregated and exposed.
    • Reference implementation on top of Dropwizard-Metrics should be provided.

    Also it is need to support requqirements from #7

    feature 
    opened by vladimir-bukhtoyarov 13
  • Concurrent processing issue with Redisson/Redis and Bucket4j

    Concurrent processing issue with Redisson/Redis and Bucket4j

    Hi Vladimir,

    We are using bucket4j component for one of our API to throttle the request. The API that we wanted to throttle is running on 1 unix servers with 2 JVMs When we have done a performance testing after enabling the throttling with bucket4j, we observed that the tokens are not being managed properly.

    Ex: We have created a bucket with 50 tokens, and ran a performance test with Jmeter with 100 concurrent users and we see that more than 50 requests were served. Here the expected behavior it should serve only 50 requests as we have only 50 tokens on the bucket.

    Also, We want to achieve the below scenario, is it possible through Bucket4j?

    Assume, our Server is able to handle 1000 requests per minute. We have 2 partners to consume our API, so we have created 2 buckets with each 500 tokens. But, we would like to handle only 50 active concurrent threads/requests at a time even though we have enough tokens in that bucket.

    We are using Redis as a db for managing the tokens and below maven dependencies

    org.redisson redisson 3.3.0 com.github.vladimir-bukhtoyarov bucket4j-core 4.4.1 com.github.vladimir-bukhtoyarov bucket4j-jcache 4.4.1 javax.cache cache-api

    Thank you in advance for your help. Could you please respond to this ASAP.

    Regards, Sudhakar

    opened by saindavs 12
  • API usage with JCache without caching the proxies on the client side

    API usage with JCache without caching the proxies on the client side

    I'm currently working on https://github.com/jhipster/generator-jhipster/issues/5388

    So basically I'm doing a request filter like here in the documentation, but using JCache with Hazelcast like here in the documentation.

    Mixing those 2 examples, my current implementation is the following (please note this is a Zuul filter, not a Servlet filter, but this doesn't make any difference):

    String bucketId = getId(RequestContext.getCurrentContext().getRequest());
            
    Bucket bucket = Bucket4j.jCacheBuilder(RecoveryStrategy.RECONSTRUCT)
        .addLimit(Bandwidth.simple(jHipsterProperties.getGateway().getRateLimiting().getLimit(),
            Duration.ofSeconds(3_600))
        .build(cache, bucketId);
    
    if (bucket.tryConsumeSingleToken()) {
        // the limit is not exceeded
        log.debug("API rate limit OK for {}", bucketId);
    } else {
        // limit is exceeded
        log.info("API rate limit exceeded for {}", bucketId);
        apiLimitExceeded();
    }
    return null;
    

    This code works, and follows the documentation, but it worries me that we "create" a new Bucket for each HTTP request. This looks rather heavy, and I'd better re-use the Bucket that was created.

    However, we just store a BucketState in the cache, and I can't find a way to get a Bucket out of the cache.

    Am I doing something wrong? I currently think the API should be modified, or maybe better documented, because I can't find a good solution here.

    feature 
    opened by jdubois 12
  • Add option to allow interval refill to happen on interval boundary

    Add option to allow interval refill to happen on interval boundary

    We're keen to use the interval buckets (instead of greedy buckets), but would like the refill to happen on an interval boundary (i.e. start of second, minute, hour, day) rather than when the bucket is created.

    The main reason for this, is to provide transparency to the end customer on when buckets are refilled.

    For example:

    • If we setup a bucket with 100 tokens that has a interval refill of 100 tokens every hour.
    • Currently, if bucket is created 20 minutes past the hour, then it will get refilled 20 minutes past the next hour.
    • If we were to create another bucket with same values but X minutes past the hour, then this would be refilled X minutes past the hour.

    This is very confusing to customers, especially when they are testing the bucket4j behaviour.

    A useful feature would be that when a bucket is created, there is an option that specifies that it should be refilled on the next interval boundary.

    The intervals could be applied to second(?), minute, hour or day, and would need to accurate to at least 10ms.

    This feature could also take into account the initial tokens for the bucket (though it won't be too difficult to do this ourselves).

    For example:

    • If we setup a bucket with 100 tokens that has a interval refill of 100 tokens every hour, specifying that the refill happens at start of an hour.
    • bucket is created 20 minutes past the hour
    • bucket gets initially created with 66 tokens (pro-rate of number of tokens for bucket with the time left to interval boundary) and refilled in 40 minutes (to align it to the hour boundary)
    • after this first refill, it should there after be refilled with 100 tokens every 60 minutes.

    I did notice that in original discussion of fixed interval buckets (https://github.com/vladimir-bukhtoyarov/bucket4j/issues/39), there was mention of "Instant timeOfFirstRefill" parameter that could have met this feature, but this wasn't implemented.

    opened by siwhiting 10
  • Dynamic configuration of capacity

    Dynamic configuration of capacity

    Hi,

    This is in reference to old issue https://github.com/vladimir-bukhtoyarov/bucket4j/issues/45 where i would like to create dynamic capacity per hour.

    Earlier version has concept of BandwidthAdjuster which kind of I am looking for. Not sure if it actually adjust bucket during run time when hour changes as feature has been decommissioned so can't test.

    Can you suggest any alternative way to achieve below using new 4.* version of Bucket4J?

    BandwidthAdjuster adjuster = new BandwidthAdjuster() { @Override public long getCapacity(long currentTime) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(currentTime); int hour = calendar.get(Calendar.HOUR_OF_DAY); if (hour >= 7 && hour <= 23) { return 10;
    } else { return 2; } } }; Buckets.withMillisTimePrecision() .withLimitedBandwidth(adjuster, TimeUnit.MINUTES, 1, 10);

    question 
    opened by nitenA 10
  • I want to set Refill, Bandwidth capacity to 0.

    I want to set Refill, Bandwidth capacity to 0.

    I took below error stack.

    java.lang.IllegalArgumentException: 0 is wrong value for period tokens, because tokens should be positive
    
    at io.github.bucket4j.BucketExceptions.nonPositivePeriodTokens(BucketExceptions.java:106)
    at io.github.bucket4j.Refill.<init>(Refill.java:46)
    at io.github.bucket4j.Refill.greedy(Refill.java:95)
    

    I read code related this issue. And find when capacity is set to 0, this library make exception.

    public static Bandwidth classic(long capacity, Refill refill) {
            if (capacity <= 0) {
                throw BucketExceptions.nonPositiveCapacity(capacity);
            }
            if (refill == null) {
                throw BucketExceptions.nullBandwidthRefill();
            }
            return new Bandwidth(capacity, refill.periodNanos, refill.tokens, capacity, refill.refillIntervally, refill.timeOfFirstRefillMillis, refill.useAdaptiveInitialTokens);
        }
    
    private Refill(long tokens, Duration period, boolean refillIntervally, long timeOfFirstRefillMillis, boolean useAdaptiveInitialTokens) {
            if (period == null) {
                throw BucketExceptions.nullRefillPeriod();
            }
            if (tokens <= 0) {
                throw BucketExceptions.nonPositivePeriodTokens(tokens);
            }
            this.periodNanos = period.toNanos();
            if (periodNanos <= 0) {
                throw BucketExceptions.nonPositivePeriod(periodNanos);
            }
    ...
    }
    

    Now I can set dynamically bucket size set. I sometimes want to stop to generate token.

    How can I do this situation? Do you have any ideas?

    bug 
    opened by BaeJi77 9
  • Why the number of token in bucket can be -1?

    Why the number of token in bucket can be -1?

    I use spring boot, bucket4j+redis to build my app. and i deploy it in k8s cluster with 2 replicas. Here is maven

            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson-spring-boot-starter</artifactId>
                <version>3.17.0</version>
            </dependency>
    
            <!-- Bucket4J starter = Bucket4J + JCache -->
            <dependency>
                <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
                <artifactId>bucket4j-spring-boot-starter</artifactId>
                <version>0.5.2</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.ehcache</groupId>
                        <artifactId>ehcache</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    

    Here is config for redis and proxyManager

    @Configuration
    public class RedisConfig {
    
        @Value("${spring.redis.host}")
        private String host;
    
        @Value("${spring.redis.port}")
        private String port;
    
        @Value("${spring.redis.password}")
        private String password;
    
    
        @Bean
        public Config config() {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://" + host + ":" + port);
            config.useSingleServer().setPassword(password);
            config.useSingleServer().setConnectionPoolSize(2);
            config.useSingleServer().setConnectionMinimumIdleSize(2);
            return config;
        }
    
        @Bean("tokenCache")
        public CacheManager cacheManager(Config config) {
            CacheManager manager = Caching.getCachingProvider().getCacheManager();
            manager.createCache(RedisKeyConstant.TOKEN_CONFIG_CACHE, RedissonConfiguration.fromConfig(config));
            return manager;
        }
    
        @Bean
        ProxyManager<String> proxyManager(CacheManager cacheManager) {
            return new JCacheProxyManager<>(cacheManager.getCache(RedisKeyConstant.TOKEN_CONFIG_CACHE));
        }
    }
    

    Here is my rate limiter

    @Service
    public class RateLimiter implements RateLimiter {
        @Autowired
        private ProxyManager<String> proxyManager;
    
        private Bucket bucket = null;
    
        public Bucket getBucket() {
            Supplier<BucketConfiguration> configSupplier = this.getConfigSupplierForUser();
            this.bucket = proxyManager.builder().build(ApiConstant.BUCKET, configSupplier);
            return this.bucket;
        }
    
        private Supplier<BucketConfiguration> getConfigSupplierForUser() {
            Refill refill = Refill.greedy(1, Duration.ofSeconds(1));
            Bandwidth limit = Bandwidth.classic(4, refill).withInitialTokens(4);
            return () -> (BucketConfiguration.builder()
                    .addLimit(limit).build());
        }
    }
    

    Here is where i use rate limiter, I use it to limit the consumption speed for rabbitMq to limit Limit the rate of calling third-party APIs.

    @Component
    public class ConsumerMethod {
        private static final Logger logger = LoggerFactory.getLogger(ConsumerMethod.class);
    
        @Autowired
        private RateLimiter rateLimiter;
    
        @Bean
        Consumer<Message<String>> Consumer() {
            logger.info("init Consumer");
            return message -> onMessage(message);
        }
    
        public void onMessage(Message<String> message) {
            Channel channel = message.getHeaders().get(AmqpHeaders.CHANNEL, Channel.class);
            Long l = message.getHeaders().get(AmqpHeaders.DELIVERY_TAG, Long.class);
            DTO dto = JSON.parseObject(message.getPayload(), DTO.class);
    
            Bucket bucket = rateLimiter.getBucket();
            long availableTokens = bucket.getAvailableTokens();
            logger.info("-----------Before Available Tokens are " + availableTokens);
            try {
                if (bucket.asBlocking().tryConsume(1, Duration.ofSeconds(1))) {
                    long availableTokens1 = bucket.getAvailableTokens();
                    logger.info("-----------Token consumed successfully, after Available Tokens are " + availableTokens1);
                    RabbitMqUtils.manualAck(channel, l);
                    // Call third-party api
                } else {
                    logger.error("-----------Token consumed failed.");
                    RabbitMqUtils.manualAck(channel, l);
                    // Do some failure methods.
                }
            } catch (InterruptedException e) {
                RabbitMqUtils.manualAck(channel, l);
                e.printStackTrace();
            }
        }
    

    where is the log after processing the msg from rabbitMQ. And the starting number of each line is indicates which replica it belongs to.

    1 2023-01-05 11:12:09.391 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are 4
    2 2023-01-05 11:12:09.472 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are 4
    1 2023-01-05 11:12:09.499 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Token consumed successfully, after Available Tokens are 3
    1 2023-01-05 11:12:09.579 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are 2
    2 2023-01-05 11:12:09.608 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Token consumed successfully, after Available Tokens are 2
    2 2023-01-05 11:12:09.695 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are 1
    1 2023-01-05 11:12:09.754 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Token consumed successfully, after Available Tokens are 1
    1 2023-01-05 11:12:09.811 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are 0
    2 2023-01-05 11:12:09.831 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Token consumed successfully, after Available Tokens are 0
    2 2023-01-05 11:12:09.896 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are -1
    2 2023-01-05 11:12:09.928 [n_group-1:28480] ERROR c.a.c.s.stream.ConsumerMethod       - -----------Token consumed failed.
    2 2023-01-05 11:12:10.054 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are -1
    2 2023-01-05 11:12:10.092 [n_group-1:28480] ERROR c.a.c.s.stream.ConsumerMethod       - -----------Token consumed failed.
    2 2023-01-05 11:12:10.155 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are -1
    2 2023-01-05 11:12:10.194 [n_group-1:28480] ERROR c.a.c.s.stream.ConsumerMethod       - -----------Token consumed failed.
    2 2023-01-05 11:12:10.254 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are -1
    2 2023-01-05 11:12:10.290 [n_group-1:28480] ERROR c.a.c.s.stream.ConsumerMethod       - -----------Token consumed failed.
    2 2023-01-05 11:12:10.351 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are -1
    2 2023-01-05 11:12:10.379 [n_group-1:28480] ERROR c.a.c.s.stream.ConsumerMethod       - -----------Token consumed failed.
    2 2023-01-05 11:12:10.442 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are 0
    1 2023-01-05 11:12:10.493 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Token consumed successfully, after Available Tokens are 0
    1 2023-01-05 11:12:10.552 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are -1
    1 2023-01-05 11:12:10.618 [n_group-1:28433] ERROR c.a.c.s.stream.ConsumerMethod       - -----------Token consumed failed.
    1 2023-01-05 11:12:10.708 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are -1
    1 2023-01-05 11:12:10.740 [n_group-1:28433] ERROR c.a.c.s.stream.ConsumerMethod       - -----------Token consumed failed.
    1 2023-01-05 11:12:10.824 [n_group-1:28433]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Before Available Tokens are -1
    1 2023-01-05 11:12:10.851 [n_group-1:28433] ERROR c.a.c.s.stream.ConsumerMethod       - -----------Token consumed failed.
    2 2023-01-05 11:12:11.497 [n_group-1:28480]  INFO c.a.c.s.stream.ConsumerMethod       - -----------Token consumed successfully, after Available Tokens are 0
    

    I wonder why the number of token in bucket can be -1, and is it the correct way to use bucket4j in multiple replicas case? How can i to optimize my app to limit the calling third-party api rate from multiple replicas?

    opened by xuecangqiuye 0
  • Bucket4j expecting redis to be up and running, if redis goes down my application going down

    Bucket4j expecting redis to be up and running, if redis goes down my application going down

    Hi Team,

    Iam trying bucket4j with redis in distributed environment. Iam using LettuceBasedProxyManager to connect to redis to get/store buckets in redis. My application is expecting redis to be up and running while starting application, also when redis goes down when application is running, my application going down. In my case i want to use redis as optional. If redis is down, my application should work fine with out rate limit. Can you please confirm is there any way to do this ?

    opened by surendrab6 2
  • Support for Elasticache Redis Cluster enabled

    Support for Elasticache Redis Cluster enabled

    opened by lindahm 2
  • Kotlin-based implementation for multiplatform scanarios

    Kotlin-based implementation for multiplatform scanarios

    Hi, bucket4j looks great, however, it can't be used in Kotlin JS and Kotlin Native.

    Have you considered implementing the core in Kotlin, so the library could be used with multiplatform projects as well?

    See https://github.com/Kotlin/kotlinx.coroutines/issues/460

    opened by vlsi 1
  • TTL support for DynamoDB backend

    TTL support for DynamoDB backend

    I noticed that the redis implementation has an option to add TTL and dynamo one does not. Was this ever considered? Could try raising a PR if there's nothing blocking the implementation.

    opened by Avinm 7
  • Close the gaps in documentation

    Close the gaps in documentation

    Following functionality that were implemented in the scope of 7.0 still not documented yet:

    • Redis support including previously implemented Redisson backend as well as new backends implemented in the scope of #273 #274 #275
    • DynamoDB support.
    • Caffeine support.
    • Optimizations like batching, delaying, predicting.
    enhancement 
    opened by vladimir-bukhtoyarov 2
Releases(8.1.0)
  • 8.1.0(Aug 30, 2022)

    What's Changed

    • Upgrade & refactor asciidoctor-maven-plugin config by @abelsromero in https://github.com/bucket4j/bucket4j/pull/284
    • Build html docs in single plugin by @abelsromero in https://github.com/bucket4j/bucket4j/pull/286
    • #287 Fix asciidoctor build messages by @abelsromero in https://github.com/bucket4j/bucket4j/pull/288
    • #285 Update Hazelcast Bucket instantiation by @abelsromero in https://github.com/bucket4j/bucket4j/pull/289
    • Readme minnor fixes by @abelsromero in https://github.com/bucket4j/bucket4j/pull/292
    • Bump postgresql from 42.3.3 to 42.4.1 in /bucket4j-postgresql by @dependabot in https://github.com/bucket4j/bucket4j/pull/290
    • Bump postgresql from 42.3.3 to 42.4.1 in /bucket4j-examples by @dependabot in https://github.com/bucket4j/bucket4j/pull/291

    New Contributors

    • @abelsromero made their first contribution in https://github.com/bucket4j/bucket4j/pull/284

    Full Changelog: https://github.com/bucket4j/bucket4j/compare/8.0.1...8.1.0

    Source code(tar.gz)
    Source code(zip)
  • 8.0.1(Jul 31, 2022)

  • 7.6.0(Jul 23, 2022)

    https://bucket4j.com/7.6.0/release-notes.html

    What's Changed

    • testcontainers 1.17.1 by @sullis in https://github.com/bucket4j/bucket4j/pull/258
    • setup-java v3 by @sullis in https://github.com/bucket4j/bucket4j/pull/259
    • use official Maven wrapper by @sullis in https://github.com/bucket4j/bucket4j/pull/260
    • fix typos by @r331 in https://github.com/bucket4j/bucket4j/pull/266
    • Add support for Lettuce by @ttulka in https://github.com/bucket4j/bucket4j/pull/273
    • Add Jedis support by @ttulka in https://github.com/bucket4j/bucket4j/pull/275
    • Add spring-data-redis support by @ttulka in https://github.com/bucket4j/bucket4j/pull/274

    New Contributors

    • @r331 made their first contribution in https://github.com/bucket4j/bucket4j/pull/266
    • @ttulka made their first contribution in https://github.com/bucket4j/bucket4j/pull/273

    Full Changelog: https://github.com/bucket4j/bucket4j/compare/7.5.0...7.6.0

    Source code(tar.gz)
    Source code(zip)
  • 7.5.0(May 2, 2022)

    https://bucket4j.com/7.5.0/release-notes.html

    What's Changed

    • Bump postgresql from 42.2.25 to 42.3.3 in /bucket4j-postgresql by @dependabot in https://github.com/vladimir-bukhtoyarov/bucket4j/pull/235
    • Bump postgresql from 42.2.25 to 42.3.3 in /bucket4j-examples by @dependabot in https://github.com/vladimir-bukhtoyarov/bucket4j/pull/234
    • Bump jackson-databind from 2.12.4 to 2.12.6.1 in /bucket4j-dynamodb-sdk-v1 by @dependabot in https://github.com/vladimir-bukhtoyarov/bucket4j/pull/248
    • Fix the refill interval example by @ebourg in https://github.com/vladimir-bukhtoyarov/bucket4j/pull/257

    New Contributors

    • @ebourg made their first contribution in https://github.com/vladimir-bukhtoyarov/bucket4j/pull/257

    Full Changelog: https://github.com/vladimir-bukhtoyarov/bucket4j/compare/7.4.0...7.5.0

    Source code(tar.gz)
    Source code(zip)
  • 7.4.0(Apr 9, 2022)

  • 6.4.0(Nov 14, 2021)

  • 6.3.0(Oct 2, 2021)

    • Additive strategy for configuration replacement https://github.com/vladimir-bukhtoyarov/bucket4j/discussions/177
    • Depend on OSS Coherence CE instead of the commercially licensed https://github.com/vladimir-bukhtoyarov/bucket4j/pull/169
    • Pull request builder has been moved to Github Actions from Travis CI #179 because of security reasons.
    Source code(tar.gz)
    Source code(zip)
  • 6.2.0(Mar 21, 2021)

  • 6.1.0(Mar 19, 2021)

  • 6.0.0(Dec 21, 2020)

    This release was inspired by user complaints:

    • #133 Dynamically changing bucket configuration
    • #135 I want to set Refill, Bandwidth capacity to 0

    Special notes about version number and backward compatibility: This release breaks backward compatibility, that is why the major version was increased. 6.0.0 version is used instead of 5.0.0 because 5.0.0 is already reserved for release form 5.0 branch.

    Backward compatibility has been broken in the following points:

    • replaceConfiguration method now accepts an additional parameter.
    • If you use native serialization for Hazelcast or Infinispan then binary serialized data is not compatible between 4.6.0 and 5.0.0, zero-downtime deployment can not be used in such use-case.
    Source code(tar.gz)
    Source code(zip)
  • 4.10.0(Mar 6, 2020)

  • 4.9.0(Feb 18, 2020)

    New features in the release:

    • #115 Compatibility with Hazelcast 4.x
    • Support for Hazelcast 3.x marked as a legacy, but will be supported by a long time.

    Note: for non-Hazelcast users there is no reasons to upgrade

    Source code(tar.gz)
    Source code(zip)
  • 4.8.0(Feb 6, 2020)

  • 4.7.0(Jan 2, 2020)

    Issue list:

    • #108 Support "Custom Serialization" for Hazelcast
    • #109 Support Protobuf serialization for Infinispan in order to be compatible with Infinispan 10.0
    Source code(tar.gz)
    Source code(zip)
  • 4.6.0(Nov 29, 2019)

    Issue list:

    • #106 providing Automatic-Module-Name via Manifest for compatibility with JPMS from java 9. Bucket4j itself is still written in java 8.
    Source code(tar.gz)
    Source code(zip)
  • 4.5.0(Jun 1, 2019)

  • 4.4.0(Apr 3, 2019)

  • 4.3.0(Dec 29, 2018)

  • 4.2.1(Dec 18, 2018)

  • 4.1.2(Dec 18, 2018)

  • 4.0.2(Dec 18, 2018)

  • 4.1.1(Sep 29, 2018)

  • 4.0.0(Apr 29, 2018)

  • 3.1.0(Dec 26, 2017)

    This release adressed to add following enhancements:

    • #53 New a method to get current rate setting for JCache
    • #54 Add method to locate existed bucket
    Source code(tar.gz)
    Source code(zip)
Owner
Vladimir Bukhtoyarov
I am lead software developer at DINS, and author of Bucket4j and Rolling-Metrics libraries.
Vladimir Bukhtoyarov
Hashids algorithm v1.0.0 implementation in Java

Hashids.java A small Java class to generate YouTube-like hashes from one or many numbers. Ported from javascript hashids.js by Ivan Akimov What is it?

CELLA 944 Jan 5, 2023
Diff Utils library is an OpenSource library for performing the comparison / diff operations between texts or some kind of data: computing diffs

Diff Utils library is an OpenSource library for performing the comparison / diff operations between texts or some kind of data: computing diffs, applying patches, generating unified diffs or parsing them, generating diff output for easy future displaying (like side-by-side view) and so on.

null 951 Jan 5, 2023
Mindustry java mod that adds a redstone-like wire-based logic system, tailored for making circuits.

See Esoterum-Solutions for builds 0.0-1.2 A small Mindustry Java mod that adds a wire-based logic system tailored for building circuits. New content s

null 36 Oct 25, 2022
Pass variables into methods based off name, not position.

Named Arguments are a feature that many languages lack. Some call it Feature Envy. The Problem You have a menu() method that prints out a 5 option men

Xavier D 3 Jul 1, 2022
A utility for guessing the CCSID of files (based on file contents)

CcsidGuesser A utility for guessing the CCSID of files (based on file contents). It can also fix CCSID tags and/or convert files to UTF-8! Usage Usage

Jesse Gorzinski 4 Feb 21, 2022
An open-source Java library for Constraint Programming

Documentation, Support and Issues Contributing Download and installation Choco-solver is an open-source Java library for Constraint Programming. Curre

null 607 Jan 3, 2023
A Java library for designing good error messages

JDoctor, a Java library for good error messages Designing good error messages is hard. In Java, most often, developers just rely on throwing exception

Cédric Champeau 125 Oct 24, 2022
Discord4J is a fast, powerful, unopinionated, reactive library to enable quick and easy development of Discord bots for Java, Kotlin, and other JVM languages using the official Discord Bot API.

Discord4J is a fast, powerful, unopinionated, reactive library to enable quick and easy development of Discord bots for Java, Kotlin, and other JVM languages using the official Discord Bot API.

null 1.5k Jan 4, 2023
Ta4j is an open source Java library for technical analysis

Ta4j is an open source Java library for technical analysis. It provides the basic components for creation, evaluation and execution of trading strategies.

null 1.7k Dec 31, 2022
hella-html is a library that makes it hella easy to generate dynamic HTML in vanilla Java.

Hella easy HTML in Java hella-html is a library that makes it hella easy to generate dynamic HTML in vanilla Java. Very lightweight and fast, the prim

null 1 Nov 23, 2022
documents4j is a Java library for converting documents into another document format

documents4j is a Java library for converting documents into another document format. This is achieved by delegating the conversion to any

documents4j 455 Dec 23, 2022
java common utils library

java-common-utils java common utils library 一个简单的Java通用工具类,目前的设想,包括简化异常处理工具、简易限流处理工具等 ExceptionHandler, 目标简化try catch的代码冗余度

xuangy 2 Jan 21, 2022
A Java API for checking if text contains profanity via the alt-profanity-checker Python library.

ProfanityCheckerAPI A Java API for checking if text contains profanity via the alt-profanity-checker Python library. It uses jep to run and interpret

William 2 Feb 19, 2022
High performance I/O library for Java using io_uring under the hood

nio_uring nio_uring is an I/O library for Java that uses io_uring under the hood, which aims to be: A simple and flexible API Super fast and efficient

Blake Beaupain 65 Dec 18, 2022
archifacts is a library to extract your architectural concepts out of your application's code

archifacts is a free (Apache 2.0 license) library for describing and detecting architectural building blocks and their relationships in your Java appl

null 45 Nov 29, 2022
Java lib for monitoring directories or individual files via java.nio.file.WatchService

ch.vorburger.fswatch Java lib for monitoring directories or individual files based on the java.nio.file.WatchService. Usage Get it from Maven Central

Michael Vorburger ⛑️ 21 Jan 7, 2022
Tencent Kona JDK11 is a no-cost, production-ready distribution of the Open Java Development Kit (OpenJDK), Long-Term Support(LTS) with quarterly updates. Tencent Kona JDK11 is certified as compatible with the Java SE standard.

Tencent Kona JDK11 Tencent Kona JDK11 is a no-cost, production-ready distribution of the Open Java Development Kit (OpenJDK), Long-Term Support(LTS) w

Tencent 268 Dec 16, 2022
This repository contains Java programs to become zero to hero in Java.

This repository contains Java programs to become zero to hero in Java. Data Structure programs topic wise are also present to learn data structure problem solving in Java. Programs related to each and every concep are present from easy to intermidiate level

Sahil Batra 15 Oct 9, 2022
Java Constraint Programming solver

https://maven-badges.herokuapp.com/maven-central/org.jacop/jacop/badge.svg [] (https://maven-badges.herokuapp.com/maven-central/org.jacop/jacop/) JaCo

null 202 Dec 30, 2022