В этом посте рассказывается, как построить архитектуру балансировки нагрузки распределенной базы данных на основе ShardingSphere, а также о влиянии балансировки нагрузки на пользовательский вариант.

Наконец, в конце будет представлено и продемонстрировано универсальное решение для распределенных баз данных Apache ShardingSphere в облаке.

Архитектура балансировки нагрузки распределенной базы данных на основе ShardingSphere

Основы архитектуры балансировки нагрузки ShardingSphere

Как многие из наших читателей, возможно, уже знают, Apache ShardingSphere — это распределенная экосистема баз данных, которая преобразует любую базу данных в распределенную базу данных и расширяет ее с помощью сегментирования данных, гибкого масштабирования, шифрования и других возможностей.

Он состоит из двух продуктов, ShardingSphere-JDBC и ShardingSphere-Proxy, которые можно развертывать независимо друг от друга и поддерживать гибридные развертывания для использования друг с другом. На следующем рисунке показана архитектура гибридного развертывания:

Решение для балансировки нагрузки ShardingSphere-JDBC

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

В результате пользователям не нужно беспокоиться о балансировке нагрузки с помощью ShardingSphere-JDBC, и они могут сосредоточиться на балансировке нагрузки своего приложения.

Решение для балансировки нагрузки SharidngSphere-Proxy

Структура развертывания

ShardingSphere-Proxy — это прозрачный прокси базы данных, который предоставляет услуги клиентам базы данных через протокол базы данных. На следующем рисунке показан ShardingSphere-Proxy как отдельный развернутый процесс с балансировкой нагрузки поверх него:

Основные сведения о решении для балансировки нагрузки

Некоторые из участников нашего сообщества подробно обсуждали, как создать кластер ShardingSphere-Proxy, а некоторые спрашивали о непоследовательном поведении ShardingSphere-Proxy после балансировки нагрузки:

Ключевым моментом балансировки нагрузки кластера ShardingSphere-Proxy является то, что сам протокол базы данных разработан с учетом состояния. Например, статус аутентификации соединения, статус транзакции, подготовленный отчет и т. д.

Если балансировка нагрузки поверх ShardingSphere-Proxy не может понять протокол базы данных, единственный вариант — выбрать четырехуровневый кластер прокси-сервера балансировки нагрузки ShardingSphere-Proxy. В этом случае состояние соединения с базой данных между клиентом и ShardingSphere-Proxy поддерживается конкретным экземпляром Proxy.

Поскольку состояние самого соединения поддерживается в конкретном экземпляре прокси-сервера, четырехуровневая балансировка нагрузки может обеспечить балансировку нагрузки только на уровне соединения. Несколько запросов для одного и того же подключения к базе данных не могут быть отправлены на несколько экземпляров прокси, т. е. балансировка нагрузки на уровне запросов невозможна.

Примечание. В этой статье не рассматриваются детали четырехуровневой и семиуровневой балансировки нагрузки.

Рекомендации для прикладного уровня

Теоретически нет функциональной разницы между подключением клиента напрямую к одному ShardingSphere-Proxy или к кластеру ShardingSphere-Proxy через портал балансировки нагрузки. Однако существуют некоторые различия в технической реализации и конфигурации различных балансировщиков нагрузки.

Например, в случае прямого подключения к ShardingSphere-Proxy без ограничения максимального времени удержания сеанса подключения к базе данных, некоторые продукты ELB имеют максимальное время удержания сеанса на уровне 4, равное 60 минутам. закрывается по тайм-ауту балансировки нагрузки, но клиент не знает о закрытии пассивного TCP-соединения, это может привести к тому, что приложение сообщит об ошибке.

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

Рассмотрите возможность создания подключения по требованию для сценариев с длительными интервалами выполнения

Например, если один экземпляр соединения создается и используется постоянно, соединение с базой данных будет бездействовать большую часть времени при выполнении задания по времени с интервалом в 1 час и коротким временем выполнения.

Если сам клиент не знает об изменениях состояния соединения, длительное время простоя увеличивает неопределенность состояния соединения.

Для сценариев с длительными интервалами выполнения рассмотрите возможность создания соединений по запросу и их освобождения после использования.

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

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

Управление соединениями с базами данных через пулы соединений может снизить затраты на самостоятельное обслуживание соединений.

Клиент рассматривает возможность включения TCP KeepAlive

Конфигурация TCP KeepAlive обычно поддерживается клиентами, например:

  • MySQL Connector/J поддерживает конфигурацию autoReconnect или tcpKeepAlive, которые не включены по умолчанию.
  • Драйвер PostgreSQL JDBC поддерживает конфигурацию tcpKeepAlive, которая не включена по умолчанию.

