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

org.mariadb.r2dbc.HaMode Maven / Gradle / Ivy

The newest version!
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2020-2024 MariaDB Corporation Ab

package org.mariadb.r2dbc;

import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.mariadb.r2dbc.client.Client;
import org.mariadb.r2dbc.client.SimpleClient;
import org.mariadb.r2dbc.message.flow.AuthenticationFlow;
import org.mariadb.r2dbc.util.HostAddress;
import reactor.core.publisher.Mono;
import reactor.netty.resources.ConnectionProvider;

/** Failover (High-availability) mode */
public enum HaMode {
  /** sequential: driver will always connect according to connection string order */
  SEQUENTIAL(new String[] {"sequential"}) {
    public List getAvailableHost(
        List hostAddresses, ConcurrentMap denyList) {
      return getAvailableHostInOrder(hostAddresses, denyList);
    }

    public Mono connectHost(
        MariadbConnectionConfiguration conf, ReentrantLock lock, boolean failFast) {
      long endingNanoTime =
          CONNECTION_LOOP_DURATION.getSeconds() * 1_000_000_000 + System.nanoTime();
      return connectHost(conf, lock, failFast, this::getAvailableHost, endingNanoTime);
    }
  },

  /** load-balance: driver will randomly connect to any host, permitting balancing connections */
  LOADBALANCE(new String[] {"load-balance", "loadbalance", "loadbalancing"}) {
    public List getAvailableHost(
        List hostAddresses, ConcurrentMap denyList) {
      // use in order not blacklisted server
      List loopAddress =
          new ArrayList<>(HaMode.getAvailableHostInOrder(hostAddresses, denyList));
      Collections.shuffle(loopAddress);
      return loopAddress;
    }

    public Mono connectHost(
        MariadbConnectionConfiguration conf, ReentrantLock lock, boolean failFast) {
      long endingNanoTime =
          CONNECTION_LOOP_DURATION.getSeconds() * 1_000_000_000 + System.nanoTime();
      return connectHost(conf, lock, failFast, this::getAvailableHost, endingNanoTime);
    }
  },

  /** no ha-mode. Connect to first host only */
  NONE(new String[] {""}) {
    public List getAvailableHost(
        List hostAddresses, ConcurrentMap denyList) {
      return hostAddresses;
    }

    public Mono connectHost(
        MariadbConnectionConfiguration conf, ReentrantLock lock, boolean failFast) {
      return connectHost(conf, lock, true, this::getAvailableHost, 0L);
    }
  };

  /** temporary blacklisted hosts */
  private static final ConcurrentMap denyList = new ConcurrentHashMap<>();

  /** denied timeout */
  private static final long DENIED_LIST_TIMEOUT =
      Long.parseLong(System.getProperty("deniedListTimeout", "60000000000"));

  private static final Duration CONNECTION_LOOP_DURATION =
      Duration.parse(System.getProperty("connectionLoopDuration", "PT10S"));

  private final String[] aliases;

  HaMode(String[] value) {
    this.aliases = value;
  }

  /**
   * Get HAMode from values or aliases
   *
   * @param value value or alias
   * @return HaMode if corresponding mode is found
   */
  public static HaMode from(String value) {
    for (HaMode haMode : values()) {
      if (haMode.name().equalsIgnoreCase(value)) {
        return haMode;
      }
      for (String alias : haMode.aliases) {
        if (alias.equalsIgnoreCase(value)) {
          return haMode;
        }
      }
    }
    throw new IllegalArgumentException(
        String.format("Wrong argument value '%s' for HaMode", value));
  }

  private static Mono connect(
      MariadbConnectionConfiguration conf, ReentrantLock lock, HostAddress hostAddress) {
    return SimpleClient.connect(
            ConnectionProvider.newConnection(),
            InetSocketAddress.createUnresolved(hostAddress.getHost(), hostAddress.getPort()),
            hostAddress,
            conf,
            lock)
        .delayUntil(client -> AuthenticationFlow.exchange(client, conf, hostAddress))
        .doOnError(e -> HaMode.failHost(hostAddress))
        .cast(Client.class)
        .flatMap(
            client ->
                MariadbConnectionFactory.setSessionVariables(conf, client).then(Mono.just(client)));
  }

  /**
   * return hosts of without blacklisted hosts. hosts in blacklist reaching blacklist timeout will
   * be present. order corresponds to connection string order.
   *
   * @param hostAddresses hosts
   * @param denyList blacklist
   * @return list without denied hosts
   */
  private static List getAvailableHostInOrder(
      List hostAddresses, ConcurrentMap denyList) {
    // use in order not blacklisted server
    List copiedList = new ArrayList<>(hostAddresses);
    denyList.entrySet().stream()
        .filter(e -> e.getValue() < System.nanoTime())
        .forEach(e -> denyList.remove(e.getKey()));
    copiedList.removeAll(denyList.keySet());
    return copiedList;
  }

  public static Mono resumeConnect(
      Throwable t,
      MariadbConnectionConfiguration conf,
      ReentrantLock lock,
      boolean failFast,
      List availableHosts,
      BiFunction, ConcurrentMap, List> availHost,
      Iterator iterator,
      long endingNanoTime) {
    if (!iterator.hasNext()) {
      if (failFast || System.nanoTime() > endingNanoTime) {
        return Mono.error(
            ExceptionFactory.INSTANCE.createParsingException(
                String.format(
                    "Fail to establish connection to %s %s: %s",
                    availableHosts,
                    (failFast || endingNanoTime == 0L) ? "" : ", reaching timeout",
                    t.getMessage()),
                t));
      }
      try {
        Thread.sleep(250);
      } catch (InterruptedException e) {
        // eat
      }
      return connectHost(conf, lock, failFast, availHost, endingNanoTime);
    }
    return HaMode.connect(conf, lock, iterator.next())
        .onErrorResume(
            tt ->
                resumeConnect(
                    tt, conf, lock, failFast, availableHosts, availHost, iterator, endingNanoTime));
  }

  public static Mono connectHost(
      MariadbConnectionConfiguration conf,
      ReentrantLock lock,
      boolean failFast,
      BiFunction, ConcurrentMap, List> availHost,
      long endingNanoTime) {
    List nonBlacklistHosts = availHost.apply(conf.getHostAddresses(), denyList);
    List availableHosts;
    if (!failFast) {
      nonBlacklistHosts.addAll(denyList.keySet());
      // remove host from denyList not in initial host list
      availableHosts =
          nonBlacklistHosts.stream()
              .filter(h -> conf.getHostAddresses().contains(h))
              .collect(Collectors.toList());
    } else {
      availableHosts = nonBlacklistHosts;
    }

    Iterator iterator = availableHosts.iterator();
    if (!iterator.hasNext())
      return Mono.error(
          ExceptionFactory.INSTANCE.createParsingException(
              "Fail to establish connection: no available host"));
    return HaMode.connect(conf, lock, iterator.next())
        .onErrorResume(
            t ->
                resumeConnect(
                    t, conf, lock, failFast, availableHosts, availHost, iterator, endingNanoTime));
  }

  public static void failHost(HostAddress hostAddress) {
    denyList.put(hostAddress, System.nanoTime() + DENIED_LIST_TIMEOUT);
  }

  /**
   * List of hosts without blacklist entries, ordered according to HA mode
   *
   * @param hostAddresses hosts
   * @param denyList hosts temporary denied
   * @return list without denied hosts
   */
  public abstract List getAvailableHost(
      List hostAddresses, ConcurrentMap denyList);

  public abstract Mono connectHost(
      MariadbConnectionConfiguration conf, ReentrantLock lock, boolean failFast);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy