Статьи

Высокая доступность с MySQL Fabric: Часть II

Первоначально написано Фернандо Ипар и Мартин Арриета

Это третий пост в нашей серии MySQL Fabric. Если вы пропустили предыдущие два, мы начали с общего представления , а затем обсудили функции высокой доступности MySQL Fabric (HA) . MySQL Fabric был RC, когда мы начали эту серию, но недавно она стала GA. Вы можете прочитать пресс-релиз здесь , и увидеть эту запись в блоге от Oracle  Mats Kindahl для более подробной информации. В нашем предыдущем посте мы показали простую настройку HA, управляемую с помощью MySQL Fabric, включая некоторые базовые сценарии сбоев. Сегодня мы представим похожий сценарий с точки зрения разработчика приложения, используя Python Connectorдля примеров. Если вы будете следовать примерам этих постов, вы заметите, что UUID для серверов будет меняться. Это потому, что мы перестраиваем среду между запусками. Символические названия остаются неизменными. Тем не менее, вот наша обычная настройка 3 узла:

[vagrant@store ~]$ mysqlfabric group lookup_servers mycluster
Command :
{ success     = True
  return      = [{'status': 'SECONDARY', 'server_uuid': '3084fcf2-df86-11e3-b46c-0800274fb806', 'mode': 'READ_ONLY', 'weight': 1.0, 'address': '192.168.70.101'}, {'status': 'SECONDARY', 'server_uuid': '35cc3529-df86-11e3-b46c-0800274fb806', 'mode': 'READ_ONLY', 'weight': 1.0, 'address': '192.168.70.102'}, {'status': 'PRIMARY', 'server_uuid': '3d3f6cda-df86-11e3-

Для наших тестов мы будем использовать этот простой скрипт:

import mysql.connector
from mysql.connector import fabric
from mysql.connector import errors
import time
config = {
    'fabric': {
        'host': '192.168.70.100',
        'port': 8080,
        'username': 'admin',
        'password': 'admin',
        'report_errors': True
    },
    'user': 'fabric',
    'password': 'f4bric',
    'database': 'test',
    'autocommit': 'true'
}
fcnx = None
print "starting loop"
while 1:
    if fcnx == None:
	print "connecting"
        fcnx = mysql.connector.connect(**config)
        fcnx.set_property(group='mycluster', mode=fabric.MODE_READWRITE)
    try:
	print "will run query"
        cur = fcnx.cursor()
        cur.execute("select id, sleep(0.2) from test.test limit 1")
        for (id) in cur:
            print id
	print "will sleep 1 second"
        time.sleep(1)
    except errors.DatabaseError:
        print "sleeping 1 second and reconnecting"
        time.sleep(1)
        del fcnx
        fcnx = mysql.connector.connect(**config)
        fcnx.set_property(group='mycluster', mode=fabric.MODE_READWRITE)
        fcnx.reset_cache()
        try:
            cur = fcnx.cursor()
            cur.execute("select 1")
        except errors.InterfaceError:
            fcnx = mysql.connector.connect(**config)
            fcnx.set_property(group='mycluster', mode=fabric.MODE_READWRITE)
            fcnx.reset_cache()

Этот простой скрипт запрашивает соединение MODE_READWRITE, а затем выдает выборки в цикле. Причина, по которой он запрашивает соединитель RW , заключается в том, что нам легче спровоцировать сбой, поскольку у нас есть два ВТОРИЧНЫХ узла, которые можно использовать для запросов, если мы запросим соединение MODE_READONLY . Выбор включает в себя короткий сон, чтобы было легче поймать его в SHOW PROCESSLIST . Для работы этому сценарию необходима таблица test.test, которая существует в группе mycluster . Выполнение следующих операторов в узле PRIMARY сделает это:

mysql> create database if not exists test;
mysql> create table if not exists test.test (id int unsigned not null auto_increment primary key) engine = innodb;
mysql> insert into test.test values (null);

Имея дело с неудачей

Когда все настроено, мы можем запустить скрипт, а затем вызвать ПЕРВИЧНЫЙ сбой. В этом случае мы смоделируем ошибку, закрыв на ней mysqld:

mysql> select @@hostname;
+-------------+
| @@hostname  |
+-------------+
| node3.local |
+-------------+
1 row in set (0.00 sec)
mysql> show processlist;
+----+--------+--------------------+------+------------------+------+-----------------------------------------------------------------------+----------------------------------------------+
| Id | User   | Host               | db   | Command          | Time | State                                                                 | Info                                         |
+----+--------+--------------------+------+------------------+------+-----------------------------------------------------------------------+----------------------------------------------+
|  5 | fabric | store:39929        | NULL | Sleep            |  217 |                                                                       | NULL                                         |
|  6 | fabric | node1:37999        | NULL | Binlog Dump GTID |  217 | Master has sent all binlog to slave; waiting for binlog to be updated | NULL                                         |
|  7 | fabric | node2:49750        | NULL | Binlog Dump GTID |  216 | Master has sent all binlog to slave; waiting for binlog to be updated | NULL                                         |
| 16 | root   | localhost          | NULL | Query            |    0 | init                                                                  | show processlist                             |
| 20 | fabric | 192.168.70.1:55889 | test | Query            |    0 | User sleep                                                            | select id, sleep(0.2) from test.test limit 1 |
+----+--------+--------------------+------+------------------+------+-----------------------------------------------------------------------+----------------------------------------------+
5 rows in set (0.00 sec)
[vagrant@node3 ~]$ sudo service mysqld stop
Stopping mysqld:                                           [  OK  ]

Пока это происходит, вот вывод из скрипта:

will sleep 1 second
will run query
(1, 0)
will sleep 1 second
will run query
(1, 0)
will sleep 1 second
will run query
(1, 0)
will sleep 1 second
will run query
sleeping 1 second and reconnecting
will run query
(1, 0)
will sleep 1 second
will run query
(1, 0)
will sleep 1 second
will run query
(1, 0)

«Спит 1 секунду и повторное подключение» означает строку сценарий получил исключение при выполнении запроса (когда ОСНОВНОЙ узел был остановлен, ждал одну секунды , а затем вновь. Следующие строки подтверждают , что все вернулось в нормальное состоянии после повторного соединения. Соответствующей кусок кода, который обрабатывает переподключение, это:

        fcnx = mysql.connector.connect(**config)
        fcnx.set_property(group='mycluster', mode=fabric.MODE_READWRITE)
        fcnx.reset_cache()

Если fcnx.reset_cache () не вызывается, драйвер больше не будет подключаться к серверу xml-rpc, а вместо этого будет использовать свой локальный кэш статуса группы. Поскольку основной узел находится в автономном режиме, это приведет к неудачной попытке повторного подключения. Сброс кеша заставляет драйвер подключаться к серверу xml-rpc и получать актуальную информацию о статусе группы. Если происходит больше сбоев и в группе нет ПЕРВИЧНОГО (или кандидата на продвижение) узла, появляется следующая ошибка:

will run query
(1, 0)
will sleep 1 second
will run query
sleeping 1 second and reconnecting
will run query
Traceback (most recent call last):
  File "./reader_test.py", line 34, in
    cur = fcnx.cursor()
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 1062, in cursor
    self._connect()
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 1012, in _connect
    exc))
mysql.connector.errors.InterfaceError: Error getting connection: No MySQL server available for group 'mycluster'

Работает без MySQL Fabric

Как мы уже говорили в предыдущих статьях, сервер XML-PRC может стать единой точкой отказа при определенных обстоятельствах. В частности, существует не менее двух проблемных сценариев, когда этот сервер не работает:

  • Когда узел выходит из строя
  • Когда предпринимаются новые попытки подключения

Первый случай достаточно очевиден. Если MySQL Fabric не запущен и узел выходит из строя, никаких действий не предпринимается, и клиенты будут получать сообщение об ошибке всякий раз, когда отправляют запрос отказавшему узлу. Это хуже, если ПЕРВИЧНЫЙпроисходит сбой, так как отказоустойчивости не произойдет, и кластер будет недоступен для записи. Второй случай означает, что пока MySQL Fabric не работает, новые подключения к группе установить невозможно. Это связано с тем, что при подключении к группе клиенты, поддерживающие MySQL Fabric, сначала подключаются к серверу XML-RPC, чтобы получить список узлов и ролей, и только затем используют свой локальный кэш для принятия решений. Одним из способов смягчения этого является использование пула соединений, что снижает необходимость в создании новых соединений и, следовательно, сводит к минимуму вероятность сбоя из-за сбоя MySQL Fabric. Это, конечно, предполагает, что что-то контролирует MySQL Fabric, гарантируя, что некоторый хост предоставляет услугу XML-PRC. Если это не так, сбой будет отложен, но в любом случае это произойдет. Вот пример того, что происходит, когда MySQL Fabric не работает иПЕРВИЧНЫЙ узел выходит из строя:

Traceback (most recent call last):
  File "./reader_test.py", line 35, in
    cur.execute("select id, sleep(0.2) from test.test limit 1")
  File "/Library/Python/2.7/site-packages/mysql/connector/cursor.py", line 491, in execute
    self._handle_result(self._connection.cmd_query(stmt))
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 1144, in cmd_query
    self.handle_mysql_error(exc)
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 1099, in handle_mysql_error
    self.reset_cache()
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 832, in reset_cache
    self._fabric.reset_cache(group=group)
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 369, in reset_cache
    self.get_group_servers(group, use_cache=False)
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 478, in get_group_servers
    inst = self.get_instance()
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 390, in get_instance
    if not inst.is_connected:
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 772, in is_connected
    self._proxy._some_nonexisting_method()  # pylint: disable=W0212
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xmlrpclib.py", line 1224, in __call__
    return self.__send(self.__name, args)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xmlrpclib.py", line 1578, in __request
    verbose=self.__verbose
  File "/Library/Python/2.7/site-packages/mysql/connector/fabric/connection.py", line 272, in request
    raise InterfaceError("Connection with Fabric failed: " + msg)
mysql.connector.errors.InterfaceError: Connection with Fabric failed: 

Это происходит, когда делается новая попытка подключения после сброса локального кэша.

Убедиться, что MySQL Fabric работает

На момент написания этой статьи, пользователь должен убедиться, что MySQL Fabric запущен и работает. Это означает, что вы можете использовать все, что вам удобно с точки зрения ГА, например, кардиостимулятор . Хотя это и добавляет сложности в настройку, сервер XML-RPC очень прост в управлении, поэтому должен работать простой менеджер ресурсов. Для серверной части MySQL Fabric не зависит от механизма хранения, поэтому простым способом решения этой проблемы может быть использование небольшого кластера MySQL, настроенного для обеспечения доступности серверной части. Команда MySQL рассказала о такой настройке здесь, Мы считаем, что подход ndb, вероятно, является самым простым для предоставления HA на уровне хранилища MySQL Fabric, но считаем, что сама MySQL Fabric должна обеспечивать или облегчать достижение HA на уровне сервера XML-RPC. Если в качестве хранилища используется ndb, это означает, что любой узел может выполнить запись, что, в свою очередь, означает, что несколько экземпляров XML-PRC должны иметь возможность одновременной записи в хранилище. Это означает, что теоретически улучшить это можно так же просто, как позволить драйверам с поддержкой Fabric получать список серверов Fabric вместо одного IP-адреса и порта для подключения.

Что дальше

В последних двух статьях мы представили функции высокой доступности MySQL Fabric, увидели, как он обрабатывает сбои на уровне узлов, как использовать базы данных MySQL с драйвером, поддерживающим MySQL Fabric, и что остается нерешенным на данный момент. В нашем следующем посте мы рассмотрим функции Sharding в MySQL Fabric.