MySQL-Replikation leicht gemacht

Diesen Beitrag schrieb ich 14 Jahre und 11 Monate zuvor; die nachfolgenden Ausführungen müssen heute weder genau so nach wie vor funktionieren, noch meiner heutigen Meinung entsprechen. Behalte das beim Lesen (und vor allem: beim Nachmachen!) bitte stets im Hinterkopf.

Geschätzte Lesezeit: 4 Minuten

Die Replikation der Datenbank kann eine feine Sache sein: zwei Datenbanken, idealerweise auf zwei verschiedenen Servern, identischer Datenbestand…

Um es vorwegzunehmen: die Replikation ersetzt kein Backup! Sobald sich Daten auf dem Master ändern – beispielsweise durch ein versehentliches DROP TABLE – werden die Änderungen auf allen Slaves übernommen; und was weg ist, ist weg, da hilft nur ein Backup :D Geht es allerdings um Redundanz, um Ausfallsicherheit oder Lastverteilung, ist die Replikation eine elegante Wahl. Steigen wir also direkt ein und gehen davon aus, dass der Master-Server im weiteren Verlauf einfach master heißt, der Slave-Server schlicht und ergreifend slave, beide in der Domäne localdomain.

Master: neuer User

Auf dem Master muss ein neuer MySQL-User angelegt werden, dem im folgenden Schritt die zur Replikation benötigten Rechte zugesprochen werden:

mysql> create user 'replication'@'slave.localdomain' identified by 'einPasswort';
mysql> GRANT REPLICATION SLAVE ON *.* to 'replication'@'slave.localdomain' identified by 'einPasswort';

Dieser Schritt ist für jeden Slave, den man hinzufügen möchte, zu wiederholen; wir gehen nachfolgend aber von einem Setup mit nur einem Slave aus.

Master: my.cnf anpassen

Die Konfiguration des Servers – /etc/mysql/my.cnf auf Debian-Systemen – muss in Hinblick auf die Replikation bearbeitet werden. Eingefügt werden müssen die nachfolgenden Zeilen – die Parameter sind selbsterklärend, für weitere Erklärungen verweise ich an dieser Stelle auf die READMEs :D

server-id               = 1
log_bin                 = /var/log/mysql/mysql-bin.log
expire_logs_days        = 10
max_binlog_size         = 100M
log                     = /var/log/mysql/mysql.log

Danach einen Restart des mysqld initiieren.

Slave: my.cnf

Man sollte darauf achten, dass sich Master und Slave hinsichtlich der verwendeten MySQL-Versionen nicht allzusehr unterscheiden (sehr hilfreich ist in diesem Zusammenhang die Webseite Replication Compatibility Between MySQL Versions); im vorliegenden Beispiel wurden identische Systeme verwendet und auf beiden das identische MySQL-Paket installiert. Die Konfiguration des Slaves muss nun der des Masters angepasst werden, insbesondere für den Fall, dass auf dem Master InnoDB verwendet wird. Wichtig: die server-id muss eine noch nicht verwendete (beliebige, aber positive) ganze Zahl sein; dem Master die 1 zuzusprechen und den Wert für jeden Slave zu inkrementieren ist naheliegend.

server-id               = 2
log                     = /var/log/mysql/mysql.log

Master: Datensicherung erstellen

Auf dem Master erstellen wir nun eine Sicherung der Datenbankinhalte, die wir anschließend auf dem Slave einspielen; in dem Moment haben beide Maschinen einen identischen Datenbestand, auf dem dann zukünftig aufgebaut werden kann. Um die Datensicherung auf dem Master zu erstellen, führen wir im ersten Schritt ein Flush auf die Tabellen aus (d.h. alle Caches werden geleert, also noch ausstehenden Daten werden in Binärdateien geschrieben) und sperren die Tabellen für weitere Schreibzugriffe (da MySQL schon während des Kopierens weitere Daten zwischenspeichern und damit den gewünschten Effekt zunichte machen würde). Das erreichen wir so:

mysql> FLUSH TABLES WITH READ LOCK;

Kein anderer Prozess hat nunmehr Schreibzugriff auf die Tabellen – so lange die Shell, in welcher der Befehl abgesetzt wurde, offenbleibt! Der Schreibschutz wird aufgehoben, wenn entweder die aufrufende Shell geschlossen oder aber das Kommando UNLOCK TABLES übergeben wird.

Dann schauen wir in der /etc/mysql/my.cnf, wie datadir definiert ist; im vorliegenden Falle ist das datadir = /data/mysql, und hier liegen alle Daten, die wir sichern müssen. Also:

$ cd /data/mysql
$ tar cvfj /tmp/mysql_snapshot.tbz .

Die resultierende Datei transportieren wir auf beliebigem Wege zum Slave, sichern sie aber auch für alle Fälle für später (wir würden sie beispielsweise benötigen, wenn wir einen dritten Slave aufsetzen wollen; dies soll aber nicht Bestand dieses Howtos sein). Den Schreibschutz der Tabellen heben wir nun wieder auf.

Hinweis: selbstredend kann das Backup auch über mysqldump erstellt und via mysql eingespielt werden; dieses Howto stützt sich jedoch auf die (hoffentlich nicht unbegründete) Annahme, dass vollständiger Shell-Zugriff zu den Systemen möglich ist. Welcher Weg im Endeffekt eingeschlagen wird, liegt im Ermessen des Admins: beide sind gleichwertig.

Slave: MySQL in Betrieb nehmen

Den MySQL-Server auf dem Slave hatten wir bislang nicht gestartet; dennoch überprüfen wir zur Sicherheit, dass er auch wirklich nicht läuft (sollte er es doch tun, muss er gestoppt werden!). Anschließend müssen wir das eben erstellte Backup einspielen; hierzu schauen wir auch auf dem Slave, wie ``datadir in /etc/mysql/my.cnf definiert ist - hier steht datadir = /var/lib/mysql. Also legen wir unser mysql_snapshot.tbz nach /var/lib/mysql` und packen es dort aus:

$ tar cvfj mysql_snapshot.tbz

Anschließend den Server auf dem Slave starten, die Log-Files prüfen und eventuelle Fehler korrigieren. Läuft nun alles? Prima!

Master & Slave: Inbetriebnahme

Am besten öffnen wir uns nun zwei Shells parallel und loggen uns einerseits auf dem Master, andererseits auf dem Slave ein. Auf dem Master loggen wir uns als User root in die MySQL-Shell ein und ermitteln das aktuelle Logfile:

mysql> show master status;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000007 |   505273 |              |                  | 
+------------------+----------+--------------+------------------+
1 row in set (0.00 sec)

Anschließend loggen wir uns, ebenfalls als User root, in die MySQL-Shell auf dem Slave ein und machen ihm deutlich, dass er zukünftig seine Daten vom Master beziehen wird. Und beachten: das root-Passwort für MySQL ist auf dem Slave nun – nach unserem erfolgreichen Einspielen des Backups vom Master – natürlich auch das selbe wie auf dem Master! :D

mysql> CHANGE MASTER TO
  -> MASTER_HOST='master.localdomain',
  -> MASTER_USER='replication',
  -> MASTER_PASSWORD='einPasswort',
  -> MASTER_LOG_FILE='mysql-bin.000007';

Den finalen Startschuss geben wir dann auf dem Slave mit folgendem Befehl:

mysql> START SLAVE;

Wir haben auf beiden Maschinen das Logging nach /var/log/mysql/mysql.log aktiviert; so können wir nun, parallel in beiden Shells, ein tail -f /var/log/mysql/mysql.log auf beiden Maschinen ausführen und beobachten, was sich tut. Aus Performance-Gründen sollte das Logging für den Produktivbetrieb jedoch wieder deaktiviert werden (Zeile log = /var/log/mysql/mysql.log auskommentieren, /etc/init.d/mysqld reload).

Ein show processlist auf dem Slave kann im laufenden Betrieb dann wie folgt aussehen:

mysql> show processlist;
+------+-------------+-----------+------+---------+-------+-----------------------------------------------------------------------+------------------+
| Id   | User        | Host      | db   | Command | Time  | State                                                                 | Info             |
+------+-------------+-----------+------+---------+-------+-----------------------------------------------------------------------+------------------+
|    7 | system user |           | NULL | Connect | 85194 | Waiting for master to send event                                      | NULL             | 
|    8 | system user |           | NULL | Connect |  3740 | Has read all relay log; waiting for the slave I/O thread to update it | NULL             | 
| 1147 | root        | localhost | NULL | Query   |     0 | NULL                                                                  | show processlist | 
+------+-------------+-----------+------+---------+-------+-----------------------------------------------------------------------+------------------+
3 rows in set (0.03 sec)

Und auf dem Master kann das so aussehen (es werden alle momentan verbundenen Slaves angezeigt):

mysql> show processlist;
+------+-------------+---------------------------+--------+-------------+-------+----------------------------------------------------------------+------------------+
| Id   | User        | Host                      | db     | Command     | Time  | State                                                          | Info             |
+------+-------------+---------------------------+--------+-------------+-------+----------------------------------------------------------------+------------------+
| 1554 | replication | slave.localdomain:57823   | NULL   | Binlog Dump |   224 | Has sent all binlog to slave; waiting for binlog to be updated | NULL             | 
| 1557 | root        | localhost                 | NULL   | Query       |     0 | NULL                                                           | show processlist | 
+------+-------------+---------------------------+--------+-------------+-------+----------------------------------------------------------------+------------------+
7 rows in set (0.01 sec)

Fazit

Das war die gesamte Magie; der Master führt sozusagen Buch über alle erfolgten Transaktionen, speichert diese im Binlog und gibt sie an alle angeschlossenen Slaves weiter. Die Slaves ihrerseits werten ausschließlich Änderungen an den Datenbanken aus und stehen daher praktisch nicht unter Last. Einsatzszenarien sind nun verschiedene denkbar: beispielsweise könnte der Master die Datenverwaltung an sich und die Weitergabe der Daten an die Slaves verwalten, wohingegen rechenzeitintensive Queries (die den Datenbestand nicht verändern) auf den Slaves ausgeführt werden könnten. Auch lässt sich so ein Switch von datenbankgestützten Anwendungen realisieren… Die Einsatzmöglichkeiten sind vielfältig.

Vielleicht seid ihr ja jetzt auf den Geschmack gekommen; viel Erfolg und viel Spaß :D

Alle Bilder dieser Seite: © Marianne Spiller – Alle Rechte vorbehalten
Hintergrundbild: 2448x 2448px, Bild genauer anschauen – © Marianne Spiller – Alle Rechte vorbehalten

Eure Gedanken zu „MySQL-Replikation leicht gemacht“

Ich freue mich über jeden Kommentar, es sei denn, er ist blöd. Deshalb behalte ich mir auch vor, die richtig blöden kurzerhand wieder zu löschen. Die Kommentarfunktion ist über GitHub realisiert, weshalb ihr euch zunächst dort einloggen und „utterances“ bestätigen müsst. Die Kommentare selbst werden im Issue-Tracker und mit dem Label „✨💬✨ comment“ erfasst – jeder Blogartikel ist ein eigenes Issue. Über GitHub könnt ihr eure Kommentare somit jederzeit bearbeiten oder löschen.