Глава 9. Связи с несколькими хостами

Следующие разделы обсуждают много тем, которые включают связи с несколькими хостами, а именно выравнивание нагрузки сервера, отказоустойчивость и репликацию.

Разработчики должны знать следующие вещи о таких связях, которые организованы через Connector/J:

9.1. Формирование отказоустойчивости сервера для связей, используя JDBC

MySQL Connector/J поддерживает отказоустойчивость сервера. Отказоустойчивость происходит, когда связанные со связью ошибки происходят для основной, активной связи. Ошибки связи, по умолчанию, размножены клиенту, который должен обращаться с ними, например, воссоздавая рабочие объекты (Statement, ResultSet и т.д.) и перезапуск процессов. Иногда драйвер мог бы в конечном счете отступить к оригинальному хосту автоматически, прежде чем клиентское приложение продолжит работу, в этом случае переключение хоста прозрачно, клиентское приложение даже не заметит его.

Связь использует поддержку кластерных систем точно так же, как стандартную связь: клиент не испытывает разрушений в процессе отказоустойчивости. Это означает, что клиент может полагаться на тот же самый экземпляр связи, даже если два последовательных запроса могли бы быть выполнены на двух различных физических хостах. Однако это не означает, что клиент не должен иметь дело с исключениями, вызванными переключением сервера.

Отказоустойчивость формируется на стадии начальной настройки связи сервера связью URL (см. объяснения формата здесь):

jdbc:mysql://[primary host][:port],[secondary host 1][:port][,[secondary host 2][:port]]...[/[database]]
[?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]

Список хостов в связи URL включает два типа хостов primary и secondary. Начиная новую связь, драйвер всегда пытается соединиться с основным хостом сначала и при необходимости с вторичными в списке последовательно, когда есть проблемы коммуникации. Даже если начальная связь с основным хостом прерывается, и драйвер связан с вторичным хостом, основной никогда не теряет особый статус: например, это может формироваться с режимом доступа, отличным от вторичных, это может быть помещено на более высокий приоритет, когда хост должен быть выбран во время процесса отказоустойчивости.

Поддержка кластерных систем формируется следующими свойствами связи (их функции объяснены в параграфах ниже):

Формирование режима доступа связи

Как с любой стандартной связью, начальная связь с основным хостом находится в режиме чтения-записи. Однако, если драйвер не устанавливает начальную связь с основным хостом, и она автоматически переключается на следующий хост в списке, режим доступа теперь зависит от значения параметра failOverReadOnly, которое true по умолчанию. То же самое происходит, если драйвер первоначально связан с основным хостом и из-за некоторой неудачи связи, он переключается на вторичный хост. Каждый раз, когда связь отступает к основному хосту, ее режим доступа будет чтением-записью, независимо от того, была ли связь с основным хостом раньше. Режим доступа связи может быть изменен в любое время во время выполнения, вызвав метод method Connection.setReadOnly(boolean), который частично перекрывает свойство failOverReadOnly. При failOverReadOnly=false и режим доступа явно установлен, это становится режимом для каждой связи после переключения хоста, неважно с каким типом хоста связываются, но, если failOverReadOnly=true, изменение режима доступа к чтению-записи возможно только, если драйвер соединяется с основным хостом, однако, даже если режим доступа не может быть изменен для текущей связи, драйвер помнит последнее намерение клиента, и отступая к основному хосту, это является способом, который будет использоваться. Для иллюстрации посмотрите следующие последовательности событий со связью с двумя хостами.

Различие между этими двумя сценариями находится в шаге 4: режим доступа для вторичного хоста в последовательности A не изменяется в том шаге, но драйвер помнит и использует установленный режим, отступая к основному хосту, который был бы только для чтения иначе, но в последовательности B, режим доступа для вторичного хоста немедленно изменяется.

Формирование отступления к основному хосту

Как уже упомянуто, основной хост особенный в договоренности отказоустойчивости когда дело доходит до режима доступа хоста. Кроме того, драйвер пытается отступить к основному хосту как можно скорее по умолчанию, даже если никакое коммуникационное исключение не происходит. Два свойства secondsBeforeRetryMaster и queriesBeforeRetryMaster определяют, когда драйвер будет готов повторить повторное соединение с основным хостом (Master в имени свойства указывает на основной хоста нашей связи URL, который является не обязательно основным хостом в репликации):

