A multi-threaded in-memory key-value store inspired by Redis, built in Java with concurrent client support, persistence, and key expiration. Handles 50+ simultaneous connections with zero blocking.
- GET/SET/DEL Commands - Basic key-value operations
- Key Expiration (TTL) - Automatic removal of expired keys using EX parameter
- Multi-Threaded Concurrency - ExecutorService thread pool for 50+ simultaneous clients
- Thread-Safe Storage - ConcurrentHashMap for atomic operations without locks
- Persistence (AOF) - Append-only file for crash recovery and durability
- Concurrent Command Processing - Each client executes independently in worker threads
- TCP Server - Listens on port 6379 (default Redis port)
- Text-Based Protocol - Simple command parsing (space-separated arguments)
- Connection Management - Graceful client disconnection handling
- Memory Efficient - Expired keys are lazily removed during access
- Concurrent Command Processing - Each client runs in its own thread
- Atomic Operations - Using Java's ConcurrentHashMap for thread safety
src/main/java/com/osama/redisclone/
βββ Main.java # Application entry point
βββ command/
β βββ CommandProcessor.java # Command parsing and execution
β βββ ParsedCommand.java # Parsed command representation
βββ model/
β βββ Entry.java # Key-value entry with expiration
βββ persistence/
β βββ PersistenceManager.java # AOF file management
βββ server/
β βββ RedisServer.java # TCP server with thread pool
βββ store/
βββ InMemoryStore.java # Thread-safe in-memory storage
ExecutorService clientPool = Executors.newCachedThreadPool();
while (true) {
Socket clientSocket = serverSocket.accept(); // Main thread
clientPool.submit(() -> handleClient(clientSocket)); // Worker thread
}- Main thread accepts new connections in a loop
- Worker threads from the pool handle each client independently
newCachedThreadPool()dynamically creates/reuses threads as needed- While one thread reads from Client A, another reads from Client B
- Result: True parallelism - multiple clients served simultaneously
- Scalability: Tested with 50 concurrent clients, all executing in parallel
- Commands are logged to
redis-clone.aoffor durability - On startup, all persisted commands are replayed to restore state
- Non-persistence variant (
processWithoutPersistence) used during replay to avoid duplicate logging - Ensures data survives server crashes
if (entry.isExpired()) {
data.remove(key, entry);
return null;
}- Keys are checked for expiration during access (lazy deletion)
- No background cleanup thread needed
- Memory efficient and avoids thundering herd problem
ConcurrentHashMapinstead ofHashMapfor thread safety- No explicit synchronization needed for concurrent operations
- Atomic operations maintain consistency across threads
- Language: Java 17
- Build Tool: Maven 3.6+
- Testing: JUnit 5
- Concurrency: Java ExecutorService Thread Pool
- Java 17 or higher
- Maven 3.6 or higher
mvn clean compilemvn exec:java -Dexec.mainClass="com.osama.redisclone.Main"The server will start on port 6379 and automatically replay any persisted commands from redis-clone.aof.
# In another terminal
nc localhost 6379
# Try these commands:
SET mykey "Hello, Redis!"
GET mykey
DEL mykey
GET mykey
SET counter 5 EX 10
GET counter
EXITExecute all unit tests:
mvn test- CommandProcessorTest - Tests command parsing and execution
- InMemoryStoreTest - Tests concurrent access and expiration
| Command | Syntax | Description |
|---|---|---|
| SET | SET key value [EX seconds] |
Set a key with optional TTL |
| GET | GET key |
Retrieve value for a key |
| DEL | DEL key |
Delete a key (returns 1 if deleted, 0 if not found) |
| EXIT | EXIT |
Close client connection |
SET name osama # Set a key
GET name # Get the value
SET temp "temporary" EX 60 # Set with 60-second expiration
DEL name # Delete the key
GET name # Returns (nil) - key was deleted
The append-only file (redis-clone.aof) stores all write commands. On server restart:
- All persisted commands are read from the file
- Commands are replayed in order
- Server state is restored exactly as it was before shutdown
This ensures no data loss in case of unexpected crashes.
| Operation | Complexity | Notes |
|---|---|---|
| GET | O(1) | HashMap lookup, with expiry check |
| SET | O(1) | HashMap insertion |
| DEL | O(1) | HashMap removal |
| Expiry Check | O(1) | Performed during key access |
| Concurrent Requests | O(1) | Each client in separate thread, no queuing |
- 10 clients Γ 20 commands each: 210 responses in ~100ms (parallel)
- 50 clients Γ 10 commands each: 500 commands in ~100ms (parallel)
- No bottlenecks: Thread pool scales dynamically with demand
- Zero waiting: While one client waits for I/O, others execute
Client 1 ββ
Client 2 ββΌββ Main Thread Accept ββ Thread Pool ββ Worker Thread 1
Client 3 ββ€ (Dispatcher) β Worker Thread 2
Client 4 ββ β Worker Thread 3
β Worker Thread N
- ConcurrentHashMap ensures atomic read/write operations from multiple threads
- ExecutorService provides proper thread pool management and lifecycle
- No race conditions:
- All store operations are atomic via ConcurrentHashMap
- Each client runs independently in its own thread
- Expiration checks are performed atomically during access
- No shared mutable state across threads
- No deadlocks: No locks used; all operations are lock-free or atomic
- Data consistency: Commands execute in the order received per client
This project demonstrates:
- Concurrent Programming: Thread pools and ExecutorService
- Data Structures: HashMap vs ConcurrentHashMap
- Networking: TCP servers and socket programming
- File I/O: Persistence and data recovery
- Clean Architecture: Separation of concerns across modules
- Software Design: Factory pattern for command processing
Potential features for future versions:
- INCR/DECR - Atomic counters
- KEYS - Pattern-based key search
- TTL - Query remaining time-to-live
- FLUSHDB - Clear all data
- INFO - Server statistics
- CONFIG - Configuration management
- RESP Protocol - Redis Serialization Protocol for compatibility
- Pub/Sub - Message publishing and subscription
- Data Structures - Lists, Sets, Hashes (beyond strings)
- LRU Eviction - Memory limit with eviction policy
Currently, this implementation uses a simplified text protocol. To use with Redis CLI or other clients that expect RESP (Redis Serialization Protocol), the protocol layer would need to be updated.
The current implementation has no built-in memory limits. As an enhancement, LRU (Least Recently Used) eviction could be implemented to manage memory when limits are exceeded.
- Write-only operations (SET, DEL) are persisted
- Read-only operations (GET) are not logged
- AOF entries are in plain text for debugging