Тем не менее, существуют некоторые ограничения на способ включения TCP KeepAlive:

  • Клиент не обязательно поддерживает настройку TCP KeepAlive или автоматическое переподключение.
  • Клиент не намерен вносить какие-либо изменения в код или конфигурацию.
  • TCP KeepAlive зависит от реализации и конфигурации операционной системы.

Случай пользователя: обрыв соединения из-за неправильной настройки балансировки нагрузки

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

описание проблемы

Для нашего случая предположим, что производственная среда пользователя использует кластер ShardingSphere-Proxy с 3 узлами, который обслуживает приложения через ELB облачного поставщика.

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

send of 115 bytes failed with errno=104 Connection reset by peer

Проверяю журналы ShardingSphere-Proxy, ненормальных сообщений нет.

Проблема возникает только с заданиями по времени, которые выполняются каждый час, а все остальные приложения обычно обращаются к ShardingSphere-Proxy.

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

Анализ проблемы

Причина, по которой приложение показывает ошибку, ясна: клиент отправляет данные по закрытому TCP-соединению.

Таким образом, целью устранения неполадок является определение точной причины, по которой соединение TCP было закрыто.

Если вы столкнулись с любой из трех причин, перечисленных ниже, мы рекомендуем вам выполнить захват сетевых пакетов как на стороне приложения, так и на стороне ShardingSphere-Proxy в течение нескольких минут до и после точки, в которой возникла проблема:

  • Проблема будет повторяться каждый час.
  • Проблема связана с сетью.
  • Проблема не влияет на операции пользователя в реальном времени.

Феномен I захвата пакетов

ShardingSphere-Proxy получает запрос на установление TCP-соединения от клиента каждые 15 секунд, но клиент отправляет RST прокси-серверу сразу после установления соединения тремя рукопожатиями.

Клиент отправляет RST прокси-серверу без ответа после получения приветствия сервера или даже до того, как прокси-сервер отправил приветствие сервера.

Однако в результатах перехвата пакетов на стороне приложения не было обнаружено трафика, соответствующего указанному выше поведению.

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

Феномен захвата пакетов II

Соединение MySQL устанавливается между клиентом и прокси-сервером ShardingSphere, и клиент отправляет RST прокси-серверу во время фазы отключения TCP-соединения.

Приведенные выше результаты захвата пакетов показывают, что клиент сначала инициировал команду COM_QUIT для ShardingSphere-Proxy, т. е. соединение MySQL было разорвано клиентом, включая, помимо прочего, следующие возможные сценарии:

  • Приложение завершило использование соединения MySQL и нормально закрыло соединение с базой данных.
  • Соединение приложения с базой данных ShardingSphere-Proxy управляется пулом соединений, который выполняет операцию освобождения незанятых соединений, время ожидания которых истекло или истекло их максимальное время существования.

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

После нескольких раундов анализа пакетов не было обнаружено, что ShardingSphere-Proxy отправлял клиенту RST в течение нескольких минут до и после повторного появления проблемы.

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

Поскольку сам ShardingSphere-Proxy не имеет логики для активного отключения клиента, проблема исследуется как на уровне клиента, так и на уровне ELB.

Клиентское приложение и проверка конфигурации ELB

На основе отзывов пользователей:

  • Временные задания приложения выполняются ежечасно, приложение не использует пул соединений с базой данных, а соединение с базой данных поддерживается вручную и предоставляется для постоянного использования временными заданиями.
  • ELB настроен с четырьмя уровнями удержания сеанса и тайм-аутом простоя сеанса, равным 40 минутам.

Принимая во внимание частоту выполнения заданий по времени, мы рекомендуем пользователям изменить время простоя сеанса ELB так, чтобы оно превышало интервал выполнения заданий по времени. После того, как пользователь изменяет время ожидания ELB на 66 минут, проблема сброса подключения больше не возникает.

Если во время устранения неполадок продолжится захват пакетов, весьма вероятно, что ELB будет захватывать трафик, который разрывает соединение TCP на 40-й минуте каждого часа.

Вывод проблемы

Клиент сообщил об ошибке сброса соединения узлом Основная причина:

Тайм-аут простоя ELB был меньше интервала выполнения задачи по времени, а клиент бездействовал дольше, чем тайм-аут удержания сеанса ELB, в результате чего соединение между клиентом и ShardingSphere-Proxy было разорвано по тайм-ауту ELB.

Клиент отправляет данные в TCP-соединение, которое было закрыто ELB, что приводит к ошибке Connection reset by peer.

Эксперимент по моделированию тайм-аута

В этой статье мы проводим простой эксперимент для проверки производительности клиента после истечения времени ожидания сеанса балансировки нагрузки и выполняем захват пакетов во время эксперимента для анализа сетевого трафика и наблюдения за поведением балансировки нагрузки.

Создание кластерной среды ShardingSphere-Proxy с балансировкой нагрузки

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

