All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.bucket4j.Bucket Maven / Gradle / Ivy

There is a newer version: 8.0.1
Show newest version
/*-
 * ========================LICENSE_START=================================
 * Bucket4j
 * %%
 * Copyright (C) 2015 - 2020 Vladimir Bukhtoyarov
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =========================LICENSE_END==================================
 */
package io.github.bucket4j;

/**
 * Performs rate limiting using algorithm based on top of ideas of Token Bucket.
 * 

* Use following links for further details: *

*/ public interface Bucket { /** * Returns the {@link BlockingBucket} view of this bucket, that provides operations which are able to block caller thread. * * @return the view to bucket that can be used as scheduler */ BlockingBucket asScheduler(); /** * Returns the verbose view of this bucket. */ VerboseBucket asVerbose(); /** * Describes whether or not this bucket supports asynchronous mode. * *

If asynchronous mode is not supported any attempt to call {@link #asAsync()} will fail with {@link UnsupportedOperationException} * * @return true if this extension supports asynchronous mode. */ boolean isAsyncModeSupported(); /** * Returns asynchronous view of this bucket. * *

If asynchronous mode is not supported by particular extension behind this bucket, * then any attempt to call this method will fail with {@link UnsupportedOperationException}. * * @return Asynchronous view of this bucket. * * @throws UnsupportedOperationException if particular extension behind the bucket does not support asynchronous mode. */ AsyncBucket asAsync(); /** * Returns asynchronous view of this bucket that allows to use bucket as async scheduler. * *

If asynchronous mode is not supported by particular extension behind this bucket, * then any attempt to call this method will fail with {@link UnsupportedOperationException}. * * @return Asynchronous view of this bucket that allows to use bucket as async scheduler. * * @throws UnsupportedOperationException if particular extension behind the bucket does not support asynchronous mode. */ AsyncScheduledBucket asAsyncScheduler(); /** * Tries to consume a specified number of tokens from this bucket. * * @param numTokens The number of tokens to consume from the bucket, must be a positive number. * * @return {@code true} if the tokens were consumed, {@code false} otherwise. */ boolean tryConsume(long numTokens); /** * Consumes {@code tokens} from bucket ignoring all limits. * In result of this operation amount of tokens in the bucket could became negative. * * There are two possible reasons to use this method: *

    *
  • An operation with high priority should be executed independently of rate limits, but it should take effect to subsequent operation with bucket.
  • *
  • You want to apply custom blocking strategy instead of default which applied on {@code asScheduler().consume(tokens)}
  • *
* * @param tokens amount of tokens that should be consumed from bucket. * * @return * the amount of rate limit violation in nanoseconds calculated in following way: *
    *
  • zero if rate limit was not violated. For example bucket had 5 tokens before invocation of {@code consumeIgnoringRateLimits(2)}, * after invocation there are 3 tokens remain in the bucket, since limits were not violated zero returned as result.
  • *
  • Positive value which describes the amount of rate limit violation in nanoseconds. * For example bucket with limit 10 tokens per 1 second, currently has the 2 tokens available, last refill happen 100 milliseconds ago, and {@code consumeIgnoringRateLimits(6)} called. * 300_000_000 will be returned as result and available tokens in the bucket will became -3, and any variation of {@code tryConsume...} will not be successful for 400 milliseconds(time required to refill amount of available tokens until 1). *
  • *
*/ long consumeIgnoringRateLimits(long tokens); /** * Tries to consume a specified number of tokens from this bucket. * * @param numTokens The number of tokens to consume from the bucket, must be a positive number. * * @return {@link ConsumptionProbe} which describes both result of consumption and tokens remaining in the bucket after consumption. */ ConsumptionProbe tryConsumeAndReturnRemaining(long numTokens); /** * Estimates ability to consume a specified number of tokens. * * @param numTokens The number of tokens to consume, must be a positive number. * * @return {@link EstimationProbe} which describes the ability to consume. */ EstimationProbe estimateAbilityToConsume(long numTokens); /** * Tries to consume as much tokens from this bucket as available at the moment of invocation. * * @return number of tokens which has been consumed, or zero if was consumed nothing. */ long tryConsumeAsMuchAsPossible(); /** * Tries to consume as much tokens from bucket as available in the bucket at the moment of invocation, * but tokens which should be consumed is limited by {@code limit}. * * @param limit maximum number of tokens to consume, should be positive. * * @return number of tokens which has been consumed, or zero if was consumed nothing. */ long tryConsumeAsMuchAsPossible(long limit); /** * Add tokensToAdd to bucket. * Resulted count of tokens are calculated by following formula: *
newTokens = Math.min(capacity, currentTokens + tokensToAdd)
* in other words resulted number of tokens never exceeds capacity independent of tokensToAdd. * *

Example of usage

* The "compensating transaction" is one of obvious use case, when any piece of code consumed tokens from bucket, tried to do something and failed, the "addTokens" will be helpful to return tokens back to bucket: *
{@code
     *      Bucket wallet;
     *      ...
     *      if(wallet.tryConsume(50)) {// get 50 cents from wallet
     *         try {
     *             buyCocaCola();
     *         } catch(NoCocaColaException e) {
     *             // return money to wallet
     *             wallet.addTokens(50);
     *         }
     *      };
     * }
* * @param tokensToAdd number of tokens to add */ void addTokens(long tokensToAdd); /** * Returns amount of available tokens in this bucket. *

This method designed to be used only for monitoring and testing, you should never use this method for business cases, * because available tokens can be changed by concurrent transactions for case of multithreaded/multi-process environment. * * @return amount of available tokens */ long getAvailableTokens(); /** * Replaces configuration of this bucket. * *

* The first hard problem of configuration replacement is making decision how to propagate available tokens from bucket with previous configuration to bucket with new configuration. * If you don't care about previous bucket state then use {@link TokensInheritanceStrategy#RESET}. * But it becomes to a tricky problem when we expect that previous consumption(that has not been compensated by refill yet) should take effect to the bucket with new configuration. * In this case you need to make a choice between {@link TokensInheritanceStrategy#PROPORTIONALLY} and {@link TokensInheritanceStrategy#AS_IS}, read documentation about both with strong attention. * *

There is another problem when you are choosing {@link TokensInheritanceStrategy#PROPORTIONALLY} and {@link TokensInheritanceStrategy#AS_IS} and bucket has more then one bandwidth. * For example how does replaceConfiguration implementation should bind bandwidths to each other in the following example? *

     * 
     *     Bucket bucket = Bucket4j.builder()
     *                       .addLimit(Bandwidth.simple(10, Duration.ofSeconds(1)))
     *                       .addLimit(Bandwidth.simple(10000, Duration.ofHours(1)))
     *                       .build();
     *     ...
     *     BucketConfiguration newConfiguration = Bucket4j.configurationBuilder()
     *                                               .addLimit(Bandwidth.simple(5000, Duration.ofHours(1)))
     *                                               .addLimit(Bandwidth.simple(100, Duration.ofSeconds(10)))
     *                                               .build();
     *     bucket.replaceConfiguration(newConfiguration, TokensInheritanceStrategy.AS_IS);
     * 
     * 
* It is obviously that simple strategy - copying tokens by bandwidth index will not work well in this case, because of it highly depends from order. * Instead of inventing the backward maggic Bucket4j provides to you ability to deap controll of this process by specifying identifiers for bandwidth, * so in case of multiple bandwidth configuratoin replacement code can copy available tokens by bandwidth ID. So it is better to rewrite code above as following: *
     * 
     * Bucket bucket = Bucket4j.builder()
     *                            .addLimit(Bandwidth.simple(10, Duration.ofSeconds(1)).withId("technical-limit"))
     *                            .addLimit(Bandwidth.simple(10000, Duration.ofHours(1)).withId("business-limit"))
     *                            .build();
     * ...
     * BucketConfiguration newConfiguration = Bucket4j.configurationBuilder()
     *                            .addLimit(Bandwidth.simple(5000, Duration.ofHours(1)).withId("business-limit"))
     *                            .addLimit(Bandwidth.simple(100, Duration.ofSeconds(10)).withId("technical-limit"))
     *                            .build();
     * bucket.replaceConfiguration(newConfiguration, TokensInheritanceStrategy.AS_IS);
     * 
     * 
* * *

* There are following rules for bandwidth identifiers: *

    *
  • * By default bandwidth has null identifier. *
  • *
  • * null value of identifier equals to another null value if and only if there is only one bandwidth with null identifier. *
  • *
  • * If identifier for bandwidth is specified then it must has unique in the bucket. Bucket does not allow to create several bandwidth with same ID. *
  • *
  • * {@link TokensInheritanceStrategy#RESET} strategy will be applied for tokens migration during config replacement for bandwidth which has no bound bandwidth with same ID in previous configuration, * idependently of strategy that was requested. *
  • *
* * @param newConfiguration the new configuration * @param tokensInheritanceStrategy specifies the rules for inheritance of available tokens */ void replaceConfiguration(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy); /** * Creates the copy of internal state. * *

This method is designed to be used only for monitoring and testing, you should never use this method for business cases. * * @return snapshot of internal state */ BucketState createSnapshot(); /** * Returns new copy of this bucket instance decorated by {@code listener}. * The created bucket will share same tokens with source bucket and vice versa. * * See javadocs for {@link BucketListener} in order to understand semantic of listener. * * @param listener the listener of bucket events. * * @return new bucket instance decorated by {@code listener} */ Bucket toListenable(BucketListener listener); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy