Mysql
 sql >> база данни >  >> RDS >> Mysql

MySQL репликация и отказване, базирано на GTID - дълбоко потапяне в грешни транзакции

Години наред репликацията на MySQL се базираше на събития в двоичен дневник - всичко, което робът знаеше, беше точното събитие и точната позиция, която току-що прочете от главния. Всяка отделна транзакция от главен регистър може да е завършила в различни двоични регистрационни файлове и на различни позиции в тези регистрационни файлове. Това беше просто решение, което идваше с ограничения - по-сложните промени в топологията можеха да изискват администратор да спре репликацията на участващите хостове. Или тези промени могат да причинят някои други проблеми, например подчинен не може да бъде преместен надолу по веригата на репликация без отнемащ време процес на възстановяване (не бихме могли лесно да променим репликацията от A -> B -> C на A -> C -> B без спиране на репликацията както на B, така и на C). Всички трябваше да заобиколим тези ограничения, докато мечтаем за глобален идентификатор на транзакция.

GTID беше въведен заедно с MySQL 5.6 и донесе някои големи промени в начина на работа на MySQL. На първо място, всяка транзакция има уникален идентификатор, който я идентифицира по същия начин на всеки сървър. Вече не е важно в коя позиция в двоичен регистрационен файл е записана транзакцията, всичко, което трябва да знаете, е GTID:„966073f3-b6a4-11e4-af2c-080027880ca6:4“. GTID е изграден от две части - уникалният идентификатор на сървъра, където транзакцията е била изпълнена за първи път, и пореден номер. В горния пример можем да видим, че транзакцията е била изпълнена от сървъра със server_uuid от „966073f3-b6a4-11e4-af2c-080027880ca6“ и това е 4-та транзакция, изпълнена там. Тази информация е достатъчна за извършване на сложни промени в топологията - MySQL знае кои транзакции са били изпълнени и следователно знае кои транзакции трябва да бъдат изпълнени следващи. Забравете за двоичните регистрационни файлове, всичко е в GTID.

И така, къде можете да намерите GTID? Ще ги намерите на две места. На подчинен, в „покажи статус на подчинен;“ ще намерите две колони:Retrieved_Gtid_Set и Executed_Gtid_Set. Първият обхваща GTID, които са били извлечени от главната чрез репликация, вторият информира за всички транзакции, които са били изпълнени на даден хост - както чрез репликация, така и изпълнени локално.

Създаване на клъстер за репликация по лесния начин

Разгръщането на клъстер за репликация на MySQL е много лесно в ClusterControl (можете да го изпробвате безплатно). Единствената предпоставка е всички хостове, които ще използвате за разгръщане на възли на MySQL, да могат да бъдат достъпни от екземпляра на ClusterControl, като се използва SSH връзка без парола.

Когато свързаността е налице, можете да разгърнете клъстер, като използвате опцията „Разгръщане“. Когато прозорецът на съветника е отворен, трябва да вземете няколко решения - какво искате да направите? Разгръщане на нов клъстер? Разположете възел на Postgresql или импортирайте съществуващ клъстер.

Искаме да разгърнем нов клъстер. След това ще ни бъде представен следния екран, в който трябва да решим какъв тип клъстер искаме да разположим. Нека изберем репликация и след това да предадем необходимите подробности за ssh свързаността.

Когато сте готови, щракнете върху Продължи. Този път трябва да решим кой доставчик на MySQL бихме искали да използваме, каква версия и няколко конфигурационни настройки, включително, наред с други, парола за root акаунта в MySQL.

И накрая, трябва да вземем решение за топологията на репликация - можете или да използвате типична настройка главен - подчинен, или да създадете по-сложна, активна - готовност - главен - главен чифт (+ подчинени, ако искате да ги добавите). След като сте готови, просто кликнете върху „Разгръщане“ и след няколко минути трябва да разполагате с клъстера си.

След като това стане, ще видите своя клъстер в списъка с клъстери на потребителския интерфейс на ClusterControl.

След като репликацията стартира и стартира, можем да разгледаме по-отблизо как работи GTID.

Грешни транзакции – какъв е проблемът?

Както споменахме в началото на тази публикация, GTID донесе значителна промяна в начина, по който хората трябва да мислят за репликацията на MySQL. Всичко е свързано с навиците. Да кажем по някаква причина, че приложение е извършило запис на един от подчинените. Не трябваше да се случва, но изненадващо, това се случва през цялото време. В резултат на това репликацията спира с грешка при дублиран ключ. Има няколко начина за справяне с такъв проблем. Един от тях би бил да се изтрие нарушителният ред и да се рестартира репликацията. Другото би било да пропуснете събитието за двоичен дневник и след това да рестартирате репликацията.

STOP SLAVE SQL_THREAD; SET GLOBAL sql_slave_skip_counter = 1; START SLAVE SQL_THREAD;

И двата начина трябва да върнат репликацията към работа, но те могат да доведат до отклонение на данните, така че е необходимо да се помни, че подчинената консистенция трябва да бъде проверена след такова събитие (pt-table-checksum и pt-table-sync работят добре тук).