В целом попытка к отступлению к основному хосту предпринята, когда по крайней мере одно из условий, определенных этими двумя свойствами, выполнено, и попытка всегда происходит в границах транзакции. Однако, если выключен auto-commit, проверка происходит только когда вызван метод Connection.commit() или Connection.rollback(). Автоматическое отступление к основному хосту может быть вообще выключено, установив одновременно secondsBeforeRetryMaster и queriesBeforeRetryMaster в 0. Установка одного из свойств в 0 отключает только одну часть проверки.

Формирование попыток повторного соединения

Устанавливая новую связь или когда событие отказоустойчивости имеет место, драйвер пытается соединиться последовательно со следующим кандидатом в списке хостов. Когда конец списка был достигнут, это начинает снова с начала списка, однако, через основной хост перескакивает, если: (a) НЕ все вторичные хосты были уже проверены, по крайней мере, однажды И (b) условия отступления, определенные secondsBeforeRetryMaster и queriesBeforeRetryMaster, еще не выполняются. Каждый просмотр списка хостов (который не обязательно закончен в конце списка) считается как единственная попытка подключения. Драйвер пробует столько попыток подключения, сколько определено значением retriesAllDown.

Бесшовное повторное соединение

Хотя не рекомендуется, можно заставить драйвер выполнить отказоустойчивость, не лишая законной силы активные экземпляры Statement или ResultSet, устанавливая любой параметр autoReconnect или autoReconnectForPools в true. Это позволяет клиенту продолжать использовать те же самые экземпляры объектов после события отказоустойчивости, не принимая исключительных мер. Это, однако, может привести к неожиданным результатам: например, если драйвер будет связан с основным хостом с режимом доступа для чтения-записи, и он переключается к вторичному хосту в режиме только чтения, дальнейшие попытки выполнить изменяющие данные запросы приведут к ошибкам, а клиент не будет знать об этом. Это ограничение особенно релевантно, используя потоковые данные: после события отказоустойчивости, ResultSet надеется быть в порядке, но основная связь уже, возможно, изменилась, и никакой курсор больше не доступен.

9.2. Формирование отказоустойчивости сервера для связей, используя X DevAPI

Используя X-протокол, Connector/J поддерживает клиентскую функцию отказоустойчивости для установления сессии. Если многократные хосты определяются в связи URL, когда Connector/J не соединяется с перечисленным хостом, это пытается соединиться с другим. Это типовой X DevAPI URL для формирования клиентской отказоустойчивости:

mysqlx://sandy:mypassword@[host1:33060,host2:33061]/test

С клиентской формируемой отказоустойчивостью, когда есть отказ установить связь, Connector/J продолжает пытаться соединиться с хостом в списке хостов. Порядок, в котором хосты проверены для связи, следующий:

Заметьте, что особенность отказоустойчивости сервера X DevAPI только допускает отказоустойчивость, когда Connector/J пытается установить связь, но не во время операций после того, как связь была уже установлена.

Объединение связи, используя X DevAPI. Используя объединение связи с X DevAPI, Connector/J отслеживает любой хост, с которым не соединился и в течение короткого времени ожидания после неудачи, избегает соединяться с ним во время создания или поиска Session. Однако, если все другие хосты уже проверены, исключенные хосты будут проверены без ожидания. Как только все хосты проверены и никакие связи не могут быть установлены, Connector/J бросает исключение com.mysql.cj.exceptions.CJCommunicationsException и возвращает сообщение Unable to connect to any of the target hosts.

9.3. Формирование выравнивания нагрузки с Connector/J

Connector/J долго обеспечивал эффективное средство, чтобы распределить нагрузку чтения-записи на многократные экземпляры сервера MySQL для Cluster или средств репликации master-master. Можно динамично формировать уравновешенные связи без приостановки обслуживания. Незавершенные транзакции не потеряны, никакие прикладные исключения не произведены, если какое-либо приложение пытается использовать конкретный экземпляр сервера.

Выравнивание нагрузки формируется на стадии начальной настройки связи сервера следующей связью URL, у которого есть формат, аналогичный общему URL JDBC для подключения MySQL, но со специализированной схемой:

