MySql HandlerSocket

Введение в HandlerSocket: описание протокола и расширения php-handlersocket

// Декабрь 20th, 2010 // Highload, Memcached, MySQL, NoSQL, PHP, Ruby, Ubuntu

Сейчас на волне популярности различных NoSQL решений создана интересная разработка – плагин для MySQL, реализущий NoSQL доступ к нему, представленный 20 октября 2010г Yoshinori Matsunobu. В этой заметке я рассмотрю сам протокол, а также его реализацию в php-расширении php-handlersocket.

Что такое HandlerSocket

HandlerSocket – это протокол, реализованный в одноимённом плагине для РСУБД MySQL, позволяющий использовать NoSQL методику для доступа к данным, хранящимся в InnoDB таблицах. Подробнее вы можете прочитать в этой заметке. Основаная причина, по которой используют NoSQL решения – это очень быстрый поиск по первичному ключу.

HandlerSocket работает как демон внутри процесса mysql, принимая TCP соединения и выполняя запросы клиентов. Он не поддерживает SQL запросы, вместо этого он предоставляет простой язык запросов для CRUD операций с таблицами. Именно поэтому, он гораздо быстрее mysqld/libmysql в некоторых случаях:

  • HandlerSocket оперирует данными без парсинга SQL запроса, что приводит к уменьшению загрузки процессора.
  • Он поддерживает пакетное выполнение запросов. Можно отправить несколько запросов сразу и получить результат за один раз, что опять же снижает нагрузку на процессор и на сеть.
  • Протокол HandlerSocket более компактный чем у mysql/libmysql, что приводит к сокращению нагрузки на сеть.

Текущая версия работает только под GNU/Linux, версия под Windows пока не планируется.

Чтобы не повторяться, я попытаюсь сравнить HandlerSocket с другими решениями.

HandlerSocket VS Memcached

+ Более высокая скорость, как показывают тесты.

+ Постоянное хранение данных.

HandlerSocket работает быстрее, так зачем теперь нужен Memcached? Ах да, мы же может создать кластер сервером memcached. Но никто нам не мешает наладить репликацию и получить тоже самое ;-)

HandlerSocket VS MongoDB, CouchDB, Redis …

+ Мы используем одну базу для SQL-доступа, и для NoSQL-доступа. Не нужно никакое другое ПО для обслуживание данных.

+ Всё что доступно для MySQL (репликация, партиционирование, и огромное количество наработок) по факту доступно и для HandlerSocket, т.к. БД одна и та же.

Недостатки HandlerSocket

– Свой язык запросов, не совместимый с другими NoSQL решениями. Но, т.к. они и между собой не совместимы, то я не считаю это серьезным недостатком. Зато есть клиенты под большинство языков. (PHP, Perl, Ruby).

– Индексы хранятся в памяти. Подключаем SSD, и это перестает быть недостатком.

– Для использования с MySQL необходимо скачать исходники и бинарники, сам плагин, скомпилить его и подключить. Но можно просто скачать Percona Server и установить его по моему мануалу. HandlerSocket уже есть в нём (для всех кроме lucid).

-/+ Даже не знаю, к чему это отнести. У HandlerSocket один порт – для запросов на чтение, а второй для запросов на запись.

Протокол HandlerSocket

Основные сведения

– Протокол HandlerSocket строковый. Каждая строка заканчивается символом LF(0x0a).

– Каждая строка состоит из конкатенации токенов, разделённых символом HT(0×09).

– Токеном может быть либо символ NULL либо закодированная строка. Важно отличать NULL от пустой строки, т.к. БД тоже отличают их.

– NULL записывается одним символом NUL(0×00).

– Закодированная строка это строка, которая кодируется по следующим правилам:

– Символы находящиеся в диапазоне [0x10 – 0xff] не кодируются.

– Символы в диапазоне [0x00 – 0x0f] обозначаются префиксом 0×01 и сдвигаются на 0×40. Например, 0×03 кодируется как 0×01 0×43.

– Не забывайте, что строки могут быть пустыми. Последовательность 0×09 0×09 значит, что между двумя символами содержится пустая строка. Последовательность 0×09 0x0a значит, что в конце последовательности есть пустая строка (в тексте обозначена как пробел).

Запрос и ответ

– Протокол HandlerSocket – это простой протокол типа «запрос-ответ». После установления соединения, клиент посылает запрос, а сервер возвращает ему ответ.

