diff --git a/throttling/README.md b/throttling/README.md index f31779573..efc966d04 100644 --- a/throttling/README.md +++ b/throttling/README.md @@ -10,11 +10,11 @@ tags: --- ## Intent -Ensure that a given tenant is not able to access resources more than the assigned limit. +Ensure that a given client is not able to access service resources more than the assigned limit. ![alt text](./etc/throttling-patern.png "Throttling pattern") ## Applicability 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 multiple tenants are consuming the same resources and restriction has to be made according to the usage per tenant. +* when multiple clients are consuming the same service resources and restriction has to be made according to the usage per client. diff --git a/throttling/etc/throttling-patern.png b/throttling/etc/throttling-patern.png deleted file mode 100644 index baa03816d..000000000 Binary files a/throttling/etc/throttling-patern.png and /dev/null differ diff --git a/throttling/etc/throttling-pattern.png b/throttling/etc/throttling-pattern.png new file mode 100644 index 000000000..a31aa54ce Binary files /dev/null and b/throttling/etc/throttling-pattern.png differ diff --git a/throttling/src/main/java/com/iluwatar/tls/App.java b/throttling/src/main/java/com/iluwatar/throttling/App.java similarity index 64% rename from throttling/src/main/java/com/iluwatar/tls/App.java rename to throttling/src/main/java/com/iluwatar/throttling/App.java index f476b10af..2dce68ab9 100644 --- a/throttling/src/main/java/com/iluwatar/tls/App.java +++ b/throttling/src/main/java/com/iluwatar/throttling/App.java @@ -21,12 +21,17 @@ * THE SOFTWARE. */ -package com.iluwatar.tls; +package com.iluwatar.throttling; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.file.AccessDeniedException; +import com.iluwatar.throttling.timer.Throttler; +import com.iluwatar.throttling.timer.ThrottleTimerImpl; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * Throttling pattern is a design pattern to throttle or limit the use of resources or even a complete service by @@ -49,36 +54,36 @@ public class App { */ public static void main(String[] args) { - Tenant adidas = new Tenant("Adidas", 80); - Tenant nike = new Tenant("Nike", 70); + Tenant adidas = new Tenant("Adidas", 5); + Tenant nike = new Tenant("Nike", 6); - B2BService adidasService = new B2BService(adidas); - B2BService nikeService = new B2BService(nike); - - Runnable adidasTask = () -> makeServiceCalls(adidasService); - Runnable nikeTask = () -> makeServiceCalls(nikeService); - - new Thread(adidasTask).start(); - new Thread(nikeTask).start(); + ExecutorService executorService = Executors.newFixedThreadPool(2); + + executorService.execute(() -> makeServiceCalls(adidas)); + executorService.execute(() -> makeServiceCalls(nike)); + + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.error("Executor Service terminated: {}", e.getMessage()); + } } /** * Make calls to the B2BService dummy API * @param service an instance of B2BService */ - private static void makeServiceCalls(B2BService service) { - for (int i = 0; i < 500; i++) { + private static void makeServiceCalls(Tenant tenant) { + Throttler timer = new ThrottleTimerImpl(10); + B2BService service = new B2BService(timer); + for (int i = 0; i < 20; i++) { + service.dummyCustomerApi(tenant); +// Sleep is introduced to keep the output in check and easy to view and analyze the results. try { - service.dummyCustomerApi(); -// This block is introduced to keep the output in check and easy to view and analyze the results. - try { - Thread.sleep(10); - } catch (InterruptedException e) { - e.printStackTrace(); - } -// It can be removed if required. - } catch (AccessDeniedException e) { - LOGGER.error("### {} ###", e.getMessage()); + Thread.sleep(1); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: {}", e.getMessage()); } } } diff --git a/throttling/src/main/java/com/iluwatar/tls/B2BService.java b/throttling/src/main/java/com/iluwatar/throttling/B2BService.java similarity index 60% rename from throttling/src/main/java/com/iluwatar/tls/B2BService.java rename to throttling/src/main/java/com/iluwatar/throttling/B2BService.java index e05a50583..c9acd4b73 100644 --- a/throttling/src/main/java/com/iluwatar/tls/B2BService.java +++ b/throttling/src/main/java/com/iluwatar/throttling/B2BService.java @@ -20,14 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.tls; +package com.iluwatar.throttling; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.file.AccessDeniedException; -import java.util.Timer; -import java.util.TimerTask; +import com.iluwatar.throttling.timer.Throttler; + import java.util.concurrent.ThreadLocalRandom; /** @@ -35,52 +34,29 @@ import java.util.concurrent.ThreadLocalRandom; */ class B2BService { - private Tenant tenant; - private int callsCounter; - private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class); - /** - * A timer is initiated as soon as the Service is initiated. The timer runs every minute and resets the - * counter. - * @param tenant the Tenant which will consume the service. - */ - public B2BService(Tenant tenant) { - this.tenant = tenant; - Timer timer = new Timer(true); - - timer.schedule(new TimerTask() { - @Override - public void run() { - callsCounter = 0; - } - }, 0, 1000); + public B2BService(Throttler timer) { + timer.start(); } /** * * @return customer id which is randomly generated - * @throws AccessDeniedException when the limit is reached */ - public int dummyCustomerApi() throws AccessDeniedException { - LOGGER.debug("Counter for {} : {} ", tenant.getName(), callsCounter); - - if (callsCounter >= tenant.getAllowedCallsPerSecond()) { - throw new AccessDeniedException("API access per second limit reached for: " + tenant.getName()); + public int dummyCustomerApi(Tenant tenant) { + String tenantName = tenant.getName(); + int 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; } - callsCounter++; + CallsCount.incrementCount(tenantName); return getRandomCustomerId(); } private int getRandomCustomerId() { return ThreadLocalRandom.current().nextInt(1, 10000); } - - /** - * - * @return current count of the calls made. - */ - public int getCurrentCallsCount() { - return callsCounter; - } } diff --git a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java new file mode 100644 index 000000000..81195b074 --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java @@ -0,0 +1,72 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.throttling; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A class to keep track of the counter of different Tenants + * @author drastogi + * + */ +public final class CallsCount { + private static Map tenantCallsCount = new ConcurrentHashMap<>(); + + /** + * Add a new tenant to the map. + * @param tenantName name of the tenant. + */ + public static void addTenant(String tenantName) { + if (!tenantCallsCount.containsKey(tenantName)) { + tenantCallsCount.put(tenantName, 0); + } + } + + /** + * Increment the count of the specified tenant. + * @param tenantName name of the tenant. + */ + public static void incrementCount(String tenantName) { + tenantCallsCount.put(tenantName, tenantCallsCount.get(tenantName) + 1); + } + + /** + * + * @param tenantName name of the tenant. + * @return the count of the tenant. + */ + public static int getCount(String tenantName) { + return tenantCallsCount.get(tenantName); + } + + /** + * Resets the count of all the tenants in the map. + */ + public static void reset() { + for (Entry e : tenantCallsCount.entrySet()) { + tenantCallsCount.put(e.getKey(), 0); + } + } +} diff --git a/throttling/src/main/java/com/iluwatar/tls/Tenant.java b/throttling/src/main/java/com/iluwatar/throttling/Tenant.java similarity index 90% rename from throttling/src/main/java/com/iluwatar/tls/Tenant.java rename to throttling/src/main/java/com/iluwatar/throttling/Tenant.java index 33d97a51e..a720e154b 100644 --- a/throttling/src/main/java/com/iluwatar/tls/Tenant.java +++ b/throttling/src/main/java/com/iluwatar/throttling/Tenant.java @@ -20,7 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.tls; +package com.iluwatar.throttling; import java.security.InvalidParameterException; @@ -44,21 +44,14 @@ public class Tenant { } this.name = name; this.allowedCallsPerSecond = allowedCallsPerSecond; + CallsCount.addTenant(name); } public String getName() { return name; } - public void setName(String name) { - this.name = name; - } - public int getAllowedCallsPerSecond() { return allowedCallsPerSecond; } - - public void setAllowedCallsPerSecond(int allowedCallsPerSecond) { - this.allowedCallsPerSecond = allowedCallsPerSecond; - } } diff --git a/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java b/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java new file mode 100644 index 000000000..51c5e3c28 --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java @@ -0,0 +1,58 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/** + * + */ +package com.iluwatar.throttling.timer; + +import java.util.Timer; +import java.util.TimerTask; + +import com.iluwatar.throttling.CallsCount; + +/** + * Implementation of throttler interface. This class resets the counter every second. + * @author drastogi + * + */ +public class ThrottleTimerImpl implements Throttler{ + + private int throttlePeriod; + + public ThrottleTimerImpl(int throttlePeriod) { + this.throttlePeriod = throttlePeriod; + } + + /** + * A timer is initiated with this method. The timer runs every second and resets the + * counter. + */ + public void start() { + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + CallsCount.reset(); + } + }, 0, throttlePeriod); + } +} diff --git a/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java b/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java new file mode 100644 index 000000000..24b73d92f --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java @@ -0,0 +1,36 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/** + * + */ +package com.iluwatar.throttling.timer; + +/** + * An interface for defining the structure of different types of throttling ways. + * @author drastogi + * + */ +public interface Throttler { + + void start(); +} diff --git a/throttling/src/test/java/com/iluwatar/throttling/AppTest.java b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java new file mode 100644 index 000000000..37c8e75ec --- /dev/null +++ b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.throttling; + +import org.junit.Test; + +/** + * Application test + */ +public class AppTest { + + @Test + public void test() { + final String[] args = {}; + App.main(args); + } +} diff --git a/throttling/src/test/java/com/iluwatar/tls/B2BServiceTest.java b/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java similarity index 70% rename from throttling/src/test/java/com/iluwatar/tls/B2BServiceTest.java rename to throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java index a9e043a95..b9ca1a1d8 100644 --- a/throttling/src/test/java/com/iluwatar/tls/B2BServiceTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java @@ -20,32 +20,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.tls; +package com.iluwatar.throttling; import org.junit.Assert; import org.junit.Test; -import java.nio.file.AccessDeniedException; +import com.iluwatar.throttling.timer.ThrottleTimerImpl; +import com.iluwatar.throttling.timer.Throttler; /** * B2BServiceTest class to test the B2BService */ public class B2BServiceTest { - + @Test - public void counterResetTest() throws AccessDeniedException { - Tenant tenant = new Tenant("testTenant", 100); - B2BService service = new B2BService(tenant); - - for (int i = 0; i < 20; i++) { - service.dummyCustomerApi(); - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } + public void dummyCustomerApiTest() { + Tenant tenant = new Tenant("testTenant", 2); + Throttler timer = new ThrottleTimerImpl(10); + B2BService service = new B2BService(timer); + + for (int i = 0; i < 5; i++) { + service.dummyCustomerApi(tenant); } - int counter = service.getCurrentCallsCount(); - Assert.assertTrue("", counter < 11); + + int counter = CallsCount.getCount(tenant.getName()); + Assert.assertTrue("Counter limit must be reached", counter == 2); } } diff --git a/throttling/src/test/java/com/iluwatar/tls/TenantTest.java b/throttling/src/test/java/com/iluwatar/throttling/TenantTest.java similarity index 95% rename from throttling/src/test/java/com/iluwatar/tls/TenantTest.java rename to throttling/src/test/java/com/iluwatar/throttling/TenantTest.java index d29152e83..2f0b5cf12 100644 --- a/throttling/src/test/java/com/iluwatar/tls/TenantTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/TenantTest.java @@ -20,10 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.tls; +package com.iluwatar.throttling; import org.junit.Test; +import com.iluwatar.throttling.Tenant; + import java.security.InvalidParameterException; /**