====== MySQL mit Replikation ======
Dieses Beispiel wird auf einem //Ubuntu 10.04 LTS Server// und //MySQL 5.1.41-3ubuntu12.1// durchgeführt.
* **5.1**
* [[http://dev.mysql.com/doc/refman/5.1/de/replication.html]]
* [[http://dev.mysql.com/doc/refman/5.1/de/replication-howto.html]]
* **5.7**
* [[http://dev.mysql.com/doc/refman/5.7/en/replication-solutions-switch.html]]
**ACHTUNG!**
- Die **server_id** muss immer eindeutig sein!
- In aktuellen Versionen von MySQL und MariaDB wird das Passwort für den Replikations-User nicht mehr aus einer CNF-Datei ausgelesen, sondern muss per SQL-Aufruf eingetütet werden.
- z.Z. (MySQL 5.1) braucht MySQL für einen Master-Master-Betrieb die Option ''log-slave-updates''. Ohne diese Option kann MySQL 5.1 nur einen Master-Slave-Betrieb! Das bedeutet, wenn der Master per BIN-Logs (als quasi als Slave) von einem Master Daten bekommt, dann gibt er sie (in seiner Funktion als Master) nicht weiter. Man sieht es daran, dass die BIN-Log-Position in "SHOW MASTER STATUS" beim einspielen von Daten, nicht weiter hoch läuft. Die BIN-Log-Position in "SHOW SLAVE STATUS" dagegen läuft prima hoch.
Zum Beispiel so:
# echo "CHANGE MASTER TO MASTER_HOST='${master_host_name}', MASTER_USER='${replication_user_name}', MASTER_PASSWORD='${replication_password}', MASTER_LOG_FILE='${recorded_log_file_name}', MASTER_LOG_POS=${recorded_log_position};" | mysql -h${slave_host_name} -uroot -p${dbrootpw}
== Master und Slave jetzt verbinden ==
#!/bin/bash
MASTER_HOST="${1}"
BENUTZER="root"
PASSWORT="geheim1"
MASTER_USER="otto"
MASTER_PASSWORD="geheim2"
if [ -n "${MASTER_HOST}" ] ; then
MASTERSTATUS="$(echo "SHOW MASTER STATUS \G;" | mysql -u${BENUTZER} -p${PASSWORT} -h${MASTER_HOST} | awk '/^[ ]*File:/ || /^[ ]*Position:/ {print $NF}')"
BINLOG="$(echo "${MASTERSTATUS}" | head -n1)"
LOGPOS="$(echo "${MASTERSTATUS}" | tail -n1)"
echo "'${MASTER_HOST}', '${BINLOG}', '${LOGPOS}'"
echo "
STOP SLAVE;
CHANGE MASTER TO \
MASTER_HOST='${MASTER_HOST}', \
MASTER_USER='${MASTER_USER}', \
MASTER_PASSWORD='${MASTER_PASSWORD}', \
MASTER_LOG_FILE='${BINLOG}', \
MASTER_LOG_POS=${LOGPOS} \
;
START SLAVE;
" | mysql -u${BENUTZER} -p${PASSWORT}
else
echo "${0} [MySQL-Master]"
fi
===== DB-Host neu initialisieren =====
# /etc/init.d/mysql stop
# aptitude update && aptitude safe-upgrade && aptitude autoclean || exit 1
# aptitude -y install mysql-server || exit 1
# /etc/init.d/mysql stop
# sed -i 's/^bind-address.*/#&/' /etc/mysql/my.cnf
# rm -fr /var/log/mysql* /var/lib/mysql
# dpkg-reconfigure --force $(dpkg -l | awk '/^ii.+mysql-server-[0-9]/ {print $2}') || exit 1
===== Replikation einrichten =====
- //172.16.0.1//: Master (db01)
- //172.16.0.2//: Slave (db02)
==== my.cnf ====
Alle Dateien, die im Verzeichnis ///etc/mysql/conf.d/// liegen und die Endung //.cnf// haben, werden in die Datei ///etc/my.cnf// eingebunden:
# ls -l /etc/mysql/conf.d/*.cnf
=== Master + Slave ===
# echo "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'geheim' WITH GRANT OPTION;FLUSH PRIVILEGES;" | mysql -uroot -pgeheim
# vi /etc/mysql/conf.d/db_fritz.cnf
[mysqld]
pid_file = mysql.pid
log_bin = mysql-bin
#log_bin = /mysqlbinlog/mysql-bin
relay_log = mysql-relay-bin
relay_log_space_limit = 4G
[server]
sql-mode=""
Status des Slave:
# echo "SHOW SLAVE STATUS \G;" | mysql -u root -p
=== db01 (Master) ===
Konfigurieren Sie ein Konto auf dem Master-Server, über das der Slave eine Verbindung herstellen kann. Dieses Konto benötigt die Berechtigung **REPLICATION SLAVE**. Wenn das Konto ausschließlich zur Replikation verwendet wird (was wir empfehlen), dann brauchen Sie keine weiteren Berechtigungen zu gewähren.
**__Nicht__ empfohlen dagegen ist soetwas:**
# echo "GRANT REPLICATION SLAVE ON *.* TO 'root'@'%' IDENTIFIED BY 'geheim';" | mysql -t -h localhost -u root -p
Besser ist soetwas:
# echo "CREATE USER fritz IDENTIFIED BY 'fritz_geheim';GRANT REPLICATION CLIENT,RELOAD,REPLICATION SLAVE ON *.* TO 'fritz'@'%' IDENTIFIED BY 'fritz_geheim';" | mysql -u root -p
# vi /etc/mysql/conf.d/replication_master.cnf
[mysqld]
server_id = 1
innodb_flush_log_at_trx_commit=1
sync_binlog = 1
log_slave_updates = 1
expire_logs_days = 3
#master_ssl =
#master_ssl_ca =
#master_ssl_capath =
#master_ssl_cert =
#master_ssl_cipher =
#master_ssl_key =
Die **server_id** muss auf jeden MySQL-Rechner eindeutig sein!
Status des Master:
# echo "SHOW MASTER STATUS;" | mysql -t -u root -p
File Position Binlog_Do_DB Binlog_Ignore_DB
mysql-bin.000003 106
Ob der Slave sich mit dem Master verbunden hat, kann man das so abfragen:
# echo "SHOW SLAVE HOSTS;" | mysql -t -uroot -p
Server_id Host Port Rpl_recovery_rank Master_id
20 vdb02 3306 0 10
Slave-Mode auf dem Master deaktivieren:
# echo "STOP SLAVE;CHANGE MASTER TO MASTER_HOST='';"|mysql -t -uroot -p
=== db02 (Slave) ===
# vi /etc/mysql/conf.d/replication_slave.cnf
[mysqld]
server_id = 2
innodb_flush_log_at_trx_commit=1
sync_binlog = 1
log_slave_updates = 1
master_host = db01
master_user = fritz
master_password = fritz_geheim
master_port = 3306
master_connect_retry = 60
report_host = mysql_slave
expire_logs_days = 30
#master_ssl =
#master_ssl_ca =
#master_ssl_capath =
#master_ssl_cert =
#master_ssl_cipher =
#master_ssl_key =
Die **server_id** muss auf jeden MySQL-Rechner eindeutig sein!
Damit der MySQL-Server sich nach dem Start nicht mit der falschen Maschine verbindet,
muss diese Datei gelöscht werden:
rm /var/lib/mysql/master.info
=== Master und Slave verbinden ===
* [[http://dev.mysql.com/doc/refman/5.1/de/replication-faq.html]]
Log-Position des Master auslesen:
# echo "SHOW MASTER STATUS \G;" | mysql -t -uroot -p -h db01
*************************** 1. row ***************************
File: mysql-bin.000205
Position: 52225615
Binlog_Do_DB:
Binlog_Ignore_DB:
Slave in die richtige Log-Position des Master einhängen:
# echo "STOP SLAVE;CHANGE MASTER TO MASTER_HOST='db01', MASTER_USER='fritz', MASTER_PASSWORD='fritz_geheim', MASTER_LOG_FILE='mysql-bin.000205', MASTER_LOG_POS=52225615;START SLAVE;"|mysql -t -uroot -p -h db02
== db_master-dump.sh ==
#!/bin/bash
### Dieses Skript erstellt einen Master-Dump
MASTER_HOST=""
MASTER_USER="root"
MASTER_PASSWD="geheim"
while [ "${#}" -ne "0" ]; do
case "${1}" in
-mh)
MASTER_HOST=${2}
shift
;;
-mu)
MASTER_USER=${2}
shift
;;
-mp)
MASTER_PASSWD=${2}
shift
;;
-h)
echo "
HILFE:
${0} [Option]
-mh [master host]
-mu [master user]
-mp [master passwd]
"
exit 1
;;
*)
if [ "$(echo "${1}"|egrep '^-')" ] ; then
echo "Der Parameter '${1}' wird nicht unterstützt!"
fi
shift
;;
esac
done
#------------------------------------------------------------------------------#
HOSTTEST="${MASTER_HOST}${SLAVE_HOST}"
if [ -z "${HOSTTEST}" ] ; then
echo "
${0} -mh [MASTER_HOST]
${0} -mh [MASTER_HOST] -mu [MASTER_USER] -mp [master_passwd]
zum Beispiel
${0} db01
"
else
ZUFALL="$(head -c 1000 /dev/urandom | tr -cd '[:alnum:]' | cut -b-12)"
### auf dem MASETER
echo "FLUSH TABLES WITH READ LOCK;" | mysql -h${MASTER_HOST} -t -u${MASTER_USER} -p${MASTER_PASSWD}
MASTERSTATUS="$(echo "SHOW MASTER STATUS \G;" | mysql -h${MASTER_HOST} -u ${MASTER_USER} -p${MASTER_PASSWD} | awk '/^[ ]*File:/ || /^[ ]*Position:/ {print $NF}')"
recorded_log_file_name="$(echo "${MASTERSTATUS}"|head -n1)"
recorded_log_position="$(echo "${MASTERSTATUS}"|tail -n1)"
time mysqldump -u${MASTER_USER} -p${MASTER_PASSWD} -h${MASTER_HOST} --opt -Ac --master-data=2 > /tmp/master-dump_${recorded_log_file_name}_${recorded_log_position}_${ZUFALL}.sql
echo "UNLOCK TABLES;" | mysql -u${MASTER_USER} -p${MASTER_PASSWD} -h${MASTER_HOST}
echo "'${MASTERSTATUS}'"
ssh ${MASTER_HOST} 'cat /etc/mysql/debian.cnf' > /tmp/master_debian_${ZUFALL}.cnf
ls -lh /tmp/master-dump_${recorded_log_file_name}_${recorded_log_position}_${ZUFALL}.sql /tmp/master_debian_${ZUFALL}.cnf
#------------------------------------------------------------------------------#
fi
== db_master-slave_repli_on.sh ==
#!/bin/bash
### Dieses Skript spielt den Master-Dump ein und aktiviert die Replikation
MASTER_DUMP=""
SLAVE_HOST=""
SLAVE_USER="root"
SLAVE_PASSWD="geheim"
REPLI_USER="otto"
REPLI_PASSWD="geheim" # FOSp85bLOpzH / Io8OoF1GZuaw3equ
while [ "${#}" -ne "0" ]; do
case "${1}" in
-sh)
SLAVE_HOST=${2}
shift
;;
-su)
SLAVE_USER=${2}
shift
;;
-sp)
SLAVE_PASSWD=${2}
shift
;;
-ru)
REPLI_USER=${2}
shift
;;
-rp)
REPLI_PASSWD=${2}
shift
;;
-ms)
MASTER_DEBIAN=${2} # Debian System-User-Datei vom Master
shift
;;
-md)
MASTER_DUMP=${2} # kompletter Dump vom Master
shift
;;
-h)
echo "
HILFE:
${0} [Option]
-md [master dump Dateiname]
-sh [slave host]
-su [slave user]
-sp [slave passwd]
-ru [repli user]
-rp [repli passwd]
"
exit 1
;;
*)
if [ "$(echo "${1}"|egrep '^-')" ] ; then
echo "Der Parameter '${1}' wird nicht unterstützt!"
fi
shift
;;
esac
done
#------------------------------------------------------------------------------#
HOSTTEST="${MASTER_HOST}${SLAVE_HOST}"
if [ -z "${HOSTTEST}" ] ; then
echo "
${0} -sh [SLAVE_HOST] -md [MASTER_DUMP]
${0} -sh [SLAVE_HOST] -su [SLAVE_USER] -sp [SLAVE_PASSWD] -ru [REPLI_USER] -rp [REPLI_PASSWD] -md [MASTER_DUMP]
zum Beispiel
${0} -sh db02 -ms /tmp/master_debian_KV9n7cIcmKhs.cnf -md /tmp/master-dump_mysql-bin.000205_52225615_KV9n7cIcmKhs.sql
"
else
### auf dem SLAVE
if [ -r "${MASTER_DUMP}" ] ; then
recorded_log_file_name="$(basename ${MASTER_DUMP} | awk -F'_' '{print $2}')"
recorded_log_position="$(basename ${MASTER_DUMP} | awk -F'_' '{print $3}')"
echo "STOP SLAVE;" | mysql -u${SLAVE_USER} -p${SLAVE_PASSWD} -h${SLAVE_HOST}
time mysql -h${SLAVE_HOST} -u${SLAVE_USER} -p${SLAVE_PASSWD} < ${MASTER_DUMP}
echo "CHANGE MASTER TO MASTER_HOST='${MASTER_HOST}', MASTER_USER='${REPLI_USER}', MASTER_PASSWORD='${REPLI_PASSWD}', MASTER_LOG_FILE='${recorded_log_file_name}', MASTER_LOG_POS=${recorded_log_position};" | mysql -h${SLAVE_HOST} -u${SLAVE_USER} -p${SLAVE_PASSWD}
echo "START SLAVE;" | mysql -h${SLAVE_HOST} -u${SLAVE_USER} -p${SLAVE_PASSWD}
if [ -r "${MASTER_DEBIAN}" ] ; then
md-test="$(basename ${MASTER_DUMP} | awk -F'_' '{gsub("[.]"," ");print $4}')"
ms-test="$(basename ${MASTER_DEBIAN} | awk -F'_' '{gsub("[.]"," ");print $3}')"
if [ "${md-test}" == "${ms-test}" ] ; then
cat ${MASTER_DEBIAN} | ssh ${MASTER_HOST} 'cat > /etc/mysql/debian.cnf'
else
echo "${MASTER_DUMP} und ${MASTER_DEBIAN} gehoeren nicht zusammen"
fi
fi
ssh ${SLAVE_HOST} '/etc/init.d/mysql restart'
fi
#------------------------------------------------------------------------------#
fi
== Access denied for user 'debian-sys-maint'@'localhost' ==
Sollte auf dem //Slave// diese Fehlermeldung erscheinen, dann muss das Passwort aus der Datei ///etc/mysql/debian.cnf// vom Master auf den Slave übertragen.
# /etc/init.d/mysql start
* Starting MariaDB database server mysqld [ OK ]
* Checking for corrupt, not cleanly closed and upgrade needing tables.
ERROR 1045 (28000): Access denied for user 'debian-sys-maint'@'localhost' (using password: YES)
oder man überträgt einfach die ganze Datei vom Master zum Slave:
# ssh root@db01 'cat /etc/mysql/debian.cnf' | ssh root@db02 'cat > debian.cnf'
===== MySQL Bin-Log's in separate Partition schreiben =====
In diesem Beispiel lege ich das neue Bin-Log-Verzeichnis in ein LVM.
LVM anlegen:
# fdisk /dev/sda
# pvcreate /dev/sda2
# vgcreate system /dev/sda2
LV anlegen und formatieren:
# lvcreate -L 100G -n mysqlbinlog system
# mkfs -t ext4 -L mysqlbinlog /dev/system/mysqlbinlog
LV mounten:
# mkdir /mysqlbinlog
# echo "LABEL=mysqlbinlog /mysqlbinlog ext4 defaults 0 0" >> /etc/fstab
# mount /mysqlbinlog
Apparmor anpassen:
# vi /etc/apparmor.d/usr.sbin.mysqld
...
/mysqlbinlog/** rw,
}
# /etc/init.d/apparmor restart
MySQL-Konfiguration anpassen, hier muss der Variable //''log_bin''// ein absoluter Pfad übergeben werden:
# vi /etc/mysql/conf.d/default.cnf
[mysqld]
pid_file = /mysqlbinlog/mysql.pid
log_bin = /mysqlbinlog/mysql-bin
relay_log = /mysqlbinlog/mysql-relay-bin
relay_log_space_limit = 4G
expire_logs_days = 7
...
===== MySQL DB in eine separate Partition schreiben =====
# mkdir /mysqldb
# echo "LABEL=mysqldb /mysqldb ext4 defaults 0 0" >> /etc/fstab
# mount /mysqldb
# mkdir /mysqldb/datadir
# chown -R mysql:mysql /mysqldb
MySQL-Konfiguration anpassen, hier muss der Variable //''log_bin''// ein absoluter Pfad übergeben werden:
# vi /etc/mysql/conf.d/default.cnf
[mysqld]
datadir = /mysqldb/datadir
...
Apparmor anpassen:
# vi /etc/apparmor.d/usr.sbin.mysqld
...
/mysqldb/** rw,
/mysqldb/datadir/** rw,
}
# /etc/init.d/apparmor restart
===== MySQL Bin-Log's =====
* [[http://dev.mysql.com/doc/refman/5.1/de/replication-sql.html]]
* [[http://dev.mysql.com/doc/refman/5.1/de/replication-master-sql.html]]
* [[http://dev.mysql.com/doc/refman/5.1/de/purge-master-logs.html]]
* [[http://dev.mysql.com/doc/refman/5.1/de/binary-log.html]]
* [[http://dev.mysql.com/doc/refman/5.1/de/show-binlog-events.html]]
==== MySQL Bin-Log's ansehen ====
# du -shc /var/lib/mysql/mysql-*bin.*
# echo "SHOW BINARY LOGS;" | mysql -t -u root -pgeheim
# echo "SHOW MASTER STATUS;" | mysql -t -u root -pgeheim
# mysqlbinlog --base64-output=decode-rows --verbose /var/lib/mysql/mysql-bin.000004 > mysql-bin.000004.txt
# echo "SHOW BINLOG EVENTS IN 'mysql-bin.000004';" | mysql -t -uroot -pgeheim
Restore der letzten Bin Log Sicherung „mtebosql01.victorvox.net:/var/lib/mysql '/var/lib/mysql'“
# mysqlbinlog --start-position=120 /var/lib/mysql/mysql-bin.000004 | gzip -9 > /var/lib/mysql/recover.sql.gz
# zcat /var/lib/mysql/recover.sql.gz | mysql –vvv
==== überflüssige MySQL Bin-Log's löschen ====
#!/bin/bash
DBPASSWD="geheim"
DBMASTER="${1}"
if [ -z "${DBMASTER}" ] ; then
echo "${0} [DB-Master-Host]"
exit 1
fi
DBSLAVES="$(echo "SHOW SLAVE HOSTS \G;" | mysql -uroot -p${DBPASSWD} -h${DBMASTER} | awk '/^[ ]*Host:/ {print $NF}')"
BINLOGNR="$(echo "SHOW MASTER STATUS \G;" | mysql -uroot -p${DBPASSWD} -h${DBMASTER} | awk '/^[ ]*File:/ {gsub("[.]"," ");print $NF}')"
for i in ${DBSLAVES}
do
BINLOGNR="${BINLOGNR}
$(echo "SHOW SLAVE STATUS \G;" | mysql -uroot -p${DBPASSWD} -h${i} | awk '/^[ ]*Master_Log_File:/ {gsub("[.]"," ");print $NF}')"
done
MAXBINLOG="$(echo "${BINLOGNR}" | awk '/^[0-9]/ {print $1}' | sort -n | head -n1)"
BINLOGDEL="PURGE BINARY LOGS TO 'mysql-bin.$(echo "${MAXBINLOG}" | sed 's/.*/000000&/' | rev | cut -c 1-6 | rev)'"
### hier wird tatsaechlich geloescht!!!
echo "${BINLOGDEL};" | mysql -uroot -p${DBPASSWD} -h${DBMASTER}
===== Probleme =====
MySQL-Replikation hat gegenüber einer Lösung mit einer MySQL-DB auf zwei per DRBD gespiegelten Rechnern zwei bedeutende Nachteile:
- Ein automatischer Schwenk zwischen den beiden MySQL-DB's in Replikation, stellte sich (in der Praxis) als nicht so einfach heraus, wie mit [[DRBD]].
- Wenn beim Schwenk auch nur ein einziges SQL-Kommando auf dem Slave nicht ausgeführt wird, sind (unter Umständen) die beiden DB's nicht mehr (automatisch) synchron zu bekommen. Dann steht zwar die Verbindung zwischen Master und Slave, aber der Slave snchronisiert sich nicht mehr...
Aus diesem Grund ist die Replikation einer MySQL-DB nur sinnvoll, wenn man:
- eine oder mehrere DBD's als "Sicherheitskopien" haben möchte, die im Fehlerfall nicht einspringen müssen
- viele Lesezugriffe auf mehrere MySQL-Systeme verteilen möchte
=== ERROR 1794 ===
ERROR 1794 (HY000) at line 1: Slave is not configured or failed to initialize properly. You must at least set --server-id to enable either a master or a slave. Additional error messages can be found in the MySQL error log.
Wenn es mit diesen Einstellungen zum Fehler "ERROR 1794" kommt:
relay_log = mysql-relay-bin
relay_log_info_repository = TABLE
relay_log_recovery = 1
sync_relay_log_info = 1
relay_log_space_limit = 4G
... dann muss man nur einen Parameter entfernen:
#relay_log = mysql-relay-bin
relay_log_info_repository = TABLE
relay_log_recovery = 1
sync_relay_log_info = 1
relay_log_space_limit = 4G
... und schon geht es nach dem Restart.
===== MySQL-BIN-LOG lesen =====
#!/bin/bash
if [ -z "${1}" ] ; then
echo "${0} /var/lib/mysql/mysql-bin.002036";
echo "less /var/lib/mysql/mysql-bin.002036.txt";
echo
echo "${0} /var/lib/mysql/mysql-bin.002036 107";
echo "less /var/lib/mysql/mysql-bin.002036_107.txt";
exit 1;
else
if [ -r "${1}" ] ; then
MASTER_LOG_FILE="${1}"
BIN_LOG_OUT="${1}"
if [ -n "${2}" ] ; then
READ_MASTER_LOG_POS="${2}"
MASTER_LOG_POSITION="-j ${READ_MASTER_LOG_POS}"
BIN_LOG_OUT="${1}_${2}"
fi
else
echo "Die BIN-LOG-Datei konnte nicht gelesen bzw. gefunden werden.";
exit 1;
fi
fi
# mysqlbinlog --base64-output=DECODE-ROWS -v -j 1013 /var/lib/mysql/mysql-bin.002035 > /tmp/mysql-bin.txt
mysqlbinlog --base64-output=DECODE-ROWS -v ${MASTER_LOG_POSITION} ${MASTER_LOG_FILE} > ${BIN_LOG_OUT}.txt
ls -l ${BIN_LOG_OUT}.txt
===== aktuelle Position in der Replikation überspringen =====
#!/bin/bash
echo "
STOP SLAVE;
SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
START SLAVE;
SHOW SLAVE STATUS \G;
" | mysql