– И запрос и ответ представляют собой одну строку.

– Запросы могут быть конвейерными; Это значит, что вы можете посылать много запросов сразу (много строк) за один раз, и получать ответ сразу на все запросы. По сути это аналог Multi-GET в Memcache протоколе.

Запрос на открытия индекса OPEN_INDEX

Запрос ‘open_index’ имеет следующий синтаксис.

P <indexid> <dbname> <tablename> <indexname> <columns>

– <indexid> это уникальный номер индекса.

– <dbname>, <tablename>, и <indexname> это строки. Для открытия индекса на первичный ключ, используйте PRIMARY в качестве <indexname>.

– <columns> список колонок, резделённых запятыми.

Когда вызывается команда открытия индекса, плагин HandlerSocket открывает указанный индекс и хранит его до закрытия соединения с клиентом. Каждый открытый индекс имеет свой уникальный <indexid>. Если индекс уже открыт, старый индекс закрывается. Вы можете открывать разные индексы с одинаковыми значениями <dbname> <tablename> <indexname> много раз, возможно с отличающимся набором колонок <clumns>. Для увеличения производительности старайтесь использовать маленькие значения <indexid>.

Запрос на получение данных FIND

Запрос «find» имеет следующий синтаксис.

<indexid> <op> <vlen> <v1> … <vn> <limit> <offset>

– <indexid> это номер индекса, по которому необходимо производить поиск. Он должен быть таким же, как и <indexid> в команде open_index.

– <op> определяет операцию сравнения, которая будет производиться при поиске. Текущая версия поддерживает следующие операции: specifies ‘=’, ‘>’, ‘>=’, ‘<’, и ‘<=’.

– <vlen> определяет длину строки параметров <v1> … <vn>. Их количество должно быть меньше или равно количеству колонок в индексе, определённых в предшествующей команде open_index.

– <v1> … <vn>определяет значения колонок, которые нужно искать.

– <limit> и <offset> это числа. Аналогичны LIMIT и OFFSET параметрам в SQL-запросе. Это необязательные параметры. Когда они опущены, используются значения 1 и 0 для них.

Обновление/удаление данных FIND_MODIFY

Запрос ‘find_modify’ имеет следующий синтаксис.

<indexid> <op> <vlen> <v1> … <vn> <limit> <offset> <mop> <m1> … <mk>

– <mop> это КОП (код операции) ‘U’ (при обновлении) или ‘D’ (при удалении). Кто изучал Ассемблер, сразу его вспомнит :-)

– <m1> … <mk> определяют значения для колонок, которые надо установить. Количество <m1> …

<mk> должно быть меньше или равно количеству колонок <columns> в открытом индексе ‘open_index’. Если <mop> равен ‘D’, эти параметры игнорируются.

Вставка данных INSERT

Запрос ‘insert’ имеет следующий синтаксис.

<indexid> ‘+’ <vlen> <v1> … <vn>

– <vlen> определяет длину строки параметров <v1> … <vn>. Их количество должно быть меньше или равно количеству колонок в индексе, определённых в предшествующей команде open_index.

– <v1> … <vn>определяет значения колонок, которые нужно записать. Для тех колонок, которых нет в <columns> будут выставлены значения по-умолчанию.

Формат ответа от HandlerSocket сервера

Сервер HandlerSocket возвращает ответ следующего синтаксиса для каждого запроса.

<errorcode> <numcolumns> <r1> … <rn>

– <errorcode> это статус ответа. 0 – значит запрос выполнен успешно

другое значение – код ошибки.

– <numcolumns> это количество колонок в результирующем наборе.

– <r1> … <rn> это результирующий набор. Длина <r1> … <rn> всегда кратна <numcolumns>. Она либо равна <r1> … <rn> либо пуста (empty).

Если <errorcode> не равен нулю, <numcolumns> всегда равно 1 а в <r1> содержится текст сообщения об ошибке, но иногда <r1> может не возвращаться.

Ответ на запрос OPEN_INDEX

Если индекс открыт успешно, HandlerSocket возвращает следующую строку.

0 1

Ответ на запрос FIND

Если запрос ‘find’ выполнен успешно, HandlerSocket возвратит строку следующего формата.

0 <numcolumns> <r1> … <rn>

– <numcolumns> всегда равно количеству колонок <columns> соответствующего запроса ‘open_index’.

