未验证 提交 11f20593 编写于 作者: I Ilkka Seppälä 提交者: GitHub

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: NYanchaoMiao <11710204@mail.sustech.edu.cn>
上级 c66ca672
......@@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
## Explanation
Real world example
Real-world example
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
> establish a thread pool.
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so
> we establish a thread pool.
In plain words
......@@ -99,7 +99,7 @@ public class PotatoPeelingTask extends Task {
}
```
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
peeling and coffee making.
```java
......
......@@ -16,10 +16,12 @@ Ensure that a given client is not able to access service resources more than the
## Explanation
Real world example
Real-world example
> A large multinational corporation offers API to its customers. The API is rate-limited and each
> customer can only make certain amount of calls per second.
> 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.
In plain words
......@@ -33,30 +35,25 @@ In plain words
**Programmatic Example**
Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
calls per `BarCustomer`.
```java
public class Tenant {
private final String name;
private final int allowedCallsPerSecond;
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
if (allowedCallsPerSecond < 0) {
throw new InvalidParameterException("Number of calls less than 0 not allowed");
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);
}
this.name = name;
this.allowedCallsPerSecond = allowedCallsPerSecond;
callsCount.addTenant(name);
}
public String getName() {
return name;
}
public int getAllowedCallsPerSecond() {
return allowedCallsPerSecond;
}
}
@Slf4j
......@@ -76,14 +73,14 @@ public final class CallsCount {
}
public void reset() {
LOGGER.debug("Resetting the map.");
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
LOGGER.info("reset counters");
}
}
```
Next we introduce the service that the tenants are calling. To track the call count we use the
throttler timer.
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
timer is used.
```java
public interface Throttler {
......@@ -111,71 +108,103 @@ public class ThrottleTimerImpl implements Throttler {
}, 0, throttlePeriod);
}
}
```
class B2BService {
`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.
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
private final CallsCount callsCount;
```java
class Bartender {
public B2BService(Throttler timer, CallsCount callsCount) {
this.callsCount = callsCount;
timer.start();
}
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
private final CallsCount callsCount;
public int dummyCustomerApi(Tenant tenant) {
var tenantName = tenant.getName();
var count = callsCount.getCount(tenantName);
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
if (count >= tenant.getAllowedCallsPerSecond()) {
LOGGER.error("API access per second limit reached for: {}", tenantName);
return -1;
public Bartender(Throttler timer, CallsCount callsCount) {
this.callsCount = callsCount;
timer.start();
}
callsCount.incrementCount(tenantName);
return getRandomCustomerId();
}
private int getRandomCustomerId() {
return ThreadLocalRandom.current().nextInt(1, 10000);
}
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);
}
}
```
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
second and Nike to 6.
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.
```java
public static void main(String[] args) {
public static void main(String[] args) {
var callsCount = new CallsCount();
var adidas = new Tenant("Adidas", 5, callsCount);
var nike = new Tenant("Nike", 6, callsCount);
var human = new BarCustomer("young human", 2, callsCount);
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
var executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
executorService.execute(() -> makeServiceCalls(nike, callsCount));
executorService.execute(() -> makeServiceCalls(human, callsCount));
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
executorService.shutdown();
try {
executorService.awaitTermination(10, TimeUnit.SECONDS);
executorService.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.error("Executor Service terminated: {}", e.getMessage());
LOGGER.error("Executor service terminated: {}", e.getMessage());
}
}
}
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
var timer = new ThrottleTimerImpl(10, callsCount);
var service = new B2BService(timer, callsCount);
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
var timer = new ThrottleTimerImpl(1000, callsCount);
var service = new Bartender(timer, callsCount);
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
IntStream.range(0, 20).forEach(i -> {
service.dummyCustomerApi(tenant);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted: {}", e.getMessage());
}
IntStream.range(0, 50).forEach(i -> {
service.orderDrink(barCustomer);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted: {}", e.getMessage());
}
});
}
}
```
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!
```
## Class diagram
......@@ -185,7 +214,7 @@ second and Nike to 6.
The Throttling pattern should be used:
* When a service access needs to be restricted to not have high impacts on the performance of the service.
* When service access needs to be restricted not to have high impact on the performance of the service.
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
## Credits
......
......@@ -34,11 +34,11 @@ import lombok.extern.slf4j.Slf4j;
* complete service by users or a particular tenant. This can allow systems to continue to function
* and meet service level agreements, even when an increase in demand places load on resources.
* <p>
* In this example we have ({@link App}) as the initiating point of the service. This is a time
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time
* based throttling, i.e. only a certain number of calls are allowed per second.
* </p>
* ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link
* B2BService}) is the service which is consumed by the tenants and is throttled.
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed.
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled.
*/
@Slf4j
public class App {
......@@ -50,33 +50,35 @@ public class App {
*/
public static void main(String[] args) {
var callsCount = new CallsCount();
var adidas = new Tenant("Adidas", 5, callsCount);
var nike = new Tenant("Nike", 6, callsCount);
var human = new BarCustomer("young human", 2, callsCount);
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
var executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
executorService.execute(() -> makeServiceCalls(nike, callsCount));
executorService.execute(() -> makeServiceCalls(human, callsCount));
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
executorService.shutdown();
try {
executorService.awaitTermination(10, TimeUnit.SECONDS);
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
LOGGER.error("Executor Service terminated: {}", e.getMessage());
executorService.shutdownNow();
}
}
/**
* Make calls to the B2BService dummy API.
* Make calls to the bartender.
*/
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
var timer = new ThrottleTimerImpl(10, callsCount);
var service = new B2BService(timer, callsCount);
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
var timer = new ThrottleTimerImpl(1000, callsCount);
var service = new Bartender(timer, callsCount);
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
IntStream.range(0, 20).forEach(i -> {
service.dummyCustomerApi(tenant);
IntStream.range(0, 50).forEach(i -> {
service.orderDrink(barCustomer);
try {
Thread.sleep(1);
Thread.sleep(100);
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted: {}", e.getMessage());
}
......
......@@ -25,22 +25,26 @@ package com.iluwatar.throttling;
import java.security.InvalidParameterException;
import lombok.Getter;
/**
* A Pojo class to create a basic Tenant with the allowed calls per second.
* BarCustomer is a tenant with a name and a number of allowed calls per second.
*/
public class Tenant {
public class BarCustomer {
@Getter
private final String name;
@Getter
private final int allowedCallsPerSecond;
/**
* Constructor.
*
* @param name Name of the tenant
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
* @param name Name of the BarCustomer
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant.
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
*/
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
if (allowedCallsPerSecond < 0) {
throw new InvalidParameterException("Number of calls less than 0 not allowed");
}
......@@ -48,12 +52,4 @@ public class Tenant {
this.allowedCallsPerSecond = allowedCallsPerSecond;
callsCount.addTenant(name);
}
public String getName() {
return name;
}
public int getAllowedCallsPerSecond() {
return allowedCallsPerSecond;
}
}
......@@ -29,33 +29,32 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A service which accepts a tenant and throttles the resource based on the time given to the
* tenant.
* Bartender is a service which accepts a BarCustomer (tenant) and throttles
* the resource based on the time given to the tenant.
*/
class B2BService {
class Bartender {
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
private final CallsCount callsCount;
public B2BService(Throttler timer, CallsCount callsCount) {
public Bartender(Throttler timer, CallsCount callsCount) {
this.callsCount = callsCount;
timer.start();
}
/**
* Calls dummy customer api.
*
* Orders a drink from the bartender.
* @return customer id which is randomly generated
*/
public int dummyCustomerApi(Tenant tenant) {
var tenantName = tenant.getName();
public int orderDrink(BarCustomer barCustomer) {
var tenantName = barCustomer.getName();
var count = callsCount.getCount(tenantName);
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
if (count >= tenant.getAllowedCallsPerSecond()) {
LOGGER.error("API access per second limit reached for: {}", 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();
}
......
......@@ -69,7 +69,7 @@ public final class CallsCount {
* Resets the count of all the tenants in the map.
*/
public void reset() {
LOGGER.debug("Resetting the map.");
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
LOGGER.info("reset counters");
}
}
......@@ -23,20 +23,21 @@
package com.iluwatar.throttling;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import java.security.InvalidParameterException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* TenantTest to test the creation of Tenant with valid parameters.
*/
public class TenantTest {
public class BarCustomerTest {
@Test
void constructorTest() {
assertThrows(InvalidParameterException.class, () -> {
new Tenant("FailTenant", -1, new CallsCount());
new BarCustomer("sirBrave", -1, new CallsCount());
});
}
}
......@@ -32,19 +32,18 @@ import org.junit.jupiter.api.Test;
/**
* B2BServiceTest class to test the B2BService
*/
public class B2BServiceTest {
public class BartenderTest {
private final CallsCount callsCount = new CallsCount();
@Test
void dummyCustomerApiTest() {
var tenant = new Tenant("testTenant", 2, callsCount);
var tenant = new BarCustomer("pirate", 2, callsCount);
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation
var timer = (Throttler) () -> {
};
var service = new B2BService(timer, callsCount);
var timer = (Throttler) () -> {};
var service = new Bartender(timer, callsCount);
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi);
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink);
var counter = callsCount.getCount(tenant.getName());
assertEquals(2, counter, "Counter limit must be reached");
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册