Настроить поток nginx

Тайм-аут простоя установлен на 1 минуту, т. е. сеанс TCP удерживается максимум 1 минуту.

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

stream {
    upstream shardingsphere {
        hash $remote_addr consistent;

        server proxy0:3307;
        server proxy1:3307;
    }

    server {
        listen 3306;
        proxy_timeout 1m;
        proxy_pass shardingsphere;
    }
}

Создать композицию Docker

version: "3.9"
services:

  nginx:
    image: nginx:1.22.0
    ports:
      - 3306:3306
    volumes:
      - /path/to/nginx.conf:/etc/nginx/nginx.conf

  proxy0:
    image: apache/shardingsphere-proxy:5.3.0
    hostname: proxy0
    ports:
      - 3307

  proxy1:
    image: apache/shardingsphere-proxy:5.3.0
    hostname: proxy1
    ports:
      - 3307

Среда запуска

 $ docker compose up -d 
[+] Running 4/4
 ⠿ Network lb_default     Created                                                                                                      0.0s
 ⠿ Container lb-proxy1-1  Started                                                                                                      0.5s
 ⠿ Container lb-proxy0-1  Started                                                                                                      0.6s
 ⠿ Container lb-nginx-1   Started            

Моделирование задач на время на основе одного и того же соединения на стороне клиента

Создать отложенное выполнение SQL на стороне клиента

Здесь доступ к ShardingSphere-Proxy осуществляется через Java и MySQL Connector/J.

Логика примерно такая:

  1. Установите соединение с прокси-сервером ShardingSphere и выполните запрос к прокси.
  2. Подождите 55 секунд, а затем выполните еще один запрос к прокси.
  3. Подождите 65 секунд, а затем выполните еще один запрос к прокси.
public static void main(String[] args) {
    try (Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306?useSSL=false", "root", "root"); Statement statement = connection.createStatement()) {
        log.info(getProxyVersion(statement));
        TimeUnit.SECONDS.sleep(55);
        log.info(getProxyVersion(statement));
        TimeUnit.SECONDS.sleep(65);
        log.info(getProxyVersion(statement));
    } catch (Exception e) {
        log.error(e.getMessage(), e);
    }
}

private static String getProxyVersion(Statement statement) throws SQLException {
    try (ResultSet resultSet = statement.executeQuery("select version()")) {
        if (resultSet.next()) {
            return resultSet.getString(1);
        }
    }
    throw new UnsupportedOperationException();
}

Ожидаемые результаты и результаты запуска на стороне клиента

Ожидаемые результаты:

  1. Клиентское соединение с ShardingSphere-Proxy установлено, и первый запрос выполнен успешно.
  2. Второй запрос клиента выполнен успешно.
  3. Третий запрос клиента приводит к ошибке из-за разрыва TCP-соединения, так как тайм-аут простоя nginx установлен на 1 минуту.

Результаты выполнения соответствуют ожиданиям. Из-за различий между языком программирования и драйвером базы данных сообщения об ошибках ведут себя по-разному, но основная причина одна и та же: оба TCP-соединения были разорваны.

Журналы показаны ниже:

15:29:12.734 [main] INFO icu.wwj.hello.jdbc.ConnectToLBProxy - 5.7.22-ShardingSphere-Proxy 5.1.1
15:30:07.745 [main] INFO icu.wwj.hello.jdbc.ConnectToLBProxy - 5.7.22-ShardingSphere-Proxy 5.1.1
15:31:12.764 [main] ERROR icu.wwj.hello.jdbc.ConnectToLBProxy - Communications link failure
The last packet successfully received from the server was 65,016 milliseconds ago. The last packet sent successfully to the server was 65,024 milliseconds ago.
        at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174)
        at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64)
        at com.mysql.cj.jdbc.StatementImpl.executeQuery(StatementImpl.java:1201)
        at icu.wwj.hello.jdbc.ConnectToLBProxy.getProxyVersion(ConnectToLBProxy.java:28)
        at icu.wwj.hello.jdbc.ConnectToLBProxy.main(ConnectToLBProxy.java:21)
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure

The last packet successfully received from the server was 65,016 milliseconds ago. The last packet sent successfully to the server was 65,024 milliseconds ago.
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
        at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
        at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
        at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151)
        at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:167)
        at com.mysql.cj.protocol.a.NativeProtocol.readMessage(NativeProtocol.java:581)
        at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:761)
        at com.mysql.cj.protocol.a.NativeProtocol.sendCommand(NativeProtocol.java:700)
        at com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol.java:1051)
        at com.mysql.cj.protocol.a.NativeProtocol.sendQueryString(NativeProtocol.java:997)
        at com.mysql.cj.NativeSession.execSQL(NativeSession.java:663)
        at com.mysql.cj.jdbc.StatementImpl.executeQuery(StatementImpl.java:1169)
        ... 2 common frames omitted
Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
        at com.mysql.cj.protocol.FullReadInputStream.readFully(FullReadInputStream.java:67)
        at com.mysql.cj.protocol.a.SimplePacketReader.readHeaderLocal(SimplePacketReader.java:81)
        at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:63)
        at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:45)
        at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:52)
        at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:41)
        at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:54)
        at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:44)
        at com.mysql.cj.protocol.a.NativeProtocol.readMessage(NativeProtocol.java:575)
        ... 8 common frames omitted