Ако се случи подобен проблем, докато използвате GTID, ще забележите някои разлики. Изтриването на ред в нарушение може да изглежда, че решава проблема, репликацията трябва да може да започне. Другият метод, използващ sql_slave_skip_counter, изобщо няма да работи - ще върне грешка. Не забравяйте, че сега не става въпрос за binlog събития, а за изпълнението на GTID или не.

Защо изтриването на реда само „изглежда“ решава проблема? Едно от най-важните неща, които трябва да имате предвид по отношение на GTID, е, че подчинен, когато се свързва с главната, проверява дали липсват транзакции, които са били изпълнени на главния. Това се наричат ​​грешни транзакции. Ако робът открие такива транзакции, той ще ги изпълни. Да предположим, че сме изпълнили следния SQL, за да изчистим ред с нарушение:

DELETE FROM mytable WHERE id=100;

Нека проверим показване на подчинен статус:

                  Master_UUID: 966073f3-b6a4-11e4-af2c-080027880ca6
           Retrieved_Gtid_Set: 966073f3-b6a4-11e4-af2c-080027880ca6:1-29
            Executed_Gtid_Set: 84d15910-b6a4-11e4-af2c-080027880ca6:1,
966073f3-b6a4-11e4-af2c-080027880ca6:1-29,

И вижте откъде идва 84d15910-b6a4-11e4-af2c-080027880ca6:1:

mysql> SHOW VARIABLES LIKE 'server_uuid'\G
*************************** 1. row ***************************
Variable_name: server_uuid
        Value: 84d15910-b6a4-11e4-af2c-080027880ca6
1 row in set (0.00 sec)

Както можете да видите, имаме 29 транзакции, които идват от главния, UUID от 966073f3-b6a4-11e4-af2c-080027880ca6 и една, която е изпълнена локално. Да кажем, че в даден момент преминаваме при отказ и главният (966073f3-b6a4-11e4-af2c-080027880ca6) става подчинен. Той ще провери своя списък с изпълнени GTID и няма да намери този:84d15910-b6a4-11e4-af2c-080027880ca6:1. В резултат на това свързаният SQL ще бъде изпълнен:

DELETE FROM mytable WHERE id=100;

Това не е нещо, което очаквахме... Ако междувременно binlog, съдържащ тази транзакция, бъде изчистен на стария подчинен, тогава новият подчинен ще се оплаче след отказ:

                Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires.'

Как да открием грешни транзакции?

MySQL предоставя две функции, които са много удобни, когато искате да сравните набори GTID на различни хостове.

GTID_SUBSET() приема два набора GTID и проверява дали първият набор е подмножество на втория.

Да кажем, че имаме следното състояние.

Учителят:

mysql> show master status\G
*************************** 1. row ***************************
             File: binlog.000002
         Position: 160205927
     Binlog_Do_DB:
 Binlog_Ignore_DB:
Executed_Gtid_Set: 8a6962d2-b907-11e4-bebc-080027880ca6:1-153,
9b09b44a-b907-11e4-bebd-080027880ca6:1,
ab8f5793-b907-11e4-bebd-080027880ca6:1-2
1 row in set (0.00 sec)

Подчинен:

mysql> show slave status\G
[...]
           Retrieved_Gtid_Set: 8a6962d2-b907-11e4-bebc-080027880ca6:1-153,
9b09b44a-b907-11e4-bebd-080027880ca6:1
            Executed_Gtid_Set: 8a6962d2-b907-11e4-bebc-080027880ca6:1-153,
9b09b44a-b907-11e4-bebd-080027880ca6:1,
ab8f5793-b907-11e4-bebd-080027880ca6:1-4

Можем да проверим дали подчинения има някакви грешни транзакции, като изпълним следния SQL:

mysql> SELECT GTID_SUBSET('8a6962d2-b907-11e4-bebc-080027880ca6:1-153,ab8f5793-b907-11e4-bebd-080027880ca6:1-4', '8a6962d2-b907-11e4-bebc-080027880ca6:1-153, 9b09b44a-b907-11e4-bebd-080027880ca6:1, ab8f5793-b907-11e4-bebd-080027880ca6:1-2') as is_subset\G
*************************** 1. row ***************************
is_subset: 0
1 row in set (0.00 sec)

Изглежда има грешни транзакции. Как да ги идентифицираме? Можем да използваме друга функция, GTID_SUBTRACT()

mysql> SELECT GTID_SUBTRACT('8a6962d2-b907-11e4-bebc-080027880ca6:1-153,ab8f5793-b907-11e4-bebd-080027880ca6:1-4', '8a6962d2-b907-11e4-bebc-080027880ca6:1-153, 9b09b44a-b907-11e4-bebd-080027880ca6:1, ab8f5793-b907-11e4-bebd-080027880ca6:1-2') as mising\G
*************************** 1. row ***************************
mising: ab8f5793-b907-11e4-bebd-080027880ca6:3-4
1 row in set (0.01 sec)

