1+ package com .iluwatar .rate .limiting .pattern ;
2+
3+ import java .util .Random ;
4+ import java .util .concurrent .*;
5+ import java .util .concurrent .atomic .AtomicBoolean ;
6+ import java .util .concurrent .atomic .AtomicInteger ;
7+
8+ /**
9+ * The <em>Rate Limiter</em> pattern is a key defensive strategy used to prevent system overload
10+ * and ensure fair usage of shared services. This demo showcases how different rate limiting techniques
11+ * can regulate traffic in distributed systems.
12+ *
13+ * <p>Specifically, this simulation implements three rate limiter strategies:
14+ *
15+ * <ul>
16+ * <li><b>Token Bucket</b> – Allows short bursts followed by steady request rates.</li>
17+ * <li><b>Fixed Window</b> – Enforces a strict limit per discrete time window (e.g., 3 requests/sec).</li>
18+ * <li><b>Adaptive</b> – Dynamically scales limits based on system health, simulating elastic backoff.</li>
19+ * </ul>
20+ *
21+ * <p>Each simulated service (e.g., S3, DynamoDB, Lambda) is governed by one of these limiters. Multiple
22+ * concurrent client threads issue randomized requests to these services over a fixed duration. Each
23+ * request is either:
24+ *
25+ * <ul>
26+ * <li><b>ALLOWED</b> – Permitted under the current rate limit</li>
27+ * <li><b>THROTTLED</b> – Rejected due to quota exhaustion</li>
28+ * <li><b>FAILED</b> – Dropped due to transient service failure</li>
29+ * </ul>
30+ *
31+ * <p>Statistics are printed every few seconds, and the simulation exits gracefully after a fixed runtime,
32+ * offering a clear view into how each limiter behaves under pressure.
33+ *
34+ * <p><b>Relation to AWS API Gateway:</b><br>
35+ * This implementation mirrors the throttling behavior described in the
36+ * <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html">
37+ * AWS API Gateway Request Throttling documentation</a>, where limits are applied per second and over
38+ * longer durations (burst and rate limits). The <code>TokenBucketRateLimiter</code> mimics burst capacity,
39+ * the <code>FixedWindowRateLimiter</code> models steady rate enforcement, and the <code>AdaptiveRateLimiter</code>
40+ * reflects elasticity in real-world systems like AWS Lambda under variable load.
41+ *
42+ * */
43+ public final class App {
44+ private static final int RUN_DURATION_SECONDS = 10 ;
45+ private static final int SHUTDOWN_TIMEOUT_SECONDS = 5 ;
46+
47+ private static final AtomicInteger successfulRequests = new AtomicInteger ();
48+ private static final AtomicInteger throttledRequests = new AtomicInteger ();
49+ private static final AtomicInteger failedRequests = new AtomicInteger ();
50+ private static final AtomicBoolean running = new AtomicBoolean (true );
51+
52+ public static void main (String [] args ) {
53+ System .out .println ("\n Starting Rate Limiter Demo" );
54+ System .out .println ("====================================" );
55+
56+ ExecutorService executor = Executors .newFixedThreadPool (3 );
57+ ScheduledExecutorService statsPrinter = Executors .newSingleThreadScheduledExecutor ();
58+
59+ try {
60+ // Explicit limiter setup for demonstration clarity
61+ TokenBucketRateLimiter tb = new TokenBucketRateLimiter (2 , 1 ); // capacity 2, refill 1/sec
62+ FixedWindowRateLimiter fw = new FixedWindowRateLimiter (3 , 1 ); // max 3 req/sec
63+ AdaptiveRateLimiter ar = new AdaptiveRateLimiter (2 , 6 ); // adaptive from 2 to 6 req/sec
64+
65+ // Print statistics every 2 seconds
66+ statsPrinter .scheduleAtFixedRate (App ::printStats , 2 , 2 , TimeUnit .SECONDS );
67+
68+ // Launch 3 simulated clients
69+ for (int i = 1 ; i <= 3 ; i ++) {
70+ executor .submit (createClientTask (i , tb , fw , ar ));
71+ }
72+
73+ // Run simulation for N seconds
74+ Thread .sleep (RUN_DURATION_SECONDS * 1000L );
75+ System .out .println ("\n Shutting down the demo..." );
76+
77+ running .set (false );
78+ executor .shutdown ();
79+ statsPrinter .shutdown ();
80+
81+ if (!executor .awaitTermination (SHUTDOWN_TIMEOUT_SECONDS , TimeUnit .SECONDS )) {
82+ executor .shutdownNow ();
83+ }
84+ if (!statsPrinter .awaitTermination (SHUTDOWN_TIMEOUT_SECONDS , TimeUnit .SECONDS )) {
85+ statsPrinter .shutdownNow ();
86+ }
87+
88+ } catch (InterruptedException e ) {
89+ Thread .currentThread ().interrupt ();
90+ } finally {
91+ printFinalStats ();
92+ System .out .println ("Demo completed." );
93+ }
94+ }
95+
96+ private static Runnable createClientTask (int clientId ,
97+ RateLimiter s3Limiter ,
98+ RateLimiter dynamoDbLimiter ,
99+ RateLimiter lambdaLimiter ) {
100+ return () -> {
101+ String [] services = {"s3" , "dynamodb" , "lambda" };
102+ String [] operations = {
103+ "GetObject" , "PutObject" , "Query" , "Scan" , "PutItem" , "Invoke" , "ListFunctions"
104+ };
105+ Random random = new Random ();
106+
107+ while (running .get () && !Thread .currentThread ().isInterrupted ()) {
108+ try {
109+ String service = services [random .nextInt (services .length )];
110+ String operation = operations [random .nextInt (operations .length )];
111+
112+ switch (service ) {
113+ case "s3" -> makeRequest (clientId , s3Limiter , service , operation );
114+ case "dynamodb" -> makeRequest (clientId , dynamoDbLimiter , service , operation );
115+ case "lambda" -> makeRequest (clientId , lambdaLimiter , service , operation );
116+ }
117+
118+ Thread .sleep (30 + random .nextInt (50 ));
119+ } catch (InterruptedException e ) {
120+ Thread .currentThread ().interrupt ();
121+ }
122+ }
123+ };
124+ }
125+
126+ private static void makeRequest (int clientId , RateLimiter limiter ,
127+ String service , String operation ) {
128+ try {
129+ limiter .check (service , operation );
130+ successfulRequests .incrementAndGet ();
131+ System .out .printf ("Client %d: %s.%s - ALLOWED%n" , clientId , service , operation );
132+ } catch (ThrottlingException e ) {
133+ throttledRequests .incrementAndGet ();
134+ System .out .printf ("Client %d: %s.%s - THROTTLED (Retry in %dms)%n" ,
135+ clientId , service , operation , e .getRetryAfterMillis ());
136+ } catch (ServiceUnavailableException e ) {
137+ failedRequests .incrementAndGet ();
138+ System .out .printf ("Client %d: %s.%s - SERVICE UNAVAILABLE%n" ,
139+ clientId , service , operation );
140+ } catch (Exception e ) {
141+ failedRequests .incrementAndGet ();
142+ System .out .printf ("Client %d: %s.%s - ERROR: %s%n" ,
143+ clientId , service , operation , e .getMessage ());
144+ }
145+ }
146+
147+ private static void printStats () {
148+ if (!running .get ()) return ;
149+ System .out .println ("\n === Current Statistics ===" );
150+ System .out .printf ("Successful Requests: %d%n" , successfulRequests .get ());
151+ System .out .printf ("Throttled Requests : %d%n" , throttledRequests .get ());
152+ System .out .printf ("Failed Requests : %d%n" , failedRequests .get ());
153+ System .out .println ("==========================\n " );
154+ }
155+
156+ private static void printFinalStats () {
157+ System .out .println ("\n Final Statistics" );
158+ System .out .println ("==========================" );
159+ System .out .printf ("Successful Requests: %d%n" , successfulRequests .get ());
160+ System .out .printf ("Throttled Requests : %d%n" , throttledRequests .get ());
161+ System .out .printf ("Failed Requests : %d%n" , failedRequests .get ());
162+ System .out .println ("==========================" );
163+ }
164+ }
0 commit comments