WordPress mit mehreren Datenbank-Zugängen

Da ich in letzter Zeit einige Probleme mit dem MySQL-Servers meines Hosting-Anbieters habe, habe ich überlegt, was für Alternativen ich habe. Jedes Mal, wenn der Server in die Knie geht, sind alle meine Webseiten nicht erreichbar: Vor allem bei diesem Blog ist das sehr ärgerlich, die Ausfälle der anderen Seiten kann ich noch verschmerzen.

Also habe ich mich mal drangesetzt, den WordPress-Quelltext analysiert und mir eine Lösung einfallen lassen. Um diese einzusetzen, müssen ein paar Modifikationen am Kern von WordPress vorgenommen werden. Die gezeigten Quelltextausschnitte und Zeilennummern beziehen sich auf WordPress 2.7.1.
Mit den Änderungen ist es übrigens gleichzeitig möglich, WordPress via SSL mit der MySQL-Datenbank sprechen zu lassen. 😉

Fangen wir mit den Änderungen an der Datei wp-config.php an - diese enthält unter anderem sämtliche Informationen, die WordPress benötigt, um sich zur Datenbank zu verbinden. Normalerweise sieht die Konfiguration der Datenbank in etwa so aus:

1
2
3
4
5
6
7
// ** MySQL Einstellungen ** //
define('DB_NAME', 'DATENBANKNAME'); // Der Name der Datenbank, die du benutzt.
define('DB_USER', 'BENUTZERNAME'); // Dein MySQL-Datenbank-Benutzername.
define('DB_PASSWORD', 'PASSWORT'); // Dein MySQL-Passwort.
define('DB_HOST', 'SERVER'); // In 99% der Fälle musst du hier nichts ändern.
define('DB_CHARSET', 'CHARSET');
define('DB_COLLATE', '');

Das ganze erweitern wir nun ein bisschen, damit wir die Daten von mehreren verschiedenen Datenbankservern angeben können. Hierfür schreiben wir alle Daten jeweils in ein Array. Jedes Array sollte gleichviele Einträge haben, wobei alle Einträge mit einem bestimmten Index zusammengehörig sind:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ** MySQL Einstellungen ** //
define('CLIENT_SSL',       2048);   // Wert für Konstante CLIENT_SSL
// define('ENABLE_DB_SELECT', 'true'); // Einkommentieren, um manuelle Wahl der Datenbank zu aktivieren

$wpdbname     = array('DATENBANKNAME1', 'DATENBANKNAME2'); // Der Name der Datenbank, die du benutzt.
$wpdbuser     = array('BENUTZERNAME1',  'BENUTZERNAME2');  // Dein MySQL-Datenbank-Benutzername.
$wpdbpassword = array('PASSWORT1',      'PASSWORT2');      // Dein MySQL-Passwort.
$wpdbhost     = array('SERVER1',        'SERVER2');        // In 99% der Fälle musst du hier nichts ändern.
$wpdbssl      = array('SSL1',           'SSL2');           // Verbindung via SSL => 'true'
$wpdbtimeout  = array(60,               60);               // Verbindungstimeout in Sekunden
$wpdbcharset  = array('CHARSET1',       'CHARSET2');
$wpdbcollate  = array('',               '');

define('DB_NAME',     serialize($wpdbname));
define('DB_USER',     serialize($wpdbuser));
define('DB_PASSWORD', serialize($wpdbpassword));
define('DB_HOST',     serialize($wpdbhost));
define('DB_SSL',      serialize($wpdbssl));
// define('DB_TIMEOUT',  serialize($wpdbtimeout)); // Einkommentieren, um Timeout-Konfiguration zu aktivieren
define('DB_CHARSET',  serialize($wpdbcharset));
define('DB_COLLATE',  serialize($wpdbcollate));

Ich hoffe, jemandem ist die neue Variable $wpdbssl und der neue definierte Wert DB_SSL aufgefallen. Über diese ist es später möglich, WordPress dazu zu bewegen, sich via SSL mit der Datenbank zu verbinden - wenn sie denn entsprechend konfiguriert ist. Der Wert 'false' bedeutet, dass SSL deaktiviert ist - jeder andere Wert aktiviert die SSL-Verbindung. 🙂

Nachdem die Konfiguration fertig ist, müssen wir nun an WordPress selber herumschrauben. Dazu müssen wir die Datei wp-includes/wp-db.php bearbeiten. In dieser wird die initiale Datenbankverbindung hergestellt.

