Подключение ProxySQL
Внимание
Данная статья документации написана нейросетью. Возможны некоторые расхождения и неточность информации!
Руководство по подключению и управлению ProxySQL в IO7 фреймворке.
Обзор
ProxySQL — прокси-сервер для MySQL/PostgreSQL, обеспечивающий:
- Read/Write split — автоматическая маршрутизация SELECT на реплики,写操作 на мастер
- Connection pooling — переиспользование соединений
- Query routing — маршрутизация по регулярным выражениям
- Health checking — автоматическое отключение нерабочих серверов
- Statistics — сбор метрик по соединениям и запросам
Архитектура
┌─────────────┐ ┌──────────┐ ┌───────────────┐
│ Приложение │───▶│ ProxySQL │───▶│ MySQL Master │
│ (IO7) │ │ (6033) │ │ (hostgroup 0) │
└─────────────┘ │ │ └───────────────┘
│ │ ┌───────────────┐
│ (6032) │───▶│ MySQL Replica │
│ admin │ │ (hostgroup 1) │
└──────────┘ └───────────────┘
ProxySQLManager
Пространство имён: IO\Database
Конструктор
public function __construct(?array $adminConfig = null)
| Параметр | Тип | Значение по умолчанию | Обязателен | Описание |
|---|---|---|---|---|
$adminConfig | ?\array | null | Нет | Конфигурация админского подключения (null для отложенной инициализации) |
Статические методы
getInstance()
public static function getInstance(): self
Получить синглтон-экземпляр менеджера.
fromConfig()
public static function fromConfig(
string $adminHost,
int $adminPort = 6032,
string $adminUser = 'admin',
string $adminPass = 'admin'
): self
Создать менеджер из параметров подключения.
| Параметр | Тип | Значение по умолчанию | Обязателен | Описание |
|---|---|---|---|---|
$adminHost | string | — | Да | Адрес ProxySQL admin |
$adminPort | int | 6032 | Нет | Порт ProxySQL admin |
$adminUser | string | 'admin' | Нет | Логин admin |
$adminPass | string | 'admin' | Нет | Пароль admin |
$manager = ProxySQLManager::fromConfig('127.0.0.1', 6032, 'admin', 'admin');
Методы инициализации
init()
public function init(
string $host,
int $port = 6032,
string $user = 'admin',
string $password = 'admin'
): self
Инициализация с админским подключением.
$manager = ProxySQLManager::getInstance();
$manager->init('127.0.0.1', 6032, 'admin', 'admin');
Управление серверами
addServer()
public function addServer(
string $host,
int $port,
int $weight = 1,
int $hostgroup = 0,
int $maxConnections = 100
): bool
Добавить сервер в hostgroup.
| Параметр | Тип | Значение по умолчанию | Обязателен | Описание |
|---|---|---|---|---|
$host | string | — | Да | Адрес сервера |
$port | int | — | Да | Порт сервера |
$weight | int | 1 | Нет | Вес сервера (для балансировки) |
$hostgroup | int | 0 | Нет | ID hostgroup |
$maxConnections | int | 100 | Нет | Макс. количество соединений |
// Мастер в hostgroup 0
$manager->addServer('192.168.1.10', 3306, 1, 0, 100);
// Реплики в hostgroup 1
$manager->addServer('192.168.1.11', 3306, 1, 1, 100);
$manager->addServer('192.168.1.12', 3306, 1, 1, 100);
removeServer()
public function removeServer(string $host, int $port): bool
Удалить сервер.
$manager->removeServer('192.168.1.12', 3306);
getServers()
public function getServers(?int $hostgroup = null): array
Получить серверы. Если $hostgroup null — все серверы.
$allServers = $manager->getServers();
$replicas = $manager->getServers(1);
setOffline() / setOnline()
public function setOffline(string $host, int $port): bool
public function setOnline(string $host, int $port): bool
Переключить статус сервера.
// Взять сервер на обслуживание
$manager->setOffline('192.168.1.12', 3306);
// Вернуть в работу
$manager->setOnline('192.168.1.12', 3306);
Управление правилами
addQueryRule()
public function addQueryRule(
string $pattern,
int $destinationHostgroup,
int $apply = 1,
?int $ruleId = null
): bool
Добавить правило маршрутизации.
| Параметр | Тип | Значение по умолчанию | Обязателен | Описание |
|---|---|---|---|---|
$pattern | string | — | Да | Регулярное выражение для матча запроса |
$destinationHostgroup | int | — | Да | ID целевого hostgroup |
$apply | int | 1 | Нет | Применять ли правило (1 — да, 0 — нет) |
$ruleId | ?\int | null | Нет | ID правила (null — генерируется из time()) |
// SELECT на реплики (hostgroup 1)
$manager->addQueryRule('^SELECT', 1);
// WRITE на мастер (hostgroup 0)
$manager->addQueryRule('^(UPDATE|INSERT|DELETE)', 0);
removeQueryRule()
public function removeQueryRule(int $ruleId): bool
Удалить правило.
$manager->removeQueryRule(100);
getQueryRules()
public function getQueryRules(): array
Получить все правила.
Настройка Read/Write
setupReadWriteSplit()
public function setupReadWriteSplit(
string $masterHost,
int $masterPort,
array $replicaHosts,
int $replicaPort = 3306
): self
Автоматическая настройка разделения чтения/записи.
| Параметр | Тип | Значение по умолчанию | Обязателен | Описание |
|---|---|---|---|---|
$masterHost | string | — | Да | Адрес мастера |
$masterPort | int | — | Да | Порт мастера |
$replicaHosts | array<int, string> | — | Да | Адреса реплик |
$replicaPort | int | 3306 | Нет | Порт реплик |
$manager->setupReadWriteSplit(
'192.168.1.10', // мастер
3306,
[ // реплики
'192.168.1.11',
'192.168.1.12',
],
3306
);
Создаёт правила:
^SELECT→ hostgroup 1 (реплики)^(UPDATE|INSERT|DELETE|ALTER|CREATE|DROP)→ hostgroup 0 (мастер)^START TRANSACTION,^COMMIT,^ROLLBACK→ hostgroup 0
Статистика
getStats()
public function getStats(): array
Получить статистику ProxySQL.
Возвращает:
[
'connection_pool' => [...], // Пул соединений
'query_digest' => [...], // Дайджест запросов (топ 20 по времени)
'global' => [...], // Глобальная статистика
]
$stats = $manager->getStats();
// Пул соединений
foreach ($stats['connection_pool'] as $pool) {
echo "Hostgroup {$pool['hostgroup']}: {$pool['ConnUsed']} active\n";
}
// Топ запросов
foreach ($stats['query_digest'] as $query) {
echo "Query: {$query['digest_text']}\n";
echo " Count: {$query['count_star']}\n";
echo " Time: {$query['sum_time']} мкс\n";
}
Сохранение конфигурации
saveConfig()
public function saveConfig(): void
Применить и сохранить конфигурацию на диск.
Важно: Вызывайте после всех изменений серверов и правил.
$manager->addServer('192.168.1.10', 3306, 1, 0);
$manager->addQueryRule('^SELECT', 1);
$manager->saveConfig();
Переменные окружения
# ProxySQL (перекрывает движок — подключение идёт через прокси)
PROXYSQL_HOST=proxysql
PROXYSQL_PORT=6033
# ProxySQL Admin (для управления через ProxySQLManager)
PROXYSQL_ADMIN_HOST=127.0.0.1
PROXYSQL_ADMIN_PORT=6032
PROXYSQL_ADMIN_USER=admin
PROXYSQL_ADMIN_PASS=admin
# Движок БД за ProxySQL
DB_ENGINE=mysql
Настройка Read/Write разделения
Полный пример
use IO\Database\ProxySQLManager;
use IO\Database\ConnectionManager;
use IO\Database\DB;
// 1. Инициализация ProxySQLManager
$manager = ProxySQLManager::fromConfig('127.0.0.1', 6032, 'admin', 'admin');
// 2. Настройка серверов
$manager->setupReadWriteSplit(
'192.168.1.10', // мастер
3306,
['192.168.1.11', '192.168.1.12'], // реплики
3306
);
// 3. Сохранение конфигурации
$manager->saveConfig();
// 4. Подключение приложения через ProxySQL
DB::init(ConnectionManager::mysqlConfig(
'127.0.0.1', // ProxySQL адрес
'mydb',
'app_user',
'secret',
6033 // ProxySQL порт для приложений
));
// 5. Все запросы автоматически маршрутизируются
$users = DB::table('users')->where('active', 1)->get(); // → реплика
DB::table('users')->insert([...]); // → мастер
Примеры
Добавление серверов
$manager = ProxySQLManager::fromConfig('127.0.0.1');
// Мастер
$manager->addServer('db-master.local', 3306, 1, 0, 100);
// Реплики
$manager->addServer('db-replica1.local', 3306, 1, 1, 100);
$manager->addServer('db-replica2.local', 3306, 2, 1, 100); // больший вес
$manager->saveConfig();
Кастомные правила маршрутизации
// Тяжёлые SELECT на выделенную реплику
$manager->addQueryRule('^SELECT.*JOIN.*orders', 2, 1, 100);
// Запросы к определённой таблице на мастер
$manager->addQueryRule('^SELECT.*FROM critical_table', 0, 1, 101);
// Транзакции всегда на мастер
$manager->addQueryRule('^START TRANSACTION', 0, 1, 200);
$manager->addQueryRule('^BEGIN', 0, 1, 201);
Обслуживание сервера
// Взять реплику на обслуживание
$manager->setOffline('db-replica2.local', 3306);
// Проверить статус
$servers = $manager->getServers(1);
foreach ($servers as $server) {
echo "{$server['hostname']}: {$server['status']}\n";
}
// Вернуть в работу
$manager->setOnline('db-replica2.local', 3306);
$manager->saveConfig();
Мониторинг
$stats = $manager->getStats();
// Проверка пула соединений
foreach ($stats['connection_pool'] as $pool) {
if ($pool['ConnUsed'] > $pool['max_connections'] * 0.8) {
// Предупреждение: высокая нагрузка
error_log("Высокая нагрузка на hostgroup {$pool['hostgroup']}");
}
}
// Топ медленных запросов
foreach (array_slice($stats['query_digest'], 0, 5) as $query) {
echo "Медленный запрос: {$query['digest_text']}\n";
echo " Среднее время: " . ($query['sum_time'] / $query['count_star']) . " мкс\n";
}
Рекомендации
- Всегда вызывайте
saveConfig()после изменений серверов или правил - Используйте
setupReadWriteSplit()для типичных конфигураций master-slave - Мониторьте статистику через
getStats()для выявления проблем - Используйте
setOffline()перед обслуживанием серверов - Разделяйте admin порт (6032) и proxy порт (6033)
- Тестируйте правила на dev-окружении перед продакшеном
FAQ
Как узнать текущую конфигурацию ProxySQL?
$servers = $manager->getServers();
$rules = $manager->getQueryRules();
echo "Серверы:\n";
foreach ($servers as $s) {
echo " {$s['hostname']}:{$s['port']} (hg={$s['hostgroup_id']}, status={$s['status']})\n";
}
echo "Правила:\n";
foreach ($rules as $r) {
echo " #{$r['rule_id']}: {$r['match_pattern']} → hg={$r['destination_hostgroup']}\n";
}
Как настроить балансировку между репликами?
// Реплика 1 с весом 1, реплика 2 с весом 2
$manager->addServer('replica1.local', 3306, 1, 1, 100);
$manager->addServer('replica2.local', 3306, 2, 1, 100);
Что делать при ошибке подключения?
try {
$manager = ProxySQLManager::fromConfig('127.0.0.1');
} catch (\Exception $e) {
error_log("ProxySQL недоступен: " . $e->getMessage());
// Fallback на прямое подключение
DB::init(ConnectionManager::mysqlConfig('db-master.local', 'mydb', 'root', 'secret'));
}
Как сбросить конфигурацию ProxySQL?
$manager->adminConnection->statement("DELETE FROM mysql_servers");
$manager->adminConnection->statement("DELETE FROM mysql_query_rules");
$manager->saveConfig();
Поддерживается ли PostgreSQL через ProxySQL?
Да, начиная с версии 2.0 ProxySQL поддерживает PostgreSQL. Используйте DB_ENGINE=pgsql и соответствующий порт.