Здравствуйте, меня зовут Александр Яковлев, я работаю в компании Ситимобил и занимаюсь эксплуатацией. Сегодня я расскажу про очень интересный продукт ProxySQL — это высокопроизводительный MySQL Proxy, который умеет очень много — отлавливать и убивать запросы по маске, с помощью него можно искать sql injection, дублировать нагрузку и много другое. Я расскажу о нашем опыте работы с ним.
С описанной ниже ситуацией рано или поздно сталкивается любой крупный IT-проект, развитие которого начиналось с пары серверов. Представим, что в проекте сначала была только одна база данных — мастер-сервер. Постепенно к нему добавили кучу слейвов. Потом внедрили шардинг.
И в один прекрасный день нагрузка внезапно возрастает в 10 раз. Например, потому что упал ваш основной конкурент, и клиенты хлынули к вам. В этот момент вам кажется, что можно элементарно масштабировать нагрузку добавлением веб-серверов. Но после того, как вы сделали это, возникает неприятная ситуация.
Рассмотрим на примере одного мастера. Допустим, у вас 50 web серверов и на каждом 200 php-fpm процессов. Тогда в мастер прилетит 50*200 коннектов, при этом в каждый слейв придет 50*200/количество слейвов (если, конечно же, в haproxy настроен roundrobin) — смотрите картинку ниже. Конечно, 10 тыс. коннектов в мастер — это много, но еще терпимо, а если будет 200 вебов, то количество коннектов будет еще больше, а один коннект = один тред.
Именно в это бутылочное горлышко мы уперлись.
![image](https://habrastorage.org/r/w780q1/getpro/habr/post_images/7b2/bb3/e2b/7b2bb3e2b3ff265076397aece6d0da81.jpg)
Дальше мы стали рассуждать: коннект в мастер устанавливается в коде всегда, но нужен ли он всем fpm процессам? Скорее всего, нет. Мы заметили, что большое количество persist-коннектов в мастер просто висят в слипах. И решили, что нам нужно демультиплексирование.
![image](https://habrastorage.org/r/w780q1/getpro/habr/post_images/df8/8e7/595/df88e75958437543e14e00a08603a7b8.jpg)
Для этого мы обратили внимание на продукт под названием ProxySQL. Он работает как обычный reverse proxy: к нему устанавливаются подключения, и он перераспределяет трафик по определенным правилам, указанным в конфигурации.
Мы установили ProxySQL на всех наших веб-серверах, а в конфигурации приложения прописали, что обращение в мастер-базу выполняется по адресу 127.0.0.1. Если раньше 200 FPM-воркеров на каждом веб-сервере означали 200 подключений к мастер-базе от этой машины, то теперь ситуация изменилась. Эти 200 подключений приходят в ProxySQL, а наружу в разное время выходят 50-70. То есть ProxySQL умеет многократно использовать уже установленные подключения.
Благодаря демультиплексированию мы на всех мастерах сократили количество подключений в 3-10 раз, график current connections одного из мастеров смотрите ниже.
![image](https://habrastorage.org/r/w1560/getpro/habr/post_images/87b/285/e02/87b285e02729e752ccf931994d4253e6.png)
Благодаря ProxySQL мы избавились от вышеописанного бутылочного горлышка. Но это не единственный рабочий процесс, который мы улучшили с помощью этого инструмента.
Второй процесс мы еще не доделали, но очень близки к завершению. С помощью ProxySQL мы планируем дублировать реальную нагрузку в тестовую среду. Это нужно для проверки новых фич боевым трафиком.
Как это будет реализовано? Приложение идет в ProxySQL, а тот отправляет трафик по двум маршрутам: в боевые базы данных, чтобы приложение функционировало, и в тестовую среду для проверки новых фич под нагрузкой.
Особенность ProxySQL в том, что есть его конфиг, который в нашем случае выкатывается через puppet (для puppet есть модуль ProxySQL), но еще есть понятие зоны runtime, когда для того, чтобы внести изменение (добавить сервер, добавить пользователя, удалить сервер и пользователя), не нужен привычный рестарт/релоад., Все делается через консоль ProxySQL, например так.
Более подробно конечно же в официальной документации proxysql.com/documentation
Спасибо за внимание.
Наш конфиг, которым мы решили описанную выше задачу смотрите ниже.
Наш конфиг
С описанной ниже ситуацией рано или поздно сталкивается любой крупный IT-проект, развитие которого начиналось с пары серверов. Представим, что в проекте сначала была только одна база данных — мастер-сервер. Постепенно к нему добавили кучу слейвов. Потом внедрили шардинг.
И в один прекрасный день нагрузка внезапно возрастает в 10 раз. Например, потому что упал ваш основной конкурент, и клиенты хлынули к вам. В этот момент вам кажется, что можно элементарно масштабировать нагрузку добавлением веб-серверов. Но после того, как вы сделали это, возникает неприятная ситуация.
Рассмотрим на примере одного мастера. Допустим, у вас 50 web серверов и на каждом 200 php-fpm процессов. Тогда в мастер прилетит 50*200 коннектов, при этом в каждый слейв придет 50*200/количество слейвов (если, конечно же, в haproxy настроен roundrobin) — смотрите картинку ниже. Конечно, 10 тыс. коннектов в мастер — это много, но еще терпимо, а если будет 200 вебов, то количество коннектов будет еще больше, а один коннект = один тред.
Именно в это бутылочное горлышко мы уперлись.
![image](https://habrastorage.org/getpro/habr/post_images/7b2/bb3/e2b/7b2bb3e2b3ff265076397aece6d0da81.jpg)
Дальше мы стали рассуждать: коннект в мастер устанавливается в коде всегда, но нужен ли он всем fpm процессам? Скорее всего, нет. Мы заметили, что большое количество persist-коннектов в мастер просто висят в слипах. И решили, что нам нужно демультиплексирование.
![image](https://habrastorage.org/getpro/habr/post_images/df8/8e7/595/df88e75958437543e14e00a08603a7b8.jpg)
Для этого мы обратили внимание на продукт под названием ProxySQL. Он работает как обычный reverse proxy: к нему устанавливаются подключения, и он перераспределяет трафик по определенным правилам, указанным в конфигурации.
Мы установили ProxySQL на всех наших веб-серверах, а в конфигурации приложения прописали, что обращение в мастер-базу выполняется по адресу 127.0.0.1. Если раньше 200 FPM-воркеров на каждом веб-сервере означали 200 подключений к мастер-базе от этой машины, то теперь ситуация изменилась. Эти 200 подключений приходят в ProxySQL, а наружу в разное время выходят 50-70. То есть ProxySQL умеет многократно использовать уже установленные подключения.
Благодаря демультиплексированию мы на всех мастерах сократили количество подключений в 3-10 раз, график current connections одного из мастеров смотрите ниже.
![image](https://habrastorage.org/getpro/habr/post_images/87b/285/e02/87b285e02729e752ccf931994d4253e6.png)
Благодаря 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;"
Более подробно конечно же в официальной документации proxysql.com/documentation
Спасибо за внимание.
Наш конфиг, которым мы решили описанную выше задачу смотрите ниже.
Наш конфиг
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,
})