Доступ readonly через MySQL-Proxy
Ubuntu 14.04
mysql-proxy 0.8.1
Используем MySQL-Proxy для следующей задачи: Нужно поднять зеркало сайта на CMS Drupal.
Т.к. при работе CMS вносит изменения в БД, репликация постоянно ломается, как решение - игнорировать все запросы кроме SELECT.
Таким образом получаем readonly доступ к базе, но не вызывая ошибок, как если бы ограничили права.
Установка
Установка
sudo apt-get install mysql-proxy
Настройка
/etc/default/mysql-proxy
ENABLED="true" OPTIONS=" --log-file=/var/log/mysql-proxy.log --log-level=message --user=nobody --event-threads=8 --keepalive --admin-username=admin --admin-password=Pa$sWoRd --admin-lua-script=/usr/lib/mysql-proxy/lua/admin.lua --proxy-lua-script=/etc/mysql/mysql-proxy.lua --proxy-skip-profiling=true "
По умолчанию порт для подключения 4040, админский порт 4041.
По умолчанию proxy-backend-addresses = 127.0.0.1:3306
Скрипт admin-lua-script указываем из установочного пакета, без него не запустится
(critical) admin-plugin.c:579: --admin-lua-script needs to be set, <install-dir>/lib/mysql-proxy/lua/admin.lua may be a good value
Стоит отметить так же опции proxy-skip-profiling и event-threads, для нагруженных систем актуально, т.к. прокси заметно будет тормозит работу с БД.
Ротация лога. Из пакета конфига logrotate нет, поэтому создадим свой
/etc/logrotate.d/mysql-proxy
/var/log/mysql-proxy.log { size 10M missingok notifempty create 640 root root postrotate /bin/kill -HUP `cat /var/run/mysql-proxy.pid 2>/dev/null` 2>/dev/null || true endscript }
Создадим пока пустой файл
sudo touch /etc/mysql/mysql-proxy.lua sudo chmon 640 /etc/mysql/mysql-proxy.lua
Запускаем
/etc/init.d/mysql-proxy start
Проверяем что приложение запустилось
ps aux | grep mysql-proxy
LUA
Все манипуляции с запросами выполняет скрипт на языке LUA. Его придется написать самостоятельно или найти что-то готовое. Из пакета можно взять парочку примеров и библиотек
dpkg -L mysql-proxy | grep lua
В скрипте используются встроенные функции, которые вызываются на разных этапах, см. документацию
Для моей задачи интересна только одна функция read_query - вызывается на этапе чтения запроса от клиента и может возвращать:
- <ничего не возвращаем> - Передаем запрос в MySQL как есть
- proxy.PROXY_SEND_QUERY - Передает запросы в MySQL из очереди proxy.queries
- proxy.PROXY_SEND_RESULT - Прерывает выполнение и возвращает клиенту сразу ответ (resultset) без запроса в MySQL
/etc/mysql/mysql-proxy.lua
function read_query( packet ) if packet:byte() == proxy.COM_QUERY then local tokenizer = require("proxy.tokenizer") local tokens = tokens or assert(tokenizer.tokenize(packet:sub(2))) local stmt = tokenizer.first_stmt_token(tokens) -- print("Query:" .. string.sub(packet, 2)) -- print("stmt_name:" .. stmt.token_name) -- print("stmt_text:" .. stmt.text) if stmt.token_name ~= "TK_SQL_SELECT" and stmt.token_name ~= "TK_SQL_SHOW" and stmt.token_name ~= "TK_SQL_SET" then -- print("intercepting: " .. string.sub(packet, 2)) proxy.response.type = proxy.MYSQLD_PACKET_RAW proxy.response.packets = { "\0" .. -- field-count 0 "\0" .. -- affected rows "\0" .. -- insert-id "\002\0" .. -- server-status "\0\0" .. -- warning-count string.char(27) .. "mysql-proxy fake response" } return proxy.PROXY_SEND_RESULT end end end
Разберем:
- if packet:byte() == proxy.COM_QUERY then - Первый байт пакета определяет его тип, см. подробнее. Мне нужны только COM_QUERY
- tokens - Вся команда разбивается на токены с помощью библиотеки tokenizer из пакета, см. объяснение
For example, the query SELECT 1 FROM dual will be returned as the following tokens: 1: text select token_name TK_SQL_SELECT' token_id 204 2: text 1 token_name TK_INTEGER token_id 11 3: text from token_name TK_SQL_FROM token_id 105 4: text dual token_name TK_SQL_DUAL token_id 87
- stmt - Только первый токен
- proxy.response.type - Формируем ответ (resultset) для клиента. Бывает следующих типов
- MYSQLD_PACKET_OK - Обычный ответ, в ответе таблица. т.е. обязательно нужно вернуть таблицу, можно даже без записей. Пример:
proxy.response.resultset = { fields = { { type = proxy.MYSQL_TYPE_LONG, name = "numbers", }, }, rows = { { 0, 1, 2, 3} } }
- MYSQLD_PACKET_ERR - Ошибка. Код и текст ошибки указать
- MYSQLD_PACKET_RAW - Формируем ответ самостоятельно по байтам, соответственно протоколу MySQL
Для отладки можно вставить print вывод на stdout, поэтому нужно запустить mysql-proxy из консоли руками со всеми опциями.
Обсуждение