ProxySQL — средство для демультиплексирования соединений

Здравствуйте, меня зовут Александр Яковлев, я работаю в компании Ситимобил и занимаюсь эксплуатацией. Сегодня я расскажу про очень интересный продукт ProxySQL — это высокопроизводительный MySQL Proxy, который умеет очень много — отлавливать и убивать запросы по маске, с помощью него можно искать sql injection, дублировать нагрузку и много другое. Я расскажу о нашем опыте работы с ним.
С описанной ниже ситуацией рано или поздно сталкивается любой крупный IT-проект, развитие которого начиналось с пары серверов. Представим, что в проекте сначала была только одна база данных — мастер-сервер. Постепенно к нему добавили кучу слейвов. Потом внедрили шардинг.
И в один прекрасный день нагрузка внезапно возрастает в 10 раз. Например, потому что упал ваш основной конкурент, и клиенты хлынули к вам. В этот момент вам кажется, что можно элементарно масштабировать нагрузку добавлением веб-серверов. Но после того, как вы сделали это, возникает неприятная ситуация.

Рассмотрим на примере одного мастера. Допустим, у вас 50 web серверов и на каждом 200 php-fpm процессов. Тогда в мастер прилетит 50×200 коннектов, при этом в каждый слейв придет 50×200/количество слейвов (если, конечно же, в haproxy настроен roundrobin) — смотрите картинку ниже. Конечно, 10 тыс. коннектов в мастер — это много, но еще терпимо, а если будет 200 вебов, то количество коннектов будет еще больше, а один коннект = один тред.
Именно в это бутылочное горлышко мы уперлись.

image

Дальше мы стали рассуждать: коннект в мастер устанавливается в коде всегда, но нужен ли он всем fpm процессам? Скорее всего, нет. Мы заметили, что большое количество persist-коннектов в мастер просто висят в слипах. И решили, что нам нужно демультиплексирование.

image

Для этого мы обратили внимание на продукт под названием ProxySQL. Он работает как обычный reverse proxy: к нему устанавливаются подключения, и он перераспределяет трафик по определенным правилам, указанным в конфигурации.
Мы установили ProxySQL на всех наших веб-серверах, а в конфигурации приложения прописали, что обращение в мастер-базу выполняется по адресу 127.0.0.1. Если раньше 200 FPM-воркеров на каждом веб-сервере означали 200 подключений к мастер-базе от этой машины, то теперь ситуация изменилась. Эти 200 подключений приходят в ProxySQL, а наружу в разное время выходят 50–70. То есть ProxySQL умеет многократно использовать уже установленные подключения.
Благодаря демультиплексированию мы на всех мастерах сократили количество подключений в 3–10 раз, график current connections одного из мастеров смотрите ниже.

image

Благодаря ProxySQL мы избавились от вышеописанного бутылочного горлышка. Но это не единственный рабочий процесс, который мы улучшили с помощью этого инструмента.
Второй процесс мы еще не доделали, но очень близки к завершению. С помощью ProxySQL мы планируем дублировать реальную нагрузку в тестовую среду. Это нужно для проверки новых фич боевым трафиком.
Как это будет реализовано? Приложение идет в ProxySQL, а тот отправляет трафик по двум маршрутам: в боевые базы данных, чтобы приложение функционировало, и в тестовую среду для проверки новых фич под нагрузкой.

Особенность ProxySQL в том, что есть его конфиг, который в нашем случае выкатывается через puppet (для puppet есть модуль ProxySQL), но еще есть понятие зоны runtime, когда для того, чтобы внести изменение (добавить сервер, добавить пользователя, удалить сервер и пользователя), не нужен привычный рестарт/релоад., Все делается через консоль ProxySQL, например так.
mysql -ulogin -ppassword -h 127.0.0.1 -P6032 -e «INSERT INTO mysql_users (username, password, default_hostgroup) VALUES ('sm_username','pass',1);; LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;»
Более подробно конено же в официальной документации github.com/sysown/proxysql/wiki/Users-configuration

Спасибо за внимание.

Наш конфиг, которым мы решили описанную выше задачу смотрите ниже.

Наш конфиг

datadir="/var/lib/proxysql"

admin_variables=
{
    admin_credentials="user:pass"
    mysql_ifaces="0.0.0.0:6032"
    refresh_interval=2000
    web_enabled=true
    web_port=6080
    stats_credentials="stats:admin"
}

mysql_variables =
{
    threads = 1000
    max_connections = 2000
    default_query_delay= 0
    default_query_timeout=1
    have_compress=true
    poll_timeout=2000
    interfaces="0.0.0.0:6033;/tmp/proxysql.sock"
    default_schema="information_schema"
    stacksize=1048576
    server_version="5.7.22"
    connect_timeout_server=10000
    monitor_history=60000
    monitor_connect_interval=200000
    monitor_ping_interval=200000
    ping_interval_server_msec=5000
    ping_timeout_server=200
    commands_stats=true
    sessions_sort=true
    monitor_username="root"
    monitor_password="password"
    monitor_galera_healthcheck_interval=200
    monitor_galera_healthcheck_timeout=80
}

mysql_servers =
(
  {
    address = "ip_real_mysql_server",
    port = 3306,
    max_connections = 10000,
    host_group = 1,
  })
mysql_users =
(
  {
    username = "user",
    password = "pass",
    default_hostgroup = 1,
    transaction_persistent = 0,
    active = 1,
  })

© Habrahabr.ru