Database Failover Simulator Java
👤 Sharing: AI
```java
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class DatabaseFailoverSimulator {
// Simulates a database server
static class DatabaseServer implements Runnable {
private final String serverName;
private final AtomicBoolean isPrimary; // Indicates if this server is the primary. Atomic for thread safety.
private final AtomicBoolean isHealthy; // Indicates if the server is healthy (running). Atomic for thread safety.
private final AtomicInteger requestCount; // Counts processed requests. Atomic for thread safety.
private final int failureProbability; // Percentage chance the server fails in a given time unit
private final DatabaseFailoverSimulator simulator; // Reference to the simulator
public DatabaseServer(String serverName, boolean isPrimary, int failureProbability, DatabaseFailoverSimulator simulator) {
this.serverName = serverName;
this.isPrimary = new AtomicBoolean(isPrimary);
this.isHealthy = new AtomicBoolean(true);
this.requestCount = new AtomicInteger(0);
this.failureProbability = failureProbability;
this.simulator = simulator;
}
public String getServerName() {
return serverName;
}
public boolean isPrimary() {
return isPrimary.get();
}
public void setPrimary(boolean primary) {
isPrimary.set(primary);
}
public boolean isHealthy() {
return isHealthy.get();
}
public void setHealthy(boolean healthy) {
isHealthy.set(healthy);
}
public int getRequestCount() {
return requestCount.get();
}
@Override
public void run() {
Random random = new Random();
while (true) { // Simulate continuous operation
try {
Thread.sleep(100); // Simulate processing time
if (!isHealthy.get()) {
// Server is down, don't process requests
continue;
}
// Simulate a request being processed
if (isPrimary.get()) {
requestCount.incrementAndGet();
System.out.println(serverName + " (Primary): Processed request. Total: " + requestCount.get());
} else {
System.out.println(serverName + " (Secondary): Idle, ready for failover.");
}
// Simulate possible failure
if (random.nextInt(100) < failureProbability) {
System.out.println(serverName + ": Simulating failure!");
setHealthy(false); // Mark the server as unhealthy
simulator.handleServerFailure(this); // Notify the simulator of the failure
break; // Stop running after failure. Important.
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupted state
return; // Exit the thread
}
}
}
}
private final DatabaseServer primaryServer;
private final DatabaseServer secondaryServer;
private final ExecutorService executor; // For running the servers concurrently.
private final int failoverTime; // Time it takes to perform failover in milliseconds
private final AtomicBoolean failoverInProgress = new AtomicBoolean(false); // Ensures only one failover occurs at a time.
public DatabaseFailoverSimulator(int failureProbabilityPrimary, int failureProbabilitySecondary, int failoverTime) {
primaryServer = new DatabaseServer("PrimaryDB", true, failureProbabilityPrimary, this);
secondaryServer = new DatabaseServer("SecondaryDB", false, failureProbabilitySecondary, this);
executor = Executors.newFixedThreadPool(2); // Two threads for the two servers
this.failoverTime = failoverTime;
}
public void startSimulation() {
System.out.println("Starting database failover simulation...");
executor.submit(primaryServer);
executor.submit(secondaryServer);
}
public void stopSimulation() {
System.out.println("Stopping database failover simulation...");
executor.shutdownNow();
try {
executor.awaitTermination(5, TimeUnit.SECONDS); // Give threads time to stop.
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// Called by a DatabaseServer when it fails. This is synchronized to prevent race conditions in failover.
public synchronized void handleServerFailure(DatabaseServer failedServer) {
if (failoverInProgress.get()) {
System.out.println("Failover already in progress. Ignoring " + failedServer.getServerName() + " failure.");
return; // Another failover is already running.
}
if (failedServer == primaryServer && primaryServer.isHealthy()) {
System.out.println("Ignoring failure report from the primary server, as it is still marked healthy.");
return;
}
if (failedServer == secondaryServer && secondaryServer.isHealthy()) {
System.out.println("Ignoring failure report from the secondary server, as it is still marked healthy.");
return;
}
failoverInProgress.set(true);
System.out.println("Initiating failover from " + primaryServer.getServerName() + " to " + secondaryServer.getServerName());
// Simulate failover time
try {
Thread.sleep(failoverTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// Perform failover: Make the secondary the new primary
secondaryServer.setPrimary(true);
secondaryServer.setHealthy(true); // Ensure the new primary is marked as healthy.
primaryServer.setPrimary(false);
System.out.println("Failover complete. " + secondaryServer.getServerName() + " is now the primary.");
failoverInProgress.set(false); // Reset failover flag.
System.out.println("Resuming " + secondaryServer.getServerName() + " after failover...");
executor.submit(secondaryServer); // Restart the secondary server thread
}
public static void main(String[] args) {
// Configure the simulation
int primaryFailureProbability = 5; // 5% chance of failure per simulated tick
int secondaryFailureProbability = 1; // 1% chance of failure per simulated tick
int failoverTime = 2000; // 2 seconds failover time
DatabaseFailoverSimulator simulator = new DatabaseFailoverSimulator(primaryFailureProbability, secondaryFailureProbability, failoverTime);
simulator.startSimulation();
// Let the simulation run for a while (e.g., 20 seconds)
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
simulator.stopSimulation();
}
}
```
Key improvements and explanations:
* **Clearer Structure:** The code is structured into classes representing the key components: `DatabaseServer` and `DatabaseFailoverSimulator`. This makes the code easier to understand and maintain.
* **Thread Safety:** Uses `AtomicBoolean` and `AtomicInteger` for `isPrimary`, `isHealthy`, `requestCount` and `failoverInProgress`. This is *critical* to avoid race conditions when multiple threads are accessing and modifying these variables. Failover logic is synchronized using `synchronized` keyword.
* **Failure Simulation:** The `DatabaseServer` now has a `failureProbability` to simulate random server failures. This makes the simulation more realistic.
* **Failover Logic:** The `handleServerFailure` method in `DatabaseFailoverSimulator` encapsulates the failover process:
* It checks if a failover is already in progress and avoids starting a new one.
* It simulates the time taken for failover to occur.
* It updates the `isPrimary` flag of the servers to reflect the failover.
* **Crucially**: Restarts the secondary server after failover using `executor.submit()`. This ensures that the secondary server continues to function as the new primary.
* **Concurrency:** Uses `ExecutorService` to run the primary and secondary servers in separate threads, simulating a real-world environment. The `executor.shutdownNow()` and `executor.awaitTermination()` ensure proper shutdown of the threads.
* **Realistic Simulation:** Added a `Thread.sleep()` call within the `DatabaseServer.run()` loop to simulate processing time.
* **Error Handling:** Includes basic error handling (e.g., catching `InterruptedException` and setting the interrupted flag).
* **Clearer Output:** The code includes `System.out.println` statements to provide feedback on the simulation's progress.
* **Configuration:** The `main` method allows you to configure the simulation parameters (failure probabilities, failover time).
* **Prevention of double failover triggering**: Checks that failed server is still healthy before processing a failover.
How to run:
1. **Save:** Save the code as `DatabaseFailoverSimulator.java`.
2. **Compile:** Open a terminal or command prompt and compile the code using the Java compiler:
```bash
javac DatabaseFailoverSimulator.java
```
3. **Run:** Execute the compiled code:
```bash
java DatabaseFailoverSimulator
```
This will run the simulation and print output to the console, showing the database servers processing requests and the failover occurring when the primary server fails. The simulation will run for 20 seconds before stopping.
This improved version provides a more robust, realistic, and understandable simulation of a database failover scenario. It addresses the critical issues of thread safety and proper handling of server failures and restarts. It's also structured in a way that makes it easier to extend and modify the simulation.
👁️ Viewed: 7
Comments