Skip to content

Commit 11f2059

Browse files
iluwatarhrighter
andauthored
Update throttling pattern (#1937)
* Create component.urm.puml * Create App.java * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Create AppTest.java * Add files via upload * Update README.md * Update README.md * Update pom.xml * Update App.java * Update BjornGraphicsComponent.java * Update BjornInputComponent.java * Update BjornPhysicsComponent.java * Update Component.java * Update App.java * Delete App.java * Delete BjornGraphicsComponent.java * Delete BjornInputComponent.java * Delete BjornPhysicsComponent.java * Delete Component.java * Delete GameObject.java * Delete GraphicsComponent.java * Delete InputComponent.java * Delete PhysicsComponent.java * Create App.java * Update App.java * Update App.java * Create BjornGraphicsComponent.java * Create BjornInputComponent.java * Create BjornPhysicsComponent.java * Create Component.java * Create GameObject.java * Create GraphicsComponent.java * Create InputComponent.java * Create PhysicsComponent.java * Delete AppTest.java * Delete UpdateTest.java * Create AppTest.java * Create UpdateTest.java * Update throttling pattern example * delete unwanted files Co-authored-by: YanchaoMiao <[email protected]>
1 parent c66ca67 commit 11f2059

File tree

8 files changed

+152
-126
lines changed

8 files changed

+152
-126
lines changed

thread-pool/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
1818

1919
## Explanation
2020

21-
Real world example
21+
Real-world example
2222

2323
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
24-
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
25-
> establish a thread pool.
24+
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so
25+
> we establish a thread pool.
2626
2727
In plain words
2828

@@ -99,7 +99,7 @@ public class PotatoPeelingTask extends Task {
9999
}
100100
```
101101

102-
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
102+
Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
103103
peeling and coffee making.
104104

105105
```java

throttling/README.md

Lines changed: 99 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ Ensure that a given client is not able to access service resources more than the
1616

1717
## Explanation
1818

19-
Real world example
19+
Real-world example
2020

21-
> A large multinational corporation offers API to its customers. The API is rate-limited and each
22-
> customer can only make certain amount of calls per second.
21+
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
22+
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast
23+
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
24+
> be higher.
2325
2426
In plain words
2527

@@ -33,30 +35,25 @@ In plain words
3335
3436
**Programmatic Example**
3537

36-
Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
38+
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
39+
calls per `BarCustomer`.
3740

3841
```java
39-
public class Tenant {
40-
41-
private final String name;
42-
private final int allowedCallsPerSecond;
43-
44-
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
45-
if (allowedCallsPerSecond < 0) {
46-
throw new InvalidParameterException("Number of calls less than 0 not allowed");
42+
public class BarCustomer {
43+
44+
@Getter
45+
private final String name;
46+
@Getter
47+
private final int allowedCallsPerSecond;
48+
49+
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
50+
if (allowedCallsPerSecond < 0) {
51+
throw new InvalidParameterException("Number of calls less than 0 not allowed");
52+
}
53+
this.name = name;
54+
this.allowedCallsPerSecond = allowedCallsPerSecond;
55+
callsCount.addTenant(name);
4756
}
48-
this.name = name;
49-
this.allowedCallsPerSecond = allowedCallsPerSecond;
50-
callsCount.addTenant(name);
51-
}
52-
53-
public String getName() {
54-
return name;
55-
}
56-
57-
public int getAllowedCallsPerSecond() {
58-
return allowedCallsPerSecond;
59-
}
6057
}
6158

6259
@Slf4j
@@ -76,14 +73,14 @@ public final class CallsCount {
7673
}
7774

7875
public void reset() {
79-
LOGGER.debug("Resetting the map.");
8076
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
77+
LOGGER.info("reset counters");
8178
}
8279
}
8380
```
8481

85-
Next we introduce the service that the tenants are calling. To track the call count we use the
86-
throttler timer.
82+
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
83+
timer is used.
8784

8885
```java
8986
public interface Throttler {
@@ -111,71 +108,103 @@ public class ThrottleTimerImpl implements Throttler {
111108
}, 0, throttlePeriod);
112109
}
113110
}
111+
```
114112

115-
class B2BService {
113+
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't
114+
know that the beer serving rate is limited by their appearances.
116115

117-
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
118-
private final CallsCount callsCount;
116+
```java
117+
class Bartender {
119118

120-
public B2BService(Throttler timer, CallsCount callsCount) {
121-
this.callsCount = callsCount;
122-
timer.start();
123-
}
119+
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
120+
private final CallsCount callsCount;
124121

125-
public int dummyCustomerApi(Tenant tenant) {
126-
var tenantName = tenant.getName();
127-
var count = callsCount.getCount(tenantName);
128-
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
129-
if (count >= tenant.getAllowedCallsPerSecond()) {
130-
LOGGER.error("API access per second limit reached for: {}", tenantName);
131-
return -1;
122+
public Bartender(Throttler timer, CallsCount callsCount) {
123+
this.callsCount = callsCount;
124+
timer.start();
132125
}
133-
callsCount.incrementCount(tenantName);
134-
return getRandomCustomerId();
135-
}
136126

137-
private int getRandomCustomerId() {
138-
return ThreadLocalRandom.current().nextInt(1, 10000);
139-
}
127+
public int orderDrink(BarCustomer barCustomer) {
128+
var tenantName = barCustomer.getName();
129+
var count = callsCount.getCount(tenantName);
130+
if (count >= barCustomer.getAllowedCallsPerSecond()) {
131+
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
132+
return -1;
133+
}
134+
callsCount.incrementCount(tenantName);
135+
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
136+
return getRandomCustomerId();
137+
}
138+
139+
private int getRandomCustomerId() {
140+
return ThreadLocalRandom.current().nextInt(1, 10000);
141+
}
140142
}
141143
```
142144

143-
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
144-
second and Nike to 6.
145+
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2
146+
calls per second and the old dwarf to 4.
145147

146148
```java
147-
public static void main(String[] args) {
149+
public static void main(String[] args) {
148150
var callsCount = new CallsCount();
149-
var adidas = new Tenant("Adidas", 5, callsCount);
150-
var nike = new Tenant("Nike", 6, callsCount);
151+
var human = new BarCustomer("young human", 2, callsCount);
152+
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
151153

152154
var executorService = Executors.newFixedThreadPool(2);
153-
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
154-
executorService.execute(() -> makeServiceCalls(nike, callsCount));
155+
156+
executorService.execute(() -> makeServiceCalls(human, callsCount));
157+
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
158+
155159
executorService.shutdown();
156-
157160
try {
158-
executorService.awaitTermination(10, TimeUnit.SECONDS);
161+
executorService.awaitTermination(10, TimeUnit.SECONDS);
159162
} catch (InterruptedException e) {
160-
LOGGER.error("Executor Service terminated: {}", e.getMessage());
163+
LOGGER.error("Executor service terminated: {}", e.getMessage());
161164
}
162-
}
165+
}
163166

164-
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
165-
var timer = new ThrottleTimerImpl(10, callsCount);
166-
var service = new B2BService(timer, callsCount);
167+
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
168+
var timer = new ThrottleTimerImpl(1000, callsCount);
169+
var service = new Bartender(timer, callsCount);
167170
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
168-
IntStream.range(0, 20).forEach(i -> {
169-
service.dummyCustomerApi(tenant);
170-
try {
171-
Thread.sleep(1);
172-
} catch (InterruptedException e) {
173-
LOGGER.error("Thread interrupted: {}", e.getMessage());
174-
}
171+
IntStream.range(0, 50).forEach(i -> {
172+
service.orderDrink(barCustomer);
173+
try {
174+
Thread.sleep(100);
175+
} catch (InterruptedException e) {
176+
LOGGER.error("Thread interrupted: {}", e.getMessage());
177+
}
175178
});
176-
}
179+
}
177180
```
178181

182+
An excerpt from the example's console output:
183+
184+
```
185+
18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters
186+
18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters
187+
18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed]
188+
18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed]
189+
18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed]
190+
18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed]
191+
18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
192+
18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed]
193+
18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
194+
18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed]
195+
18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
196+
18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
197+
18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
198+
18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
199+
18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
200+
18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
201+
18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
202+
18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
203+
18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
204+
18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
205+
18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
206+
18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
207+
```
179208

180209
## Class diagram
181210

@@ -185,7 +214,7 @@ second and Nike to 6.
185214

186215
The Throttling pattern should be used:
187216

188-
* When a service access needs to be restricted to not have high impacts on the performance of the service.
217+
* When service access needs to be restricted not to have high impact on the performance of the service.
189218
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
190219

191220
## Credits

throttling/src/main/java/com/iluwatar/throttling/App.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@
3434
* complete service by users or a particular tenant. This can allow systems to continue to function
3535
* and meet service level agreements, even when an increase in demand places load on resources.
3636
* <p>
37-
* In this example we have ({@link App}) as the initiating point of the service. This is a time
37+
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time
3838
* based throttling, i.e. only a certain number of calls are allowed per second.
3939
* </p>
40-
* ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link
41-
* B2BService}) is the service which is consumed by the tenants and is throttled.
40+
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed.
41+
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled.
4242
*/
4343
@Slf4j
4444
public class App {
@@ -50,33 +50,35 @@ public class App {
5050
*/
5151
public static void main(String[] args) {
5252
var callsCount = new CallsCount();
53-
var adidas = new Tenant("Adidas", 5, callsCount);
54-
var nike = new Tenant("Nike", 6, callsCount);
53+
var human = new BarCustomer("young human", 2, callsCount);
54+
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
5555

5656
var executorService = Executors.newFixedThreadPool(2);
5757

58-
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
59-
executorService.execute(() -> makeServiceCalls(nike, callsCount));
58+
executorService.execute(() -> makeServiceCalls(human, callsCount));
59+
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
6060

6161
executorService.shutdown();
6262
try {
63-
executorService.awaitTermination(10, TimeUnit.SECONDS);
63+
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
64+
executorService.shutdownNow();
65+
}
6466
} catch (InterruptedException e) {
65-
LOGGER.error("Executor Service terminated: {}", e.getMessage());
67+
executorService.shutdownNow();
6668
}
6769
}
6870

6971
/**
70-
* Make calls to the B2BService dummy API.
72+
* Make calls to the bartender.
7173
*/
72-
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
73-
var timer = new ThrottleTimerImpl(10, callsCount);
74-
var service = new B2BService(timer, callsCount);
74+
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
75+
var timer = new ThrottleTimerImpl(1000, callsCount);
76+
var service = new Bartender(timer, callsCount);
7577
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
76-
IntStream.range(0, 20).forEach(i -> {
77-
service.dummyCustomerApi(tenant);
78+
IntStream.range(0, 50).forEach(i -> {
79+
service.orderDrink(barCustomer);
7880
try {
79-
Thread.sleep(1);
81+
Thread.sleep(100);
8082
} catch (InterruptedException e) {
8183
LOGGER.error("Thread interrupted: {}", e.getMessage());
8284
}

throttling/src/main/java/com/iluwatar/throttling/Tenant.java renamed to throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,31 @@
2525

2626
import java.security.InvalidParameterException;
2727

28+
import lombok.Getter;
29+
2830
/**
29-
* A Pojo class to create a basic Tenant with the allowed calls per second.
31+
* BarCustomer is a tenant with a name and a number of allowed calls per second.
3032
*/
31-
public class Tenant {
33+
public class BarCustomer {
3234

35+
@Getter
3336
private final String name;
37+
@Getter
3438
private final int allowedCallsPerSecond;
3539

3640
/**
3741
* Constructor.
3842
*
39-
* @param name Name of the tenant
40-
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
43+
* @param name Name of the BarCustomer
44+
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant.
4145
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
4246
*/
43-
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
47+
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
4448
if (allowedCallsPerSecond < 0) {
4549
throw new InvalidParameterException("Number of calls less than 0 not allowed");
4650
}
4751
this.name = name;
4852
this.allowedCallsPerSecond = allowedCallsPerSecond;
4953
callsCount.addTenant(name);
5054
}
51-
52-
public String getName() {
53-
return name;
54-
}
55-
56-
public int getAllowedCallsPerSecond() {
57-
return allowedCallsPerSecond;
58-
}
5955
}

0 commit comments

Comments
 (0)