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