– <r1> … <rn> это результирующий набор. Если N строк найдено, длина <r1>… <rn> будет равна ( <numcolumns> * N ).

Ответ на запрос FIND_MODIFY

Если запрос ‘find_modify’ выполнен успешно, HandlerSocket возвратит строку такого вида:

0 1 <numfound>

<numfound>- это количество строк, подошедших под условие запроса.

Ответ на запрос INSERT

Если запрос ‘insert’ выполнен успешно, HanderSocket возвратит строку такого вида:

0 1

Расширение PHP-HandlerSocket

Само расширение можно найти в GitHub’е: http://code.google.com/p/php-handlersocket/

Как вы уже знаете, HandlerSocket демон использует два порта. Один (9998) для чтения и другой (9999) для записи. Поэтому при инстанцировании объекта класса, необходимо использовать тот или иной порт, в зависимости от типа запроса.

Структура класса:

HandlerSocket {
    /* Constants */
    const HandlerSocket::PRIMARY;

    /* Methods */
    __construct  ( string $host, int $port, [ array $options ])
    public bool openIndex ( int $id, string $db, string $table, string $index, string $fields )
    public mixed executeSingle ( int $id, string $op, array $fields [, int $limit, int $skip, strint $modop, array $values ] )
    public mixed executeMulti ( array $requests )
    public int executeUpdate ( int $id, string $op, array $fields, array $values [, int $limit, int $skip ] )
    public int executeDelete ( int $id, string $op, array $fields [, int $limit, int $skip ] )
    public bool executeInsert ( int $id, array $values )
    public string getError ( void )
}

Дальше предлагаю посмотреть на пример из документации, чтобы понять, как именно работает расширение.

openIndex(1, $dbname, $table, HandlerSocket::PRIMARY, 'k,v')))
{
    echo $hs->getError(), PHP_EOL;
    die();
}

$retval = $hs->executeSingle(1, '=', array('k1'), 1, 0);

var_dump($retval);

// Чтение данных Multi-Get (find по тому же индексу)
$retval = $hs->executeMulti(
    array(array(1, '=', array('k1'), 1, 0),
          array(1, '=', array('k2'), 1, 0)));

var_dump($retval);

unset($hs);

// Обновление данных (open_index & find_modify mod = U)
$hs = new HandlerSocket($host, $port_wr);
if (!($hs->openIndex(2, $dbname, $table, '', 'v')))
{
    echo $hs->getError(), PHP_EOL;
    die();
}

if (!($hs->executeUpdate(2, '=', array('k1'), array('V1'), 1, 0)))
{
    echo $hs->getError(), PHP_EOL;
    die();
}

unset($hs);

// Вставка данных (open_index & insert)
$hs = new HandlerSocket($host, $port_wr);
if (!($hs->openIndex(3, $dbname, $table, '', 'k,v')))
{
    echo $hs->getError(), PHP_EOL;
    die();
}

if (!($hs->executeInsert(3, array('k2', 'v2'))))
{
    echo $hs->getError(), PHP_EOL;
}
if (!($hs->executeInsert(3, array('k3', 'v3'))))
{
    echo 'A', $hs->getError(), PHP_EOL;
}
if (!($hs->executeInsert(3, array('k4', 'v4'))))
{
    echo 'B', $hs->getError(), PHP_EOL;
}

unset($hs);

// Удаление данных (open_index & find_modify mod = D)
$hs = new HandlerSocket($host, $port_wr);
if (!($hs->openIndex(4, $dbname, $table, '', '')))
{
    echo $hs->getError(), PHP_EOL;
    die();
}

if (!($hs->executeDelete(4, '=', array('k2'))))
{
    echo $hs->getError(), PHP_EOL;
    die();
}

Драйвера для языков программирования

  • PHP
    http://openpear.org/package/Net_HandlerSocket
    http://github.com/tz-lom/HSPHP
    http://code.google.com/p/php-handlersocket/
  • Java
    http://code.google.com/p/hs4j/
    http://code.google.com/p/handlersocketforjava/
  • Python
    https://code.launchpad.net/~songofacandy/+junk/pyhandlersocket
  • Ruby
    https://github.com/winebarrel/ruby-handlersocket
    https://github.com/miyucy/handlersocket
  • JavaScript(Node.js)
    https://github.com/koichik/node-handlersocket

Вообще мне очень нравится эта разработка. Если у кого-то есть что добавить, буду рад услышать ваше мнение.