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