A robust, multi-node distributed caching system built in Java. This project demonstrates advanced concurrent programming concepts, modular enterprise architecture (SOLID principles), and fault-tolerant system design.
- Distributed Node Architecture: Keys are deterministically routed to specific cache nodes using a Modulo Routing strategy, ensuring even data distribution.
- Thundering Herd Prevention (Request Collapsing): If thousands of threads request a missing key simultaneously, the
RequestCollapserensures only one database query is executed, safely sharing the result with all waiting threads. - Asynchronous Prefetching: Uses a background
Prefetcherthread pool to predict and load future keys (e.g., fetching keyN+1whenNis requested) without blocking the main application thread. - Pluggable LRU Eviction: Each cache node manages its own memory capacity using a custom Least Recently Used (LRU) algorithm, integrated via an Observer pattern to safely decouple storage from eviction logic.
- Thread-Safe Dynamic Scaling: Nodes can be safely added or removed at runtime. The system uses concurrent data structures (
CopyOnWriteArrayList,ConcurrentHashMap) to allow dynamic scaling and self-healing routing without crashing active read/write requests.
The codebase strictly follows the Single Responsibility Principle and is divided into logical domains:
src/
├── collapser/
│ └── RequestCollapser.java # Collapses concurrent DB requests via CompletableFuture
├── data/
│ ├── CacheEntry.java # Immutable data object holding value and TTL
│ ├── CacheNode.java # Represents a single server/node in the cluster
│ └── StorageEngine.java # Thread-safe map manager; reacts to eviction events
├── eviction/
│ ├── IEvictionObserver.java # Observer interface for eviction callbacks
│ ├── IEvictionStrategy.java # Base interface for custom eviction algorithms
│ └── LRUStrategy.java # Custom Doubly-Linked-List + HashMap implementation
├── manager/
│ └── CacheManager.java # The core orchestrator and context provider
├── prefetch/
│ ├── IPrefetchContext.java # Interface hiding main cache logic from background threads
│ ├── IPrefetchStrategy.java # Interface for key prediction logic
│ ├── LinearPrefetchStrategy.java # Predicts the next 'N' sequential keys
│ └── Prefetcher.java # Runs async background fetches via ExecutorService
└── routing/
├── IRouter.java # Interface for node selection logic
└── ModuloRouter.java # Selects nodes using (key % nodeCount)
This diagram showcases the complete system architecture, emphasizing Dependency Inversion. The CacheManager orchestrates the system by depending on abstractions (IRouter, IPrefetchContext) rather than concrete implementations, preventing circular dependencies.
classDiagram
%% Core Management
class CacheManager {
-List~CacheNode~ nodes
-IRouter router
-RequestCollapser collapser
-Prefetcher prefetcher
-int timeToLive
+CacheManager(IRouter, RequestCollapser, Prefetcher, int)
+addNode(CacheNode node) void
+removeNode(CacheNode node) void
+get(int key) String
+put(int key, String value) void
-prefetchAsync(int key) void
}
class IPrefetchContext {
<<interface>>
+isPresent(int key) boolean
+fetch(int key) String
+putInCache(int key, String data) void
}
%% Orchestration Tools
class RequestCollapser {
-ConcurrentHashMap~Integer, CompletableFuture~String~~ map
+RequestCollapser()
+execute(int key, IPrefetchContext context) String
}
class Prefetcher {
-IPrefetchStrategy strategy
-ExecutorService executor
+Prefetcher(IPrefetchStrategy strategy)
+run(int currentKey, IPrefetchContext context) void
+shutdown() void
}
class IPrefetchStrategy {
<<interface>>
+getNextKeys(int currentKey) List~Integer~
}
class LinearPrefetchStrategy {
-int nextKeys
+LinearPrefetchStrategy(int nextKeys)
+getNextKeys(int currentKey) List~Integer~
}
%% Routing
class IRouter {
<<interface>>
+getTargetNode(int key, List~CacheNode~ nodes) CacheNode
}
class ModuloRouter {
+getTargetNode(int key, List~CacheNode~ nodes) CacheNode
}
%% Data Storage Layer
class CacheNode {
-String id
-StorageEngine engine
+CacheNode(String id, int capacity, IEvictionStrategy strategy)
+getId() String
+get(int key) CacheEntry
+put(int key, String value, int timeToLive) void
+isPresent(int key) boolean
}
class StorageEngine {
-Map~Integer, CacheEntry~ data
-int capacity
-IEvictionStrategy strategy
+create(int capacity, IEvictionStrategy strategy)$ StorageEngine
+set(int key, String value, int timeToLive) void
+get(int key) CacheEntry
+remove(int key) void
+isPresent(int key) boolean
+onEvict(int key) void
}
class CacheEntry {
-String value
-int expiryTime
+CacheEntry(String value, int timeToLive)
+getValue() String
+isExpired() boolean
}
%% Eviction Layer
class IEvictionStrategy {
<<interface>>
+addObserver(IEvictionObserver observer) void
+record(int key) void
+removeKey(int key) void
+evict() void
}
class IEvictionObserver {
<<interface>>
+onEvict(int key) void
}
class LRUStrategy {
-Node head
-Node tail
-Map~Integer, Node~ map
-List~IEvictionObserver~ observers
+LRUStrategy()
}
%% Relationships
CacheManager ..|> IPrefetchContext : Implements
CacheManager --> IRouter : Uses
CacheManager --> RequestCollapser : Uses
CacheManager --> Prefetcher : Triggers
CacheManager o-- CacheNode : Manages Cluster
ModuloRouter ..|> IRouter : Implements
LinearPrefetchStrategy ..|> IPrefetchStrategy : Implements
Prefetcher o-- IPrefetchStrategy : Has-A
Prefetcher --> IPrefetchContext : Calls back to
RequestCollapser --> IPrefetchContext : Calls back to
CacheNode *-- StorageEngine : Owns
StorageEngine *-- CacheEntry : Stores
StorageEngine ..|> IEvictionObserver : Implements
StorageEngine --> IEvictionStrategy : Delegates to
IEvictionStrategy --> IEvictionObserver : Notifies
LRUStrategy ..|> IEvictionStrategy : Implements
- Java 11 or higher
- Git
1. Clone the repository
git clone https://github.com/aryen1101/Distributed_Cache.git2. Navigate into the project folder
cd Distributed_Cache3. Create an output folder and compile
mkdir out
javac -d out -sourcepath src src/Main.java4. Run the application
java -cp out Main| Principle | Application |
|---|---|
| Single Responsibility | Each class owns exactly one concern (routing, eviction, collapsing, etc.) |
| Dependency Inversion | CacheManager depends on IRouter and IPrefetchContext, not concrete classes |
| Open/Closed | New eviction strategies or routing algorithms can be plugged in without modifying existing code |
| Observer Pattern | StorageEngine reacts to eviction events via IEvictionObserver without tight coupling to LRUStrategy |