2017-09-07 12:54:19 +02:00
|
|
|
---
|
|
|
|
layout: pattern
|
2017-09-13 08:22:29 +03:00
|
|
|
title: Throttling
|
2017-09-07 12:54:19 +02:00
|
|
|
folder: throttling
|
|
|
|
permalink: /patterns/throttling/
|
2017-09-13 20:39:31 +03:00
|
|
|
categories: Behavioral
|
2021-05-19 10:49:05 -06:00
|
|
|
language: en
|
2017-09-07 12:54:19 +02:00
|
|
|
tags:
|
2019-12-13 21:09:28 +02:00
|
|
|
- Performance
|
2020-07-26 11:30:42 +03:00
|
|
|
- Cloud distributed
|
2017-09-07 12:54:19 +02:00
|
|
|
---
|
|
|
|
|
2017-09-07 15:27:18 +02:00
|
|
|
## Intent
|
2020-09-13 18:26:52 +03:00
|
|
|
|
2017-09-08 16:15:31 +02:00
|
|
|
Ensure that a given client is not able to access service resources more than the assigned limit.
|
2017-09-07 15:27:18 +02:00
|
|
|
|
2020-07-22 20:31:56 +03:00
|
|
|
## Explanation
|
2020-09-13 18:26:52 +03:00
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
Real-world example
|
2020-07-22 20:31:56 +03:00
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
|
|
|
|
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast
|
|
|
|
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
|
|
|
|
> be higher.
|
2020-07-22 20:31:56 +03:00
|
|
|
|
|
|
|
In plain words
|
|
|
|
|
|
|
|
> Throttling pattern is used to rate-limit access to a resource.
|
|
|
|
|
|
|
|
[Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) says
|
|
|
|
|
2020-09-13 18:26:52 +03:00
|
|
|
> Control the consumption of resources used by an instance of an application, an individual tenant,
|
|
|
|
> or an entire service. This can allow the system to continue to function and meet service level
|
|
|
|
> agreements, even when an increase in demand places an extreme load on resources.
|
2020-07-22 20:31:56 +03:00
|
|
|
|
|
|
|
**Programmatic Example**
|
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
|
|
|
|
calls per `BarCustomer`.
|
2020-07-22 20:31:56 +03:00
|
|
|
|
|
|
|
```java
|
2022-01-07 09:46:59 +02:00
|
|
|
public class BarCustomer {
|
|
|
|
|
|
|
|
@Getter
|
|
|
|
private final String name;
|
|
|
|
@Getter
|
|
|
|
private final int allowedCallsPerSecond;
|
|
|
|
|
|
|
|
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
|
|
|
if (allowedCallsPerSecond < 0) {
|
|
|
|
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
|
|
|
}
|
|
|
|
this.name = name;
|
|
|
|
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
|
|
|
callsCount.addTenant(name);
|
2020-07-22 20:31:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-13 13:19:21 +01:00
|
|
|
@Slf4j
|
2020-07-22 20:31:56 +03:00
|
|
|
public final class CallsCount {
|
2020-07-30 20:28:47 +03:00
|
|
|
private final Map<String, AtomicLong> tenantCallsCount = new ConcurrentHashMap<>();
|
2020-07-22 20:31:56 +03:00
|
|
|
|
|
|
|
public void addTenant(String tenantName) {
|
|
|
|
tenantCallsCount.putIfAbsent(tenantName, new AtomicLong(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
public void incrementCount(String tenantName) {
|
|
|
|
tenantCallsCount.get(tenantName).incrementAndGet();
|
|
|
|
}
|
|
|
|
|
|
|
|
public long getCount(String tenantName) {
|
|
|
|
return tenantCallsCount.get(tenantName).get();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void reset() {
|
|
|
|
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
2022-01-07 09:46:59 +02:00
|
|
|
LOGGER.info("reset counters");
|
2020-07-22 20:31:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
|
|
|
|
timer is used.
|
2020-07-22 20:31:56 +03:00
|
|
|
|
|
|
|
```java
|
|
|
|
public interface Throttler {
|
|
|
|
|
|
|
|
void start();
|
|
|
|
}
|
|
|
|
|
|
|
|
public class ThrottleTimerImpl implements Throttler {
|
|
|
|
|
|
|
|
private final int throttlePeriod;
|
|
|
|
private final CallsCount callsCount;
|
|
|
|
|
|
|
|
public ThrottleTimerImpl(int throttlePeriod, CallsCount callsCount) {
|
|
|
|
this.throttlePeriod = throttlePeriod;
|
|
|
|
this.callsCount = callsCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void start() {
|
|
|
|
new Timer(true).schedule(new TimerTask() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
callsCount.reset();
|
|
|
|
}
|
|
|
|
}, 0, throttlePeriod);
|
|
|
|
}
|
|
|
|
}
|
2022-01-07 09:46:59 +02:00
|
|
|
```
|
2020-07-22 20:31:56 +03:00
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't
|
|
|
|
know that the beer serving rate is limited by their appearances.
|
2020-07-22 20:31:56 +03:00
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
```java
|
|
|
|
class Bartender {
|
2020-07-22 20:31:56 +03:00
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
|
|
|
private final CallsCount callsCount;
|
2020-07-22 20:31:56 +03:00
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
public Bartender(Throttler timer, CallsCount callsCount) {
|
|
|
|
this.callsCount = callsCount;
|
|
|
|
timer.start();
|
2020-07-22 20:31:56 +03:00
|
|
|
}
|
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
public int orderDrink(BarCustomer barCustomer) {
|
|
|
|
var tenantName = barCustomer.getName();
|
|
|
|
var count = callsCount.getCount(tenantName);
|
|
|
|
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
|
|
|
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
callsCount.incrementCount(tenantName);
|
|
|
|
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
|
|
|
|
return getRandomCustomerId();
|
|
|
|
}
|
|
|
|
|
|
|
|
private int getRandomCustomerId() {
|
|
|
|
return ThreadLocalRandom.current().nextInt(1, 10000);
|
|
|
|
}
|
2020-07-22 20:31:56 +03:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2
|
|
|
|
calls per second and the old dwarf to 4.
|
2020-07-22 20:31:56 +03:00
|
|
|
|
|
|
|
```java
|
2022-01-07 09:46:59 +02:00
|
|
|
public static void main(String[] args) {
|
2020-07-22 20:31:56 +03:00
|
|
|
var callsCount = new CallsCount();
|
2022-01-07 09:46:59 +02:00
|
|
|
var human = new BarCustomer("young human", 2, callsCount);
|
|
|
|
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
2020-07-22 20:31:56 +03:00
|
|
|
|
|
|
|
var executorService = Executors.newFixedThreadPool(2);
|
2022-01-07 09:46:59 +02:00
|
|
|
|
|
|
|
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
|
|
|
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
|
|
|
|
2020-07-22 20:31:56 +03:00
|
|
|
executorService.shutdown();
|
|
|
|
try {
|
2022-01-07 09:46:59 +02:00
|
|
|
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
2020-07-22 20:31:56 +03:00
|
|
|
} catch (InterruptedException e) {
|
2022-01-07 09:46:59 +02:00
|
|
|
LOGGER.error("Executor service terminated: {}", e.getMessage());
|
2020-07-22 20:31:56 +03:00
|
|
|
}
|
2022-01-07 09:46:59 +02:00
|
|
|
}
|
2020-07-22 20:31:56 +03:00
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
|
|
|
var timer = new ThrottleTimerImpl(1000, callsCount);
|
|
|
|
var service = new Bartender(timer, callsCount);
|
2020-07-22 20:31:56 +03:00
|
|
|
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
2022-01-07 09:46:59 +02:00
|
|
|
IntStream.range(0, 50).forEach(i -> {
|
|
|
|
service.orderDrink(barCustomer);
|
|
|
|
try {
|
|
|
|
Thread.sleep(100);
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
|
|
|
}
|
2020-07-22 20:31:56 +03:00
|
|
|
});
|
2022-01-07 09:46:59 +02:00
|
|
|
}
|
2020-07-22 20:31:56 +03:00
|
|
|
```
|
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
An excerpt from the example's console output:
|
|
|
|
|
|
|
|
```
|
|
|
|
18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters
|
|
|
|
18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters
|
|
|
|
18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed]
|
|
|
|
18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed]
|
|
|
|
18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed]
|
|
|
|
18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed]
|
|
|
|
18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
|
|
|
18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed]
|
|
|
|
18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
|
|
|
18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed]
|
|
|
|
18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
|
|
|
18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
|
|
|
18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
|
|
|
18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
|
|
|
18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
|
|
|
18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
|
|
|
18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
|
|
|
18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
|
|
|
18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
|
|
|
18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
|
|
|
18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
|
|
|
18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
|
|
|
```
|
2020-07-22 20:31:56 +03:00
|
|
|
|
2019-12-07 20:01:13 +02:00
|
|
|
## Class diagram
|
2020-09-13 18:26:52 +03:00
|
|
|
|
|
|
|

|
2019-12-07 20:01:13 +02:00
|
|
|
|
2017-09-07 12:54:19 +02:00
|
|
|
## Applicability
|
2020-09-13 18:26:52 +03:00
|
|
|
|
2017-09-07 12:54:19 +02:00
|
|
|
The Throttling pattern should be used:
|
|
|
|
|
2022-01-07 09:46:59 +02:00
|
|
|
* When service access needs to be restricted not to have high impact on the performance of the service.
|
2019-12-13 21:09:28 +02:00
|
|
|
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
|
2020-07-22 20:31:56 +03:00
|
|
|
|
|
|
|
## Credits
|
|
|
|
|
|
|
|
* [Throttling pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling)
|
|
|
|
* [Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications (Microsoft patterns & practices)](https://www.amazon.com/gp/product/B00ITGHBBS/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B00ITGHBBS&linkId=12aacdd0cec04f372e7152689525631a)
|