Анализ результатов захвата пакетов

Результаты захвата пакетов показывают, что после тайм-аута простоя подключения nginx одновременно отключается от клиента и прокси по TCP. Однако, поскольку клиент не знает об этом, nginx возвращает RST после отправки команды.

После тайм-аута простоя соединения nginx процесс отключения TCP с прокси-сервером завершается нормально, и прокси-сервер совершенно не знает, когда клиент отправляет последующие запросы, используя отключенное соединение.

Проанализируйте следующие результаты захвата пакетов:

  • Цифры 1–44 — это взаимодействие между клиентом и ShardingSphere-Proxy для установления соединения с MySQL.
  • Номера 45–50 — это первые запросы, выполненные клиентом.
  • Номера 55–60 — это второй запрос, выполняемый через 55 секунд после того, как клиент выполнил первый запрос.
  • Номера 73–77 — это процессы отключения TCP-соединения, инициированные nginx как для клиента, так и для ShardingSphere-Proxy после истечения времени ожидания сеанса.
  • Номера 78–79 — это третий запрос, выполняемый через 65 секунд после того, как клиент выполнит второй запрос, и произойдет сброс соединения.

ShardingSphere в облаке Универсальное решение

Развертывание и обслуживание кластеров ShardingSphere-Proxy, а также балансировка нагрузки вручную могут быть трудоемкими и занимать много времени. Чтобы решить эту проблему, Apache ShardingSphere запустила ShardingSphere on Cloud, набор облачных решений.

ShardingSphere-on-Cloud включает сценарии автоматического развертывания на виртуальные машины в облачных средах, таких как AWS, GCP и Alibaba Cloud, например шаблоны стека CloudFormation, сценарии развертывания Terraform одним щелчком мыши, диаграммы Helm в облачных средах Kubernetes, Operator.

ShardingSphere-on-Cloud включает в себя следующие инструменты: Helm Charts, Operator, автоматическое горизонтальное масштабирование и другие инструменты в облачной среде Kubernetes, а также разнообразный практический контент по высокой доступности, наблюдаемости, соответствию требованиям безопасности и более.

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

  • ShardingSphere-Proxy на основе Helm Charts для развертывания одним щелчком мыши в средах Kubernetes.
  • ShardingSphere-Proxy на основе оператора для развертывания одним щелчком мыши и автоматического обслуживания в средах Kubernetes.
  • ShardingSphere-Proxy на базе AWS CloudFormation для быстрого развертывания.
  • Быстрое развертывание ShardingSphere-Proxy на базе Terraform в средах AWS.

В этом посте кратко демонстрируется одна из фундаментальных возможностей ShardingSphere в облаке: развертывание кластеров ShardingSphere-Proxy в Kubernetes одним щелчком мыши с использованием Helm Charts.

  1. Используйте следующую команду из 3 строк, чтобы создать кластер ShardingSphere-Proxy с 3 узлами в кластере Kubernetes с конфигурацией по умолчанию и обслуживать его через Службу.
helm repo add shardingsphere https://apache.github.io/shardingsphere-on-cloud
helm repo update
helm install shardingsphere-proxy shardingsphere/apache-shardingsphere-proxy-charts -n shardingsphere

2. Приложение может получить доступ к кластеру ShardingSphere-Proxy через домен svc.

kubectl run mysql-client --image=mysql:5.7.36 --image-pull-policy=IfNotPresent -- sleep 300
kubectl exec -i -t mysql-client -- mysql -h shardingsphere-proxy-apache-shardingsphere-proxy.shardingsphere.svc.cluster.local -P3307 -uroot -proot

Это всего лишь демонстрация одной из основных возможностей ShardingSphere в облаке.

Дополнительные сведения о дополнительных функциях, доступных в производственных средах, см. в официальной документации ShardingSphere-on-Cloud.

Соответствующие ссылки

🔗 Официальный сайт ShardingSphere

🔗 Официальный репозиторий проекта ShardingSphere

🔗 Официальный сайт ShardingSphere-on-Cloud

🔗« Отчет о проекте ShardingSphere-on-Cloud

🔗 ShardingSphere Twitter

🔗 ShardingSphere Slack