Verteilte Datenbank mit MySQL-Proxy

Zwischen-Welt

Die MySQL-Datenbank hat sich im Gespann mit PHP und Apache zum tüchtigen Arbeitspferd auf Webservern entwickelt. Der freie MySQL-Proxy kann einzelne Datenbank-Server zu Clustern verbinden und sogar Anfragen und Antworten on-the-fly umschreiben.

Oberflächlich betrachtet ist der MySQL-Proxy [1] nicht mehr als ein Vermittler zwischen Datenbank-Client und -Server. Ohne spezifische Einstellungen gibt er Anfragen und Antworten zwischen Client und Server ohne weitere Veränderungen weiter. Mit dem nötigen Know-how eingesetzt, ist MySQL-Proxy ein mächtiges Werkzeug, dass eingehenden Datenbankabfragen und ihre Ergebnisse überwachen, analysieren und verändern kann. Dieser Artikel stellt seine Möglichkeiten und Einsatzgebiete auf und gibt eine Einführung, wie man MySQL-Proxy in eigenen Projekten einsetzen kann.

Die Kommunikation zwischen Client und Server bietet typischerweise drei Eingriffspunkte für einen Proxy: beim Verbindungsaufbau (Transparent Proxy, Loadbalancing), nach dem Empfangen der Client-Abfrage (Query-Modification, Code Injection) vor der Rückgabe des Query-Results an den Client (Result-Modification). An allen Punkten ist eine Vorgangsprotokollierung möglich, um das Systemverhalten zu analysieren. Abbildung 1 zeigt schematisch die möglichen Ansatzpunkte und verdeutlicht die damit verbunden Möglichkeiten.

Abbildung 1: Der Mysql-Proxy kann Anfragen an die Datenbank verändern, wie auch dessen Antworten.

Die Vermittlungsstelle

Nach erfolgreicher Installation als Binary oder aus dem Quellcode ist der MySQL-Proxy einsatzbereit und kann mit dem Befehl »mysql-proxy« in Betrieb genommen werden. Mehr ist im ersten Schritt nicht notwendig, um einen Proxy auf seinem Standardport zu starten. Allerdings erwartet der Proxy einen lokal mit Standardeinstellungen laufenden MySQL-Server. Ein Verbindungsaufbau unter Angabe des Ports nutzt somit bereits den Proxy als Vermittler.

mysql --host=127.0.0.1 --port=4040 ↩
--user=Username --password

Zwar bietet der Proxy hier nicht als ein Port-Forwarder, jedoch basieren alle weiteren Funktionen auf diesem simplen Prinzip. Natürlich muss das Proxy-Zielsystem nicht auf dem gleichen Server oder Standardport laufen. Der Befehl »mysql-proxy --help-all« gibt Auskunft über die zur Verfügung stehenden Parameter.

Erweitert man die Konfiguration um einen zusätzlichen Backend-Server, hat man den einfachen Proxy-Server im Handumdrehen um Loadbalancer- und Failover-Funktionalität erweitert. Der Kommandozeilenbefehl könnte dann wie folgt aussehen.

mysql-proxy ↩
--proxy-backend-addresses=192.168.192.1:3306 ↩
--proxy-backend-addresses=192.168.192.2:3306 &

Alle eingehenden Anfragen verteilt der Proxy dann im Round-Robin-Prinzip an die verfügbaren Backend-Server. Verbindungsfehler, die zum Beispiel beim Ausfall eines Backend-Servers auftreten, meldet er auf der Standardausgabe. Bei ausschließlicher Verwendung als Loadbalancer- beziehungsweise Failover-Proxy sollte man das Profiling mit dem Parameter »--proxy-skip-profiling« ausschalten, um den Overhead zu minimieren.

Queries unter die Lupe nehmen

Über die reine Vermittlungstätigkeit hinaus, bietet MySQL-Proxy eine außergewöhnlich flexible Schnittstelle, um den Datenfluss zwischen Client und Server(n) zu überwachen, zu protokollieren und vor allem zu verändern. Mit Hilfe der integrierten Lua-Engine bietet der MySQL-Proxy nahezu beliebige Möglichkeiten, den Datenfluss zu manipulieren.