jdbc:mysql:loadbalance://[host1][:port],[host2][:port][,[host3][:port]]...[/[database]]
[?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]

Есть два свойства конфигурации, связанные с этой функциональностью:

Как только связь была установлена, используя правильные свойства связи, много контролирующих свойств доступны:

Следующие операции управления могут также быть выполнены:

Интерфейс JMX com.mysql.cj.jdbc.jmx.LoadBalanceConnectionGroupManagerMBean имеет следующие методы:

Метод getRegisteredConnectionGroups() возвращает имена всех групп связи, определенных в этом загрузчике класса.

Можно проверить эту установку со следующим кодом:

public class Test {
  private static String URL = "jdbc:mysql:loadbalance://" +
                 "localhost:3306,localhost:3310/test?" +
                 "loadBalanceConnectionGroup=first&ha.enableJMX=true";

  public static void main(String[] args) throws Exception {
    new Thread(new Repeater()).start();
    new Thread(new Repeater()).start();
    new Thread(new Repeater()).start();
  }
  static Connection getNewConnection() throws SQLException,
         ClassNotFoundException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    return DriverManager.getConnection(URL, "root", "");
  }

  static void executeSimpleTransaction(Connection c, int conn, int trans) {
    try {
      c.setAutoCommit(false);
      Statement s = c.createStatement();
      s.executeQuery("SELECT SLEEP(1) /* Connection: " + conn +
                     ", transaction: " + trans + " */");
      c.commit();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }
  public static class Repeater implements Runnable {
    public void run() {
      for(int i=0; i < 100; i++) {
        try {
          Connection c = getNewConnection();
          for(int j=0; j < 10; j++) {
            executeSimpleTransaction(c, i, j);
            Thread.sleep(Math.round(100 * Math.random()));
          }
          c.close();
          Thread.sleep(100);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
  }
}

После компилирования приложение может быть запущено с флагом -Dcom.sun.management.jmxremote, чтобы позволить удаленное управление. jconsole может тогда быть запущена. Главный класс Test будет перечислен jconsole. Выберите это и нажмите Connect. Можно тогда перейти к com.mysql.cj.jdbc.jmx.LoadBalanceConnectionGroupManager . В этом пункте можно нажать на различные операции и исследовать возвращенный результат.

Если бы у вас теперь был дополнительный экземпляр MySQL на порте 3309, вы могли бы гарантировать, что Connector/J начинает использовать его при помощи addHost(), который выставляется в jconsole. Обратите внимание на то, что эти операции могут быть выполнены динамично, не имея необходимости останавливать приложение.

Для получения дополнительной информации о комбинации выравнивания нагрузки и отказоустойчивости, посмотрите раздел 9.5.

9.4. Формирование репликации Master/Slave с Connector/J

Эта секция описывает много особенностей поддержки Connector/J репликации.

Повторение формируется на стадии начальной настройки связи сервера связью URL, у которой есть формат, аналогичный общему URL JDBC для подключения MySQL, но со специализированной схемой:

jdbc:mysql:replication://[master host][:port],[slave host 1][:port][,[slave host 2][:port]]...[/[database]]
[?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]

Пользователи могут определить свойство allowMasterDownConnections=true, чтобы позволить объекты Connection, которые будут созданы даже при том, что никакие основные хосты недостижимы. Такие объекты Connection сообщают, что они только для чтения, и isMasterConnection() вернет false для них. Connection проверяет на доступные мастер-хосты при вызове Connection.setReadOnly(false), бросив SQLException, если он не может установить связь с мастер-хостом или переключаясь на основную связь, если хост доступен.

Пользователи могут определить свойство allowSlavesDownConnections=true, чтобы позволить объекты Connection, которые будут созданы даже при том, что никакие подчиненные хосты недостижимы. Connection во время выполнения проверяются на доступные подчиненные хосты при вызове Connection.setReadOnly(true) (см. объяснение метода ниже), бросая SQLException, если это не может установить связь с подчиненным хостом, если свойство readFromMasterWhenNoSlaves = true (см. ниже для описания свойства).

Масштабирование нагрузки чтения, распределяя трафик на хосты Slave

Connector/J поддерживает связи репликации. Это может автоматически послать запросы read/write на мастер-хост, используя отказоустойчивость или циклический алгоритм балансировки на основе статуса Connection.getReadOnly().

Приложение сигнализирует, что хочет, чтобы транзакция была только для чтения, вызывая Connection.setReadOnly(true). Связь будет использовать одну из slave-связей, которые распределяют нагрузку на подчиненные хосты, используя циклическую схему. Данная связь прилипает к хосту, пока команда границы транзакции (передача или отмена) не дается, или пока хост не удален из службы. После запроса Connection.setReadOnly(true), если вы хотите позволить связь мастер-хосту, когда никакие подчиненные не доступны, установите свойство readFromMasterWhenNoSlaves = true. Заметьте, что основной хост будет использоваться в статусе только для чтения в этих случаях, как будто это подчиненный хост. Также заметьте, что установка readFromMasterWhenNoSlaves=true может привести к дополнительной нагрузке для основного хоста.

Если у вас есть транзакция записи или процесс, который чувствителен ко времени (помните, репликация в MySQL асинхронная), установите связь в режим не только для чтения, вызвав Connection.setReadOnly(false), и драйвер гарантирует, что дальнейшие вызовы посылают в основной сервер MySQL. Драйвер заботится о размножении текущего состояния autocommit, уровня изоляции и каталога между всеми связями, которые это использует, чтобы достигнуть функциональности выравнивания нагрузки.

Чтобы позволить эту функциональность, используйте специализированную схему репликации (jdbc:mysql:replication:// ) соединяясь с сервером.

Вот короткий пример того, как связь могла бы использоваться в автономном приложении:

import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Properties;
import java.sql.DriverManager;

public class ReplicationDemo {
  public static void main(String[] args) throws Exception {
    Properties props = new Properties();
    // We want this for failover on the slaves
    props.put("autoReconnect", "true");
    // We want to load balance between the slaves
    props.put("roundRobinLoadBalance", "true");
    props.put("user", "foo");
    props.put("password", "password");

    // Looks like a normal MySQL JDBC url, with a
    // comma-separated list of hosts, the first
    // being the 'master', the rest being any number
    // of slaves that the driver will load balance against
    Connection conn =
      DriverManager.getConnection("jdbc:mysql:replication://master,
                                  slave1,slave2,slave3/test", props);

    // Perform read/write work on the master
    // by setting the read-only flag to "false"
    conn.setReadOnly(false);
    conn.setAutoCommit(false);
    conn.createStatement().executeUpdate("UPDATE some_table ....");
    conn.commit();

    // Now, do a query from a slave, the driver automatically picks one
    // from the list
    conn.setReadOnly(true);
    ResultSet rs = conn.createStatement().executeQuery("SELECT a,b
                                                       FROM alt_table");
    .......
  }
}

Рассмотрите использование Load Balancing JDBC Pool (lbpool), который обеспечивает обертку вокруг типичного драйвера JDBC и позволяет вам использовать пул соединения с БД, который включает проверки на системные отказы и неравномерное распределение нагрузки. Для получения дополнительной информации посмотрите Load Balancing JDBC Driver for MySQL (mysql-lbpool).

Поддержка топографий репликации с несколькими мастерами

Connector/J репликации с несколькими мастерами.

Связь URL для репликации рассмотрена ранее в формате jdbc:mysql:replication://master,slave1,slave2, slave3/test). Она предполагает, что первым (и только первым) хостом является мастер. Поддержка развертывания с произвольным числом мастеров и подчиненных требует синтаксис URL "address-equals" для многократной связи хоста, обсужденной в разделе 6.2, со свойством type=[master|slave]:

jdbc:mysql:replication://address=(type=master)(host=master1host),
                         address=(type=master)(host=master2host),
                         address=(type=slave)(host=slave1host)/database

Connector/J использует уравновешенную связь внутренне для управления основными связями, это означает, что ReplicationConnection, когда формируется, чтобы использовать многократные мастер-хосты, выставляет те же самые опции уравновесить нагрузку через основные хосты, как описано в разделе 9.3.

Живая реконфигурация топографии репликации

Connector/J также поддерживает живое управление хостом репликации. Это позволяет пользователям продвинуть хосты для приложений Java, не требуя перезапуска приложений.

Хостами репликации наиболее эффективно управляют в контексте группы соединения репликации. Класс ReplicationConnectionGroup представляет логическую группировку связей, которые могут быть организованы вместе. Могут быть одна или более таких групп соединения репликации в данном загрузчике класса Java (может быть приложение с двумя различными ресурсами JDBC, которые должны управляться независимо). Этот ключевой класс выставляет методы управления хоста для соединений репликации и объекты ReplicationConnection регистрируются с соответствующим ReplicationConnectionGroup, если значение для нового свойства replicationConnectionGroup определяется. Объект ReplicationConnectionGroup отслеживает эти связи, пока они не закрываются, и это используется, чтобы управлять хостами, связанными с этими связями.

Некоторые важные методы, связанные с управлением хостами, включают:

Некоторые полезные метрики управления включают:

ReplicationConnectionGroupManager

com.mysql.cj.jdbc.ha.ReplicationConnectionGroupManager обеспечивает доступ к группам соединения репликации, вместе с некоторыми служебными методами.

Другие методы ReplicationConnectionGroupManager отражают ReplicationConnectionGroup за исключением того, что первый аргумент это название группы (String). Эти методы будут управлять на всем соответствии ReplicationConnectionGroups, что полезно для удаления сервера из обслуживания и цдаления его из всех имеющихся ReplicationConnectionGroups.

Эти методы могли бы быть полезны для управления хостами репликации в JVM, если применение вызывает изменения топографии. Для управления конфигурациями хоста снаружи JVM может использоваться JMX.

Применение JMX для управления хостами репликации

Когда Connector/J начат с ha.enableJMX=true и значение установлено для свойства replicationConnectionGroup, JMX MBean будет зарегистрирован, позволяя манипуляцию хостов репликации клиентом JMX. Интерфейс MBean определяется в com.mysql.cj.jdbc.jmx.ReplicationGroupManagerMBean и позволяет статические методы ReplicationConnectionGroupManager :

public abstract void addSlaveHost(String groupFilter, String host) throws SQLException;
public abstract void removeSlaveHost(String groupFilter, String host) throws SQLException;
public abstract void promoteSlaveToMaster(String groupFilter, String host) throws SQLException;
public abstract void removeMasterHost(String groupFilter, String host) throws SQLException;
public abstract String getMasterHostsList(String group);
public abstract String getSlaveHostsList(String group);
public abstract String getRegisteredConnectionGroups();
public abstract int getActiveMasterHostCount(String group);
public abstract int getActiveSlaveHostCount(String group);
public abstract int getSlavePromotionCount(String group);
public abstract long getTotalLogicalConnectionCount(String group);
public abstract long getActiveLogicalConnectionCount(String group);

9.5. Продвинутая конфигурация выравнивания нагрузки и отказоустойчивости

Connector/J обеспечивает полезное внедрение выравнивания нагрузки для MySQL Cluster или систем с несколькими мастер-хостами, как объяснено в разделе 9.3. Это же самое внедрение используется для балансирования нагрузки между подчиненными хостами только для чтения при репликации.

Пытаясь уравновесить рабочую нагрузку между многократными серверами, драйвер должен определить, когда безопасно менять серверы, так как такое действие, например, посреди транзакции может вызвать проблемы. Важно не потерять статус информацию. Поэтому Connector/J только попытается выбрать новый сервер, когда одно из следующего произойдет:

  1. На границах транзакции (транзакции явно переданы или отменены).

  2. При коммуникационном исключении (статус SQL, начинающийся с "08").

  3. Когда условия совпадения SQLException определяются пользователем, используя точки расширения, определенные свойствами loadBalanceSQLStateFailover, loadBalanceSQLExceptionSubclassFailover или loadBalanceExceptionChecker.

Третье условие обрабатывает три свойства, которые позволяют вам управлять триггером отказоустойчивости SQLException:

В то время как три условия отказоустойчивости перечислили большинство ситуаций, если autocommit включен, Connector/J никогда не балансирует повторно и продолжает использовать ту же самую физическую связь. Это может быть проблематично, особенно когда выравнивание нагрузки используется, чтобы распределить нагрузку только для чтения на много хостов. Однако Connector/J может формироваться, чтобы повторно балансировать после того, как определенное число запросов будет выполнено, когда включен autocommit. Эта функциональность зависит от следующих свойств: