All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsimpledb.kv.raft.RaftKVImplementation Maven / Gradle / Ivy

Go to download

JSimpleDB distributed, linearizable ACID compliant key/value store implementation based on the Raft consensus algorithm.

There is a newer version: 3.6.1
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package org.jsimpledb.kv.raft;

import java.io.File;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayDeque;

import org.dellroad.stuff.net.TCPNetwork;
import org.jsimpledb.kv.KVDatabase;
import org.jsimpledb.kv.KVImplementation;
import org.jsimpledb.kv.mvcc.AtomicKVStore;
import org.jsimpledb.kv.raft.fallback.FallbackKVDatabase;
import org.jsimpledb.kv.raft.fallback.FallbackTarget;
import org.jsimpledb.kv.raft.fallback.MergeStrategy;
import org.jsimpledb.kv.raft.fallback.NullMergeStrategy;
import org.jsimpledb.kv.raft.fallback.OverwriteMergeStrategy;

public class RaftKVImplementation extends KVImplementation {

    @Override
    public String[][] getCommandLineOptions() {
        return new String[][] {
            { "--raft directory",
              "Use Raft key/value database in specified directory" },
            { "--raft-min-election-timeout",
              "Specify Raft minimum election timeout in ms (default " + RaftKVDatabase.DEFAULT_MIN_ELECTION_TIMEOUT + ")" },
            { "--raft-max-election-timeout",
              "Specify Raft maximum election timeout in ms (default " + RaftKVDatabase.DEFAULT_MAX_ELECTION_TIMEOUT + ")" },
            { "--raft-heartbeat-timeout",
              "Specify Raft leader heartbeat timeout in ms (default " + RaftKVDatabase.DEFAULT_HEARTBEAT_TIMEOUT + ")" },
            { "--raft-identity",
              "Specify Raft identity" },
            { "--raft-address address",
              "Specify Specify local Raft node's IP address" },
            { "--raft-port",
              "Specify Specify local Raft node's TCP port (default " + RaftKVDatabase.DEFAULT_TCP_PORT + ")" },
            { "--raft-fallback statefile",
              "Use Raft fallback database with specified state file" },
            { "--raft-fallback-check-interval",
              "Specify Raft fallback check interval in milliseconds (default " + FallbackTarget.DEFAULT_CHECK_INTERVAL + ")" },
            { "--raft-fallback-min-available",
              "Specify Raft fallback min available time in milliseconds (default "
                + FallbackTarget.DEFAULT_MIN_AVAILABLE_TIME + ")" },
            { "--raft-fallback-min-unavailable",
              "Specify Raft fallback min unavailable time in milliseconds (default "
                + FallbackTarget.DEFAULT_MIN_UNAVAILABLE_TIME + ")" },
            { "--raft-fallback-check-timeout",
              "Specify Raft fallback availability check TX timeout in milliseconds (default "
                + FallbackTarget.DEFAULT_TRANSACTION_TIMEOUT + ")" },
            { "--raft-fallback-unavailable-merge",
              "Specify Raft fallback unavailable merge strategy class name (default `"
                + OverwriteMergeStrategy.class.getName() + "')" },
            { "--raft-fallback-rejoin-merge",
              "Specify Raft fallback rejoin merge strategy class name (default `"
                + NullMergeStrategy.class.getName() + "')" },
        };
    }

    @Override
    public String getUsageText() {
        return "Raft requires its own internal key/value store, which should also be specified along with `--raft'.\n"
          + "For Raft fallback, specify `--raft-fallback' in addition.";
    }

    @Override
    public Config parseCommandLineOptions(ArrayDeque options) {

        // Raft options
        String arg = this.parseCommandLineOption(options, "--raft");
        if (arg == null)
            return null;
        final Config config = new Config(new File(arg));
        if ((arg = this.parseCommandLineOption(options, "--raft-identity")) != null)
            config.getRaft().setIdentity(arg);
        if ((arg = this.parseCommandLineOption(options, "--raft-address")) != null) {
            config.setAddress(TCPNetwork.parseAddressPart(arg));
            config.setPort(TCPNetwork.parsePortPart(arg, config.getPort()));
        }
        if ((arg = this.parseCommandLineOption(options, "--raft-port")) != null) {
            final int port = TCPNetwork.parsePortPart("x:" + arg, -1);
            if (port == -1)
                throw new IllegalArgumentException("invalid TCP port `" + arg + "'");
            config.setPort(port);
        }
        int value;
        if ((value = this.parseMillisecondsOption(options, "min-election-timeout")) != -1)
            config.getRaft().setMinElectionTimeout(value);
        if ((value = this.parseMillisecondsOption(options, "max-election-timeout")) != -1)
            config.getRaft().setMaxElectionTimeout(value);
        if ((value = this.parseMillisecondsOption(options, "heartbeat-timeout")) != -1)
            config.getRaft().setHeartbeatTimeout(value);
        if ((value = this.parseMillisecondsOption(options, "fallback-check-interval")) != -1)
            config.getFallbackTarget().setCheckInterval(value);
        if ((value = this.parseMillisecondsOption(options, "fallback-check-timeout")) != -1)
            config.getFallbackTarget().setTransactionTimeout(value);
        if ((value = this.parseMillisecondsOption(options, "fallback-min-available")) != -1)
            config.getFallbackTarget().setMinAvailableTime(value);
        if ((value = this.parseMillisecondsOption(options, "fallback-min-unavailable")) != -1)
            config.getFallbackTarget().setMinUnavailableTime(value);

        // Raft fallback options
        if ((arg = this.parseCommandLineOption(options, "--raft-fallback")) != null) {
            final File stateFile = new File(arg);
            if (stateFile.exists() && !stateFile.isFile())
                throw new IllegalArgumentException("file `" + arg + "' is not a regular file");
            config.getFallback().setStateFile(stateFile);
        }
        MergeStrategy mergeStrategy;
        if ((mergeStrategy = this.parseMergeStrategy(options, "unavailable")) != null)
            config.getFallbackTarget().setUnavailableMergeStrategy(mergeStrategy);
        if ((mergeStrategy = this.parseMergeStrategy(options, "rejoin")) != null)
            config.getFallbackTarget().setRejoinMergeStrategy(mergeStrategy);

        // Done
        return config;
    }

    private MergeStrategy parseMergeStrategy(ArrayDeque options, String name) {
        final String className = this.parseCommandLineOption(options, "--raft-fallback-" + name + "-merge");
        if (className == null)
            return null;
        try {
            return (MergeStrategy)Class.forName(className, false, Thread.currentThread().getContextClassLoader()).newInstance();
        } catch (Exception e) {
            throw new IllegalArgumentException("invalid Raft fallback merge strategy `" + className + "': " + e.getMessage(), e);
        }
    }

    private int parseMillisecondsOption(ArrayDeque options, String name) {
        final String arg = this.parseCommandLineOption(options, "--raft-" + name);
        if (arg == null)
            return -1;
        try {
            final int value = Integer.parseInt(arg, 10);
            if (value < 0)
                throw new NumberFormatException("value cannot be negative");
            return value;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("invalid milliseconds value `"
              + arg + "' for `--raft-" + name + "': " + e.getMessage(), e);
        }
    }

    @Override
    public KVDatabase createKVDatabase(Object configuration, KVDatabase kvdb, AtomicKVStore kvstore) {
        final Config config = (Config)configuration;
        final RaftKVDatabase raft = config.configureRaft(kvstore);
        return config.isFallback() ? config.configureFallback(kvdb) : raft;
    }

    @Override
    public boolean requiresAtomicKVStore(Object configuration) {
        return true;
    }

    @Override
    public boolean requiresKVDatabase(Object configuration) {
        return ((Config)configuration).isFallback();
    }

    @Override
    public String getDescription(Object configuration) {
        final Config config = (Config)configuration;
        return "Raft " + config.getRaft().getLogDirectory().getName() + (config.isFallback() ? "/Fallback" : "");
    }

// Config

    private static class Config {

        private final RaftKVDatabase raft = new RaftKVDatabase();
        private final FallbackTarget fallbackTarget = new FallbackTarget();
        private final FallbackKVDatabase fallback = new FallbackKVDatabase();

        private String address;
        private int port = RaftKVDatabase.DEFAULT_TCP_PORT;

        Config(File dir) {
            if (dir == null)
                throw new IllegalArgumentException("null dir");
            this.raft.setLogDirectory(dir);
            this.fallbackTarget.setRaftKVDatabase(this.raft);
            this.fallback.setFallbackTarget(this.fallbackTarget);
        }

        public RaftKVDatabase getRaft() {
            return this.raft;
        }

        public FallbackKVDatabase getFallback() {
            return this.fallback;
        }

        public FallbackTarget getFallbackTarget() {
            return this.fallbackTarget;
        }

        public boolean isFallback() {
            return this.fallback.getStateFile() != null;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public int getPort() {
            return this.port;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public RaftKVDatabase configureRaft(AtomicKVStore kvstore) {
            this.raft.setKVStore(kvstore);
            final TCPNetwork network = new TCPNetwork(RaftKVDatabase.DEFAULT_TCP_PORT);
            try {
                network.setListenAddress(this.address != null ?
                  new InetSocketAddress(InetAddress.getByName(this.address), this.port) : new InetSocketAddress(this.port));
            } catch (UnknownHostException e) {
                throw new RuntimeException("can't resolve local Raft address `" + this.address + "'", e);
            }
            this.raft.setNetwork(network);
            return this.raft;
        }

        public FallbackKVDatabase configureFallback(KVDatabase standaloneKV) {
            this.fallback.setStandaloneTarget(standaloneKV);
            return this.fallback;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy