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

io.aeron.samples.cluster.ClusterConfig Maven / Gradle / Ivy

There is a newer version: 1.46.7
Show newest version
/*
 * Copyright 2014-2024 Real Logic Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.aeron.samples.cluster;

import io.aeron.CommonContext;
import io.aeron.archive.Archive;
import io.aeron.archive.ArchiveThreadingMode;
import io.aeron.archive.client.AeronArchive;
import io.aeron.cluster.ConsensusModule;
import io.aeron.cluster.service.ClusteredService;
import io.aeron.cluster.service.ClusteredServiceContainer;
import io.aeron.driver.MediaDriver;
import io.aeron.driver.MinMulticastFlowControlSupplier;
import io.aeron.driver.ThreadingMode;
import org.agrona.ErrorHandler;
import org.agrona.concurrent.NoOpLock;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * Wrapper class to simplify cluster configuration. This is sample code and is intended to show a mechanism for
 * managing the configuration of the components required for Aeron Cluster. This code may change between versions
 * and API compatibility is not guaranteed.
 */
public final class ClusterConfig
{
    /**
     * Number of ports per node reserved.
     */
    public static final int PORTS_PER_NODE = 100;

    /**
     * Offset from base port that the archive control channel is on.
     */
    public static final int ARCHIVE_CONTROL_PORT_OFFSET = 1;

    /**
     * Offset from base port that the client facing port is on for ingress.
     */
    public static final int CLIENT_FACING_PORT_OFFSET = 2;

    /**
     * Offset from base port that the member listens on for consensus traffic.
     */
    public static final int MEMBER_FACING_PORT_OFFSET = 3;

    /**
     * Offset from base port that the cluster log is on.
     */
    public static final int LOG_PORT_OFFSET = 4;

    /**
     * Offset from base port that the transfer of files is on.
     */
    public static final int TRANSFER_PORT_OFFSET = 5;

    /**
     * Subdirectory into which archive files are stored.
     */
    public static final String ARCHIVE_SUB_DIR = "archive";

    /**
     * Subdirectory into which cluster files are stored.
     */
    public static final String CLUSTER_SUB_DIR = "cluster";

    private final int memberId;
    private final String ingressHostname;
    private final String clusterHostname;
    private final MediaDriver.Context mediaDriverContext;
    private final Archive.Context archiveContext;
    private final AeronArchive.Context aeronArchiveContext;
    private final ConsensusModule.Context consensusModuleContext;
    private final List clusteredServiceContexts;

    ClusterConfig(
        final int memberId,
        final String ingressHostname,
        final String clusterHostname,
        final MediaDriver.Context mediaDriverContext,
        final Archive.Context archiveContext,
        final AeronArchive.Context aeronArchiveContext,
        final ConsensusModule.Context consensusModuleContext,
        final List clusteredServiceContexts)
    {
        this.memberId = memberId;
        this.ingressHostname = ingressHostname;
        this.clusterHostname = clusterHostname;
        this.mediaDriverContext = mediaDriverContext;
        this.archiveContext = archiveContext;
        this.aeronArchiveContext = aeronArchiveContext;
        this.consensusModuleContext = consensusModuleContext;
        this.clusteredServiceContexts = clusteredServiceContexts;
    }

    /**
     * Create a new ClusterConfig. This call allows for 2 separate lists of hostnames, so that there can be 'external'
     * addresses for ingress requests and 'internal' addresses that will handle all the cluster replication and
     * control traffic.
     *
     * @param startingMemberId   id for the first member in the list of entries.
     * @param memberId           id for this node.
     * @param ingressHostnames   list of hostnames that will receive ingress request traffic.
     * @param clusterHostnames   list of hostnames that will receive cluster traffic.
     * @param portBase           base port to derive remaining ports from.
     * @param parentDir          directory under which the persistent directories will be created.
     * @param clusteredService   instance of the clustered service that will run with this configuration.
     * @param additionalServices instances of additional clustered services that will run with this configuration.
     * @return configuration that wraps all aeron service configuration.
     */
    public static ClusterConfig create(
        final int startingMemberId,
        final int memberId,
        final List ingressHostnames,
        final List clusterHostnames,
        final int portBase,
        final File parentDir,
        final ClusteredService clusteredService,
        final ClusteredService... additionalServices)
    {
        if (memberId < startingMemberId || (startingMemberId + ingressHostnames.size()) <= memberId)
        {
            throw new IllegalArgumentException(
                "memberId=" + memberId + " is invalid, should be " + startingMemberId +
                " <= memberId < " + startingMemberId + ingressHostnames.size());
        }

        final String clusterMembers = clusterMembers(startingMemberId, ingressHostnames, clusterHostnames, portBase);

        final String aeronDirName = CommonContext.getAeronDirectoryName() + "-" + memberId + "-driver";
        final File baseDir = new File(parentDir, "aeron-cluster-" + memberId);

        final String ingressHostname = ingressHostnames.get(memberId - startingMemberId);
        final String hostname = clusterHostnames.get(memberId - startingMemberId);

        final MediaDriver.Context mediaDriverContext = new MediaDriver.Context()
            .aeronDirectoryName(aeronDirName)
            .threadingMode(ThreadingMode.SHARED)
            .termBufferSparseFile(true)
            .multicastFlowControlSupplier(new MinMulticastFlowControlSupplier());

        final AeronArchive.Context replicationArchiveContext = new AeronArchive.Context()
            .controlResponseChannel("aeron:udp?endpoint=" + hostname + ":0");

        final Archive.Context archiveContext = new Archive.Context()
            .aeronDirectoryName(aeronDirName)
            .archiveDir(new File(baseDir, ARCHIVE_SUB_DIR))
            .controlChannel(udpChannel(memberId, hostname, portBase, ARCHIVE_CONTROL_PORT_OFFSET))
            .archiveClientContext(replicationArchiveContext)
            .localControlChannel("aeron:ipc?term-length=64k")
            .replicationChannel("aeron:udp?endpoint=" + hostname + ":0")
            .recordingEventsEnabled(false)
            .threadingMode(ArchiveThreadingMode.SHARED);

        final AeronArchive.Context aeronArchiveContext = new AeronArchive.Context()
            .lock(NoOpLock.INSTANCE)
            .controlRequestChannel(archiveContext.localControlChannel())
            .controlRequestStreamId(archiveContext.localControlStreamId())
            .controlResponseChannel(archiveContext.localControlChannel())
            .aeronDirectoryName(aeronDirName);

        final ConsensusModule.Context consensusModuleContext = new ConsensusModule.Context()
            .clusterMemberId(memberId)
            .clusterMembers(clusterMembers)
            .clusterDir(new File(baseDir, CLUSTER_SUB_DIR))
            .archiveContext(aeronArchiveContext.clone())
            .serviceCount(1 + additionalServices.length)
            .replicationChannel("aeron:udp?endpoint=" + hostname + ":0");

        final List serviceContexts = new ArrayList<>();

        final ClusteredServiceContainer.Context clusteredServiceContext = new ClusteredServiceContainer.Context()
            .aeronDirectoryName(aeronDirName)
            .archiveContext(aeronArchiveContext.clone())
            .clusterDir(new File(baseDir, CLUSTER_SUB_DIR))
            .clusteredService(clusteredService)
            .serviceId(0);
        serviceContexts.add(clusteredServiceContext);

        for (int i = 0; i < additionalServices.length; i++)
        {
            final ClusteredServiceContainer.Context additionalServiceContext = new ClusteredServiceContainer.Context()
                .aeronDirectoryName(aeronDirName)
                .archiveContext(aeronArchiveContext.clone())
                .clusterDir(new File(baseDir, CLUSTER_SUB_DIR))
                .clusteredService(additionalServices[i])
                .serviceId(i + 1);
            serviceContexts.add(additionalServiceContext);
        }

        return new ClusterConfig(
            memberId,
            ingressHostname,
            hostname,
            mediaDriverContext,
            archiveContext,
            aeronArchiveContext,
            consensusModuleContext,
            serviceContexts);
    }

    /**
     * Create a new ClusterConfig. This call allows for 2 separate lists of hostnames, so that there can be 'external'
     * addresses for ingress requests and 'internal' addresses that will handle all the cluster replication and
     * control traffic.
     *
     * @param nodeId             id for this node.
     * @param ingressHostnames   list of hostnames that will receive ingress request traffic.
     * @param clusterHostnames   list of hostnames that will receive cluster traffic.
     * @param portBase           base port to derive remaining ports from.
     * @param clusteredService   instance of the clustered service that will run with this configuration.
     * @param additionalServices instances of additional clustered services that will run with this configuration.
     * @return configuration that wraps all aeron service configuration.
     */
    public static ClusterConfig create(
        final int nodeId,
        final List ingressHostnames,
        final List clusterHostnames,
        final int portBase,
        final ClusteredService clusteredService,
        final ClusteredService... additionalServices)
    {
        return create(
            0,
            nodeId,
            ingressHostnames,
            clusterHostnames,
            portBase,
            new File(System.getProperty("user.dir")),
            clusteredService,
            additionalServices);
    }