Нашите липсващи GTID са ab8f5793-b907-11e4-bebd-080027880ca6:3-4 – тези транзакции са били изпълнени на подчинения, но не и на главния.

Как да разрешим проблеми, причинени от грешни транзакции?

Има два начина – инжектиране на празни транзакции или изключване на транзакции от историята на GTID.

За да инжектираме празни транзакции, можем да използваме следния SQL:

mysql> SET gtid_next='ab8f5793-b907-11e4-bebd-080027880ca6:3';
Query OK, 0 rows affected (0.01 sec)
mysql> begin ; commit;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.01 sec)
mysql> SET gtid_next='ab8f5793-b907-11e4-bebd-080027880ca6:4';
Query OK, 0 rows affected (0.00 sec)
mysql> begin ; commit;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.01 sec)
mysql> SET gtid_next=automatic;
Query OK, 0 rows affected (0.00 sec)

Това трябва да се изпълни на всеки хост в топологията на репликация, който няма изпълнени тези GTID. Ако главният е наличен, можете да инжектирате тези транзакции там и да ги оставите да се репликират надолу по веригата. Ако главният не е наличен (например, той се е сринал), тези празни транзакции трябва да се изпълнят на всеки подчинен. Oracle разработи инструмент, наречен mysqlslavetrx, който е предназначен да автоматизира този процес.

Друг подход е да премахнете GTID от историята:

Спиране на подчинен:

mysql> STOP SLAVE;

Отпечатайте Executed_Gtid_Set на подчинения:

mysql> SHOW MASTER STATUS\G

Нулиране на информацията за GTID:

RESET MASTER;

Задайте GTID_PURGED на правилен набор от GTID. въз основа на данни от SHOW MASTER STATUS. Трябва да изключите грешни транзакции от набора.

SET GLOBAL GTID_PURGED='8a6962d2-b907-11e4-bebc-080027880ca6:1-153, 9b09b44a-b907-11e4-bebd-080027880ca6:1, ab8f5793-b907-11e4-bebd-080027880ca6:1-2';

Стартиране на подчинен.

mysql> START SLAVE\G

Във всеки случай трябва да проверите последователността на подчинените си устройства, като използвате pt-table-checksum и pt-table-sync (ако е необходимо) - грешната транзакция може да доведе до отклоняване на данните.

Отказ при ClusterControl

Започвайки от версия 1.4, ClusterControl подобри своите процеси за обработка при отказ за MySQL репликация. Все още можете да извършите ръчен главен превключвател, като повишите един от подчинените в главен. След това останалите подчинени устройства ще преминат към новия господар. От версия 1.4, ClusterControl също има способността да извършва напълно автоматизирано преминаване при отказ, ако главният се повреди. Разгледахме го задълбочено в публикация в блог, описваща ClusterControl и автоматизирано превключване при отказ. Все пак бихме искали да споменем една функция, пряко свързана с темата на тази публикация.

По подразбиране ClusterControl извършва превключване при отказ по „безопасен начин“ – в момента на отказ (или превключване, ако потребителят е изпълнил главен превключвател), ClusterControl избира главен кандидат и след това проверява, че този възел няма никакви грешни транзакции което би повлияло на репликацията, след като бъде повишена до овладяване. Ако бъде открита грешна транзакция, ClusterControl ще спре процеса на отказ и главният кандидат няма да бъде повишен да стане нов главен.

Ако искате да сте 100% сигурни, че ClusterControl ще популяризира нов главен код, дори ако бъдат открити някои проблеми (като грешни транзакции), можете да направите това, като използвате настройката replication_stop_on_error=0 в конфигурацията на cmon. Разбира се, както обсъдихме, това може да доведе до проблеми с репликацията – подчинените могат да започнат да искат двоичен дневник, който вече не е наличен.

За да се справим с такива случаи, добавихме експериментална поддръжка за възстановяване на подчинени устройства. Ако зададете replication_auto_rebuild_slave=1 в конфигурацията на cmon и вашето подчинено устройство е маркирано като надолу със следната грешка в MySQL, ClusterControl ще се опита да изгради отново подчинения, използвайки данни от главния:

Получих фатална грешка 1236 от главния при четене на данни от двоичен регистрационен файл:„Подчинът се свързва с помощта на CHANGE MASTER TO MASTER_AUTO_POSITION =1, но главният е изчистил двоични регистрационни файлове, съдържащи GTID, които подчиненото устройство изисква.“

Такава настройка може да не винаги е подходяща, тъй като процесът на възстановяване ще предизвика повишено натоварване на главния. Възможно е също така наборът ви от данни да е много голям и редовното възстановяване не е опция – ето защо това поведение е деактивирано по подразбиране.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Може ли MySQL да замени няколко знака?

  2. PolyScale.ai – Мащабиране на MySQL и PostgreSQL с глобално кеширане

  3. Заобикаляне на MySQL Грешка не може да се отвори отново

  4. MySQL – Вземете последната цена на заявката, като използвате SHOW STATUS КАТО „Last_Query_Cost“

  5. Как да проверя дали съществува ред в MySQL? (т.е. проверете дали съществува имейл в MySQL)