In der Funktion __construct() befindet sich ab Zeile 312 folgender Quelltext:

1
2
3
4
5
6
7
        if ( defined('DB_CHARSET') )
            $this->charset = DB_CHARSET;

        if ( defined('DB_COLLATE') )
            $this->collate = DB_COLLATE;

        $this->dbh = @mysql_connect($dbhost, $dbuser, $dbpassword, true);

Diesen ersetzen wir nun durch folgenden Quelltext. Dieser ist für die Verarbeitung der Arrays zuständig - es werden nacheinander alle Konfigurationen durchprobiert, bis eine Verbindung zu eine der MySQL-Datenbanken hergestellt werden kann. Dabei wird auch berücksichtigt, dass es ja mal passieren kann, dass nicht alle Konfigurations-Arrays gleich lang sind; ebenso wird SSL berücksichtigt und der Timeout der Verbindung gesetzt, falls nötig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
        // which database to select
        $selectedName = '';

        // get array values
        $dbuser     = unserialize($dbuser);
        $dbpassword = unserialize($dbpassword);
        $dbname     = unserialize($dbname);
        $dbhost     = unserialize($dbhost);

        $dbssl = null;
        if ( defined('DB_SSL') ) {
            $dbssl = unserialize(DB_SSL);
        }
        $dbtimeout = null;
        if ( defined('DB_TIMEOUT') ) {
            $dbtimeout = unserialize(DB_TIMEOUT);
        }
        $dbcharset = null;
        if ( defined('DB_CHARSET') ) {
            $dbcharset = unserialize(DB_CHARSET);
        }
        $dbcollate = null;
        if ( defined('DB_COLLATE') ) {
            $dbcollate = unserialize(DB_COLLATE);
        }

        $this->dbh = false;

        // get number of available complete config sets
        $minCount = count($dbuser);
        if (count($dbpassword) < $minCount) { $minCount = count($dbpassword); }
        if (count($dbname) < $minCount) { $minCount = count($dbname); }
        if (count($dbhost) < $minCount) { $minCount = count($dbhost); }
        if ($dbssl != null) { if (count($dbssl) < $minCount) { $minCount = count($dbssl); } }
        if ($dbtimeout != null) { if (count($dbtimeout) < $minCount) { $minCount = count($dbtimeout); } }
        if ($dbcharset != null) { if (count($dbcharset) < $minCount) { $minCount = count($dbcharset); } }
        if ($dbcollate != null) { if (count($dbcollate) < $minCount) { $minCount = count($dbcollate); } }

        // find first working config set
        if ($minCount > 0) {
            $index = 0;

            // allow manual selection of used config set
            if (defined('ENABLE_DB_SELECT')) {
                if (ENABLE_DB_SELECT == 'true') {
                    $dbid = null;
                    if (isset($_GET['dbid'])) {
                        $dbid = $_GET['dbid'];
                    } else {
                        if (isset($_COOKIE['dbid'])) {
                            $dbid = $_COOKIE['dbid'];
                        }
                    }

                    if ($dbid != null) {
                        if (isset($_SERVER['HTTP_HOST'])) {
                            $domain = $_SERVER['HTTP_HOST'];
                        } else {
                            $domain = $_SERVER['SERVER_NAME'];
                        }

                        if ((strlen(trim($dbid)) > 0) && (is_numeric($dbid))) {
                            $dbid = intval($dbid);

                            if ($minCount > $dbid) {
                                $index    = $dbid;
                                $minCount = $dbid + 1;
                            }

                            setcookie('dbid', $dbid, time() + 300000, '/', $domain);
                        } else {
                            setcookie('dbid', '', time(), '/', $domain);
                        }
                    }
                }
            }

            do {
                $selectedName = $dbname[$index];

                if ($dbtimeout != null) {
                    ini_set('mysql.connect_timeout', $dbtimeout[$index]); // set connect timeout
                }

                if (($dbssl != null) && ($dbssl[$index] == 'true')) {
                    $this->dbh = @mysql_connect($dbhost[$index], $dbuser[$index], $dbpassword[$index], true, CLIENT_SSL);
                } else {
                    $this->dbh = @mysql_connect($dbhost[$index], $dbuser[$index], $dbpassword[$index], true);
                }

                if ($dbcharset != null) {
                    $this->charset = $dbcharset[$index];
                }
                if ($dbcollate != null)  {
                    $this->collate = $dbcollate[$index];
                }

                $index++;
            } while ((!$this->dbh) && ($index < $minCount));
        }

Nun folgt nur noch eine kleine Änderung in der Zeile 435. Dort müssen wir dafür sorgen, dass die richtige Datenbank ausgewählt. Das bisherige...

1
        $this->select($dbname);

...ersetzen wir schnell durch die neue Version:

1
        $this->select($selectedName);

Herzlichen Glückwunsch! Wenn du alles richtig gemacht hast, sollte dein WordPress nun in der Lage sein, sich via SSL zu einer Datenbank zu verbinden und obendrein auch noch die Fähigkeit besitzen, sich zu einer Ersatzdatenbank zu verbinden, falls die erste(n) Datenbank(en) nicht erreichbar sind! 😀

Für Tipps, Ideen und Anregungen bin ich jederzeit offen 🙂 . Die Idee, die Arrays serialisiert zu übergeben, habe ich übrigens bei PHP.net gefunden 😀 ! Die Definition der Konstante CLIENT_SSL stammt ebenfalls von dort.

Und nicht vergessen: Ich hafte nicht für Schäden an Software, Hardware oder für Vermögensschäden, die durch Anwendung dieser Änderungen entstanden sind oder entstehen könnten. 😉

Update:
Es gibt da noch eine kleine Ergänzung, die ich gerne noch loswerden möchte. Zuerst einmal ist es durchaus möglich, den Quelltext so zu erweitern, dass unterschiedliche Anfragen an verschiedene Datenbanken weitergereicht werden - dadurch ist es möglich Load-Balancing zu implementieren.
Auf der anderen Seite gibt es jedoch auch ein Problem: Man muss es hinkriegen, die verschiedenen Datenbanken möglichst synchron zu halten. Es wäre ärgerlich, wenn ein Artikel in einer Datenbank erstellt wird, dieser aber in der anderen nicht zu sehen ist. Genauso problematisch sind Kommentare und Trackbacks, da diese schnell verloren gehen könnten. Eine funktionierende Synchronisation muss ich deshalb erst noch erstellen. Derzeit gebe ich mich damit zufrieden, dass nur bei der Aktivität einer Datenbank kommentiert werden kann, während dies bei der anderen Datenbank deaktiviert ist.

Update:
Ich habe noch einmal den gesamten Quelltext überarbeitet und dabei einige Änderungen überflüssig gemacht. Nun muss nur noch die Funktion __construct() in der Datei wp-db.php bearbeitet werden.

Mehrfache Grüße, Kenny

8 Kommentare » Schreibe einen Kommentar

  1. Die Anleitung geht nicht mehr ...

    Parse error: syntax error, unexpected '$selectedName' (T_VARIABLE), expecting function (T_FUNCTION) in ..... on line 678

    außerdem stehen die dinge nicht in den zeilen wo sie stehen sollten

  2. Testkommentar, wie in Twitter von dir angefragt.

    Achja, kennst du das hyperdb-Projekt?
    Das könnte für dich auch interessant sein.

        • Ich habe mir die HyperDB jetzt in Ruhe angesehen und kann sagen, dass sie nicht dem entspricht, was ich suche. Das liegt nicht primär an der Fähigkeit, mehrere DBs zu verwalten, sondern daran, dass die Lösung keine reine Failover-Lösung ist.

          Zudem habe ich bei ihr das massive Problem, dass sie sich nicht um das Synchronisieren der DBs kümmert. Diese Funktionalität habe ich meiner Variante inzwischen einverleibt (auch wenn einige Ansätze noch ziemlick hakelig sind).
          Die HyperDB setzt hier auf die Replikationsfähigkeiten von MySQL, die ich leider bei meiner zur Verfügung stehenden Infrastruktur nicht nutzen kann.

Schreibe einen Kommentar

Um Ihnen beim weiteren Kommentieren auf dieser Webseite die erneute Eingabe Ihrer Daten zu ersparen, wird beim Absenden Ihres Kommentars ein Cookie an Ihren Browser gesendet und von diesem gespeichert. Mit dem Absenden eines Kommentars auf dieser Webseite stimmen Sie der Speicherung und Übertragung dieses Cookies explizit zu.

Pflichtfelder sind mit * markiert.