    /**
     * Create a new ClusterConfig. This only supports a single lists of hostnames.
     *
     * @param nodeId           id for this node.
     * @param hostnames        list of hostnames that will receive ingress request and cluster traffic.
     * @param portBase         base port to derive remaining ports from.
     * @param clusteredService instance of the clustered service that will run on this node.
     * @return configuration that wraps the detailed aeron service configuration.
     */
    public static ClusterConfig create(
        final int nodeId,
        final List hostnames,
        final int portBase,
        final ClusteredService clusteredService)
    {
        return create(nodeId, hostnames, hostnames, portBase, clusteredService);
    }

    /**
     * Set the same error handler for all contexts.
     *
     * @param errorHandler to receive errors.
     */
    public void errorHandler(final ErrorHandler errorHandler)
    {
        this.mediaDriverContext.errorHandler(errorHandler);
        this.archiveContext.errorHandler(errorHandler);
        this.aeronArchiveContext.errorHandler(errorHandler);
        this.consensusModuleContext.errorHandler(errorHandler);
        this.clusteredServiceContexts.forEach((ctx) -> ctx.errorHandler(errorHandler));
    }

    /**
     * Set the aeron directory for all configuration contexts.
     *
     * @param aeronDir directory to use for aeron.
     */
    public void aeronDirectoryName(final String aeronDir)
    {
        this.mediaDriverContext.aeronDirectoryName(aeronDir);
        this.archiveContext.aeronDirectoryName(aeronDir);
        this.aeronArchiveContext.aeronDirectoryName(aeronDir);
        this.consensusModuleContext.aeronDirectoryName(aeronDir);
        this.clusteredServiceContexts.forEach(ctx -> ctx.aeronDirectoryName(aeronDir));
    }

    /**
     * Set the base directory for cluster and archive.
     *
     * @param baseDir parent directory to be used for archive and cluster stored data.
     */
    public void baseDir(final File baseDir)
    {
        this.archiveContext.archiveDir(new File(baseDir, ARCHIVE_SUB_DIR));
        this.consensusModuleContext.clusterDir(new File(baseDir, CLUSTER_SUB_DIR));
        this.clusteredServiceContexts.forEach((ctx) -> ctx.clusterDir(new File(baseDir, CLUSTER_SUB_DIR)));
    }

    /**
     * Gets the configuration's media driver context.
     *
     * @return configured {@link io.aeron.driver.MediaDriver.Context}.
     * @see io.aeron.driver.MediaDriver.Context
     */
    public MediaDriver.Context mediaDriverContext()
    {
        return mediaDriverContext;
    }

    /**
     * Gets the configuration's archive context.
     *
     * @return configured {@link io.aeron.archive.Archive.Context}.
     * @see io.aeron.archive.Archive.Context
     */
    public Archive.Context archiveContext()
    {
        return archiveContext;
    }

    /**
     * Gets the configuration's aeron archive context.
     *
     * @return configured {@link io.aeron.archive.Archive.Context}.
     * @see io.aeron.archive.client.AeronArchive.Context
     */
    public AeronArchive.Context aeronArchiveContext()
    {
        return aeronArchiveContext;
    }

    /**
     * Gets the configuration's consensus module context.
     *
     * @return configured {@link io.aeron.cluster.ConsensusModule.Context}.
     * @see io.aeron.cluster.ConsensusModule.Context
     */
    public ConsensusModule.Context consensusModuleContext()
    {
        return consensusModuleContext;
    }

    /**
     * Gets the configuration's clustered service container context.
     *
     * @return configured {@link io.aeron.cluster.service.ClusteredServiceContainer.Context}.
     * @see io.aeron.cluster.service.ClusteredServiceContainer.Context
     */
    public ClusteredServiceContainer.Context clusteredServiceContext()
    {
        return clusteredServiceContexts.get(0);
    }

    /**
     * Gets the configuration's list of clustered service container contexts.
     *
     * @return configured list of {@link io.aeron.cluster.service.ClusteredServiceContainer.Context}.
     * @see io.aeron.cluster.service.ClusteredServiceContainer.Context
     */
    public List clusteredServiceContexts()
    {
        return clusteredServiceContexts;
    }

    /**
     * memberId of this node.
     *
     * @return memberId.
     */
    public int memberId()
    {
        return memberId;
    }

    /**
     * Hostname of this node that will receive ingress traffic.
     *
     * @return ingress hostname.
     */
    public String ingressHostname()
    {
        return ingressHostname;
    }

    /**
     * Hostname of this node that will receive cluster traffic.
     *
     * @return cluster hostname
     */
    public String clusterHostname()
    {
        return clusterHostname;
    }