Lua ist eine Skriptsprache, die meist zur Steuerung von C-Programmen dient, um diese ohne Veränderung des Source-Codes zu erweitern und zu skripten. Lua-Code wird zur Ausführung in eine Art Bytecode übersetzt und ist somit quasi plattformunabhängig. Ein Beschreibung von Lua und deren Aufbau würde den Rahmen des Artikels sprengen, exemplarisch werden jedoch auf einige Skripte näher beschrieben. Eine Einführung in Lua gibt der Artikel [2] in einem früheren Linux-Magazin-Sonderheft.

Wie erwähnt kann Lua an verschiedenen Punkten der Client-Server-Kommunikation ansetzten. Zur Veränderung beziehungsweise Erweiterung einer Abfrage bedient sich Lua der »read_query()« -Funktion. Der Lua-Code in Listing 1 verdeutlicht das Funktionsprinzip.

Listing 1

inform_user.lua

-- inform_user.lua
 function read_query(packet)
   if string.byte(packet) == proxy.COM_QUERY then
     print("The client committed the following query: " .. string.sub(packet, 2))
   end
 end

Der Start des Proxys mit Angabe des Lua-Skripts aktiviert die entsprechende Funktion und führt zu folgender Ausgabe auf der Console:

The client committed the following query: ↩
select @@version_comment limit 1
The client committed the following query: ↩
SHOW TABLES FROM test

Dieses einfache Beispiel zeigt bereits deutlich, welche Möglichkeiten der MySQL-Proxy und sein Skript-Interface hier bieten.

Interessant wird eine solcher Proxy-Einsatz natürlich richtig bei der Modifikation des Datenverkehrs. Das Lua-Script in Listing 2 korrigiert einzelne Fehler in der Query-Syntax und verhindert die Ausführung einiger Befehle.

Listing 2

inspect_query.lua

-- inspect_query.lua
 function read_query( packet )
   if string.byte(packet) == proxy.COM_QUERY then
     local query = string.sub(packet, 2)
     print ("received " .. query)
     -- matches "SELECT" as first word of the query
     if string.match(string.upper(query), '^%s*SELECT') then
         proxy.response = {
            type = proxy.MYSQLD_PACKET_ERR,
            errmsg = "SELECT ON DATABASE NOT ALLOWED",
            errno = 1305,
            sqlstate = "HY000"
         }
         return proxy.PROXY_SEND_QUERY
     end
     local replacing = false
     -- matches "INSET" as first word of the query
     if string.match(string.upper(query), '^%s*INSET') then
         query = string.gsub(query,'^%s*%w+', 'INSERT')
         replacing = true
     -- matches "UDATE" as first word of the query
     elseif string.match(string.upper(query), '^%s*UDATE') then
         query = string.gsub(query,'^%s*%w+', 'UPDATE')
         replacing = true
     end
     if (replacing) then
         print("replaced with " .. query )
         proxy.queries:append(1, string.char(proxy.COM_QUERY) .. query )
         return proxy.PROXY_SEND_QUERY
     end
     -- matches "SELECT" as first word of the query
     if string.match(string.upper(query), '^%s*SELECT') then
         proxy.response = {
            type = proxy.MYSQLD_PACKET_ERR,
            errmsg = "SELECT ON DATABASE NOT ALLOWED",
            errno = 1205,
            sqlstate = "HY000"
         }
     end
   end
 end

Nach dem Start des MySQL-Proxys mit dem Parameter »--proxy-lua-script=inspect_query.lua« führt der Proxy folgende Veränderungen durch: Ein Select der Daten wird durch die erste Abfrage im Lua-Script unterdrückt und führt zu einem Client-Fehler mit angegebener Warnung. So lassen sich einfache Select-Abfragen komplett unterdrücken. In den beiden nächsten Schritten korrigiert das Skript falsch geschriebene Insert- oder Update-Befehle, was im weiteren zum korrekten Ergebnis führt.

Mit Hilfe von Lua sind beliebige Kombinationen und Einbeziehung von Fremddaten möglich. So könnten auch Abfragen auf einzelne Namen innerhalb des Proxies unterdrückt oder auf Wunsch verändert.

Ähnliche Artikel

comments powered by Disqus
Einmal pro Woche aktuelle News, kostenlose Artikel und nützliche ADMIN-Tipps.
Ich habe die Datenschutzerklärung gelesen und bin einverstanden.

Konfigurationsmanagement

Ich konfiguriere meine Server

  • von Hand
  • mit eigenen Skripts
  • mit Puppet
  • mit Ansible
  • mit Saltstack
  • mit Chef
  • mit CFengine
  • mit dem Nix-System
  • mit Containern
  • mit anderer Konfigurationsmanagement-Software

Ausgabe /2023