Bucket
is a rate-limiter that is implemented on the top of ideas of well-known Token Bucket algorithm.
In the Bucket4j library code the Bucket
is represented by interface io.github.bucket4j.Bucket.
-
BucketConfiguration specifies an immutable collection of limitation rules that are used by the bucket during its work.
-
BucketState the place where bucket stores mutable state like the amount of currently available tokens.
A bucket can be constructed via a special builder API BucketBuilder that is available by factory method:
Bucket bucket = Bucket.builder()
.addLimit(...)
.build();
BucketConfiguration
can be described as collection of limits that are used by Bucket during its job. Configuration
In the Bucket4j library code the BucketConfiguration
is represented by class io.github.bucket4j.BucketConfiguration. Configuration is immutable, there is no way to add or remove a limit to already created configuration. However, you can replace the configuration of the bucket via creating a new configuration instance and calling bucket.replaceConfiguration(newConfiguration)
.
Usually, you should not create BucketConfiguration directly(excepting the case with configuration replacement) because BucketBuilder does for you behind the scene, for rare cases when you need to create configuration directly you have to use ConfigurationBuilder
that is available by factory method:
BucketConfiguration configuration = BucketConfiguration.builder()
.addLimit(...)
.build()
Important
|
Most users configure a single limit per configuration, but it is strongly recommended to analyze whether short-timed bursts problem can affect your application and if so then think about adding more limits. |
Limitations that are used by bucket can be denoted in terms of bandwidths. Bandwidth is denoted by the following terms:
- Capacity
-
Capacity is the term that is directly inherited from the classic interpretation of the token-bucket algorithm, this specifies how many tokens your bucket has.
- Refill
-
Refill specifies how fast tokens can be refilled after it was consumed from a bucket.
- Initial tokens
-
Bucket4j extends the token-bucket algorithm by allowing to specify the initial amount of tokens for each bandwidth. By default, an initial amount of tokens equal to capacity and can be changed by
withInitialTokens
method:Bandwidth bandwidth = Bandwidth.simple(42, Duration.ofMinutes(1)) .withInitialTokens(13);
- ID
-
The identifier is the optional attribute that is null by default. You may prefer to assign identifiers for bandwidths if you use on-the-fly configuration replacement and your buckets have more than one bandwidth per bucket, otherwise, it is better to avoid using identifiers to preserve memory. The Identifier for bandwidth can be specified by
withId
method:BucketConfiguration configuration = BucketConfiguration.builder() .addLimit(Bandwidth.simple(1000, Duration.ofMinutes(1)).withId("business-limit")) .addLimit(Bandwidth.simple(100, Duration.ofSeconds(1)).withId("burst-protection")) .build();
NoteIdentifiers are critical for on-the-fly configuration replacement functionality because during replacement it needs to decide how correctly propagate information about already consumed tokens from the state before config replacement to the state after replacement. This is not a trivial task especially when the number of limits is changing.
Specifies the speed of tokens regeneration.
- Greedy
-
This type of refill greedily regenerates tokens manner, it tries to add the tokens to the bucket as soon as possible. For example refill "10 tokens per 1 second" adds 1 token per every 100 milliseconds, in other words, the refill will not wait 1 second to regenerate a whole bunch of 10 tokens. The three refills below do refill of tokens with the same speed:
Refill.greedy(600, Duration.ofMinutes(1)); Refill.greedy(10, Duration.ofSeconds(1)); Refill.greedy(1, Duration.ofMillis(100));
Greedy
is the default type of refill that is used when you createsimple
bandwidth// the two lines of code bellow are fully equivalent Bandwidth.simple(100, Duration.ofMinutes(1)) Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1)))
- Interval
-
This type of refill regenerates tokens in an interval manner. "Interval" in opposite to "greedy" will wait until the whole period will be elapsed before regenerating the whole amount of tokens.
Example:// generates 100 tokens each minute Refill.intervally(100, Duration.ofMinutes(1));
- IntervallyAligned
-
This type of refill regenerates that does refill of tokens in an interval manner. Interval" in opposite to "greedy" will wait until the whole period will be elapsed before regenerating the whole amount of tokens. In addition to Interval it is possible to specify the time when the first refill should happen. This type can be used to configure clear interval boundary i.e. start of the second, minute, hour, day. To get more details to read javadocs for
Refill#intervallyAligned
method.Example:// imagine that wall clock is 16:20, the first refill will happen at 17:00 // first refill will happen in the beginning of next hour Instant firstRefillTime = ZonedDateTime.now() .truncatedTo(ChronoUnit.HOURS) .plus(1, ChronoUnit.HOURS) .toInstant(); Bandwidth.classic(400, Refill.intervallyAligned(400, Duration.ofHours(1), firstRefillTime, true));
BucketState is the place where bucket stores own mutable state like:
-
Amount of currently available tokens.
-
Timestamp when the last refill was happen.
BucketState
is represented by interface io.github.bucket4j.BucketState. Usually you never interact with this interface, excepting the cases when you want to get access to low-level diagnostic API that is described in
It was explicitly decided by library authors to not provide for end-users to construct a library entity via direct constructors.
-
To be able in the future to change internal implementations without breaking backward compatibility.
-
To to provide
Fluent Builder API
that in our minds is a good modern library design pattern.
LocalBucketBuilder
is a fluent builder that is specialized to construct the local buckets, where a local bucket is a bucket that holds an internal state just in memory and does not provide clustering functionality. Bellow an example of LocalBucketBuilder usage:
Bucket bucket = Bucket.builder()
.addLimit(Bandwidth.simple())
.withNanosecondPrecision()
.withSynchronizationStrategy(SynchronizationStrategy.LOCK_FREE)
.build()