    /**
     * String representing the cluster members configuration which can be used for
     * {@link io.aeron.cluster.ClusterMember#parse(String)}.
     *
     * @param ingressHostnames of the cluster members.
     * @param clusterHostnames of the cluster members internal address (can be the same as 'hostnames').
     * @param portBase         initial port to derive other port from via appropriate node id and offset.
     * @return the String which can be used for {@link io.aeron.cluster.ClusterMember#parse(String)}.
     */
    public static String clusterMembers(
        final List ingressHostnames, final List clusterHostnames, final int portBase)
    {
        return clusterMembers(0, ingressHostnames, clusterHostnames, portBase);
    }

    /**
     * String representing the cluster members configuration which can be used for
     * {@link io.aeron.cluster.ClusterMember#parse(String)}.
     *
     * @param startingMemberId first memberId to be used in the list of clusterMembers. The memberId will increment by 1
     *                         from that value for each entry.
     * @param ingressHostnames of the cluster members.
     * @param clusterHostnames of the cluster members internal address (can be the same as 'hostnames').
     * @param portBase         initial port to derive other port from via appropriate node id and offset.
     * @return the String which can be used for {@link io.aeron.cluster.ClusterMember#parse(String)}.
     */
    public static String clusterMembers(
        final int startingMemberId,
        final List ingressHostnames,
        final List clusterHostnames,
        final int portBase)
    {
        if (ingressHostnames.size() != clusterHostnames.size())
        {
            throw new IllegalArgumentException("ingressHostnames and clusterHostnames must be the same size");
        }

        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < ingressHostnames.size(); i++)
        {
            final int memberId = i + startingMemberId;
            sb.append(memberId);
            sb.append(',').append(endpoint(memberId, ingressHostnames.get(i), portBase, CLIENT_FACING_PORT_OFFSET));
            sb.append(',').append(endpoint(memberId, clusterHostnames.get(i), portBase, MEMBER_FACING_PORT_OFFSET));
            sb.append(',').append(endpoint(memberId, clusterHostnames.get(i), portBase, LOG_PORT_OFFSET));
            sb.append(',').append(endpoint(memberId, clusterHostnames.get(i), portBase, TRANSFER_PORT_OFFSET));
            sb.append(',').append(endpoint(memberId, clusterHostnames.get(i), portBase, ARCHIVE_CONTROL_PORT_OFFSET));
            sb.append('|');
        }

        return sb.toString();
    }

    /**
     * Ingress endpoints generated from a list of hostnames.
     *
     * @param hostnames              for the cluster members.
     * @param portBase               Base port for the cluster
     * @param clientFacingPortOffset Offset for the client facing port
     * @return a formatted string of ingress endpoints for connecting to a cluster.
     */
    public static String ingressEndpoints(
        final List hostnames,
        final int portBase,
        final int clientFacingPortOffset)
    {
        return ingressEndpoints(0, hostnames, portBase, clientFacingPortOffset);
    }


    /**
     * Ingress endpoints generated from a list of hostnames.
     *
     * @param startingMemberId       first memberId to be used when generating the ports.
     * @param hostnames              for the cluster members.
     * @param portBase               Base port for the cluster
     * @param clientFacingPortOffset Offset for the client facing port
     * @return a formatted string of ingress endpoints for connecting to a cluster.
     */
    public static String ingressEndpoints(
        final int startingMemberId,
        final List hostnames,
        final int portBase,
        final int clientFacingPortOffset)
    {
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < hostnames.size(); i++)
        {
            final int memberId = i + startingMemberId;
            sb.append(memberId).append('=');
            sb.append(hostnames.get(i)).append(':').append(calculatePort(memberId, portBase, clientFacingPortOffset));
            sb.append(',');
        }

        sb.setLength(sb.length() - 1);

        return sb.toString();
    }

    /**
     * Calculates a port for use with a node based on a specific offset.  Can be used with the predefined offsets, e.g.
     * {@link ClusterConfig#ARCHIVE_CONTROL_PORT_OFFSET} or with custom offsets. For custom offsets select a value
     * larger than largest predefined offsets.  A value larger than the largest predefined offset, but less than
     * {@link ClusterConfig#PORTS_PER_NODE} is required.
     *
     * @param nodeId   The id for the member of the cluster.
     * @param portBase The port base to be used.
     * @param offset   The offset to add onto the port base
     * @return a calculated port, which should be unique for the specified criteria.
     */
    public static int calculatePort(final int nodeId, final int portBase, final int offset)
    {
        return portBase + (nodeId * PORTS_PER_NODE) + offset;
    }

    private static String udpChannel(final int nodeId, final String hostname, final int portBase, final int portOffset)
    {
        final int port = calculatePort(nodeId, portBase, portOffset);
        return "aeron:udp?endpoint=" + hostname + ":" + port;
    }

    private static String endpoint(final int nodeId, final String hostname, final int portBase, final int portOffset)
    {
        return hostname + ":" + calculatePort(nodeId, portBase, portOffset);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy