rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Es gibt viele Einschränkungen, aber es gibt auch viele Anwendungsfälle für rqlite anstelle von SQlite.
In einem Projekt verwende ich eine SQLite -Datenbank. Die Daten sind nicht kritisch, sie können jederzeit neu geladen werden. Dennoch möchte ich nicht, dass ein Teil der Anwendung nicht mehr reagiert, wenn die SQLite -Datenbank vorübergehend nicht verfügbar ist.
Ich war auf der Suche nach einer schnellen, mehr oder weniger fehlertoleranten Datenbank, und auch distributed, so dass ich einige Lesermodule replizieren kann. Bei der Suche im Internet tauchten einige Lösungen auf, und rqlite schien eine gute Wahl zu sein.
In diesem Beitrag richte ich einen rqlite -Cluster mit drei Knoten mit Docker-Compose ein und greife dann mit einer Python -Anwendung auf die Knoten zu.
Ich verwende wie immer Ubuntu 22.04.
Beschränkungen von rqlite
Ich fange mit den Einschränkungen an, weil sie vielleicht nicht für Ihren Fall passen. Hier sind sie:
- Transaktionen werden nicht unterstützt.
- Es besteht ein geringes Risiko von Datenverlusten, falls der Knoten abstürzt, bevor die Daten in der Warteschlange persistiert sind.
- Geschwindigkeit. Erwarten Sie nicht die gleichen Zeiten wie beim direkten Zugriff auf eine SQLite -Datenbank. Es gibt nicht nur den Netzwerk-Overhead, auch die Schreiblatenz ist aufgrund des Raft -Konsensalgorithmus viel höher als bei SQLite . Die Verwendung von Bulk-Writes verbessert die Leistung erheblich.
- Nicht-deterministische Funktionen und andere, siehe das Dokument rqlite 'Developer Guide'.
Weitere Informationen finden Sie in dem Dokument 'Comparing Litestream, rqlite, and dqlite', siehe Links unten.
Die Dateien docker-compose.yml und '.env'.
Es gibt viele Möglichkeiten, einen rqlite -Cluster einzurichten. Hier verwenden wir den rqlite Docker image , um einen Cluster aus drei rqlite -Knoten zu erstellen, indem wir den Anweisungen für 'Automatic Bootstrapping' folgen, siehe Links unten.
Wir geben keine Ports für das Hostsystem frei, sondern stellen die Knoten (nur) in einem Docker -Netzwerk zur Verfügung. Der Hostname wird von den rqlite -Knoten für die Erkennung verwendet, und andere Anwendungen verwenden die Hostnamen für den Zugriff auf den Knoten-Cluster. Und wir verwenden Volumes , weil wir die im Raft gespeicherten Daten auch dann erhalten wollen, wenn der Cluster für kurze Zeit ausfällt.
Wichtig: Nach der Änderung der Datei docker-compose.yml und/oder '.env' müssen die Daten aus den gemounteten Verzeichnissen (./data/rqlite-node-1, ./data/rqlite-node-2, ./data/rqlite-node-3) entfernt werden. Wenn Sie dies nicht tun, können Sie alle Arten von seltsamen Verhaltensweisen erhalten!
Die Datei docker-compose.yml:
# docker-compose.yml
version: '3.7'
services:
rqlite-node-1:
image: rqlite/rqlite:7.21.4
hostname: ${RQLITE_NODE_1_HOSTNAME}
volumes:
- ./data/rqlite-node-1:/rqlite/file/data
command:
- rqlited
- -node-id
- "${RQLITE_NODE_1_NODE_ID}"
- -http-addr
- "${RQLITE_NODE_1_HTTP_ADDR}"
- -raft-addr
- "${RQLITE_NODE_1_RAFT_ADDR}"
- -http-adv-addr
- "${RQLITE_NODE_1_HTTP_ADV_ADDR}"
- -raft-adv-addr
- "${RQLITE_NODE_1_RAFT_ADV_ADDR}"
# join
- -bootstrap-expect
- "3"
- -join
- "${RQLITE_NODE_1_JOIN_ADDR},${RQLITE_NODE_2_JOIN_ADDR},${RQLITE_NODE_3_JOIN_ADDR}"
- /rqlite/file/data
networks:
- rqlite-cluster-network
rqlite-node-2:
image: rqlite/rqlite:7.21.4
hostname: ${RQLITE_NODE_2_HOSTNAME}
volumes:
- ./data/rqlite-node-2:/rqlite/file/data
command:
- rqlited
- -node-id
- "${RQLITE_NODE_2_NODE_ID}"
- -http-addr
- "${RQLITE_NODE_2_HTTP_ADDR}"
- -raft-addr
- "${RQLITE_NODE_2_RAFT_ADDR}"
- -http-adv-addr
- "${RQLITE_NODE_2_HTTP_ADV_ADDR}"
- -raft-adv-addr
- "${RQLITE_NODE_2_RAFT_ADV_ADDR}"
# join
- -bootstrap-expect
- "3"
- -join
- "${RQLITE_NODE_1_JOIN_ADDR},${RQLITE_NODE_2_JOIN_ADDR},${RQLITE_NODE_3_JOIN_ADDR}"
- /rqlite/file/data
networks:
- rqlite-cluster-network
rqlite-node-3:
image: rqlite/rqlite:7.21.4
hostname: ${RQLITE_NODE_3_HOSTNAME}
volumes:
- ./data/rqlite-node-3:/rqlite/file/data
command:
- rqlited
- -node-id
- "${RQLITE_NODE_3_NODE_ID}"
- -http-addr
- "${RQLITE_NODE_3_HTTP_ADDR}"
- -raft-addr
- "${RQLITE_NODE_3_RAFT_ADDR}"
- -http-adv-addr
- "${RQLITE_NODE_3_HTTP_ADV_ADDR}"
- -raft-adv-addr
- "${RQLITE_NODE_3_RAFT_ADV_ADDR}"
# join
- -bootstrap-expect
- "3"
- -join
- "${RQLITE_NODE_1_JOIN_ADDR},${RQLITE_NODE_2_JOIN_ADDR},${RQLITE_NODE_3_JOIN_ADDR}"
- /rqlite/file/data
networks:
- rqlite-cluster-network
networks:
rqlite-cluster-network:
external: true
name: rqlite-cluster-network
The '.env'-file:
# .env
COMPOSE_PROJECT_NAME=rqlite-cluster
# RQLITE_NODE_1
RQLITE_NODE_1_HOSTNAME=rqlite-node-1
RQLITE_NODE_1_NODE_ID=rqlite-node-1
RQLITE_NODE_1_DATA_DIR=/rqlite/file/data
RQLITE_NODE_1_HTTP_ADDR=rqlite-node-1:4001
RQLITE_NODE_1_RAFT_ADDR=rqlite-node-1:4002
RQLITE_NODE_1_HTTP_ADV_ADDR=rqlite-node-1:4001
RQLITE_NODE_1_RAFT_ADV_ADDR=rqlite-node-1:4002
# join
RQLITE_NODE_1_JOIN_ADDR=rqlite-node-1:4001
# RQLITE_NODE_2
RQLITE_NODE_2_HOSTNAME=rqlite-node-2
RQLITE_NODE_2_NODE_ID=rqlite-node-2
RQLITE_NODE_2_DATA_DIR=/rqlite/file/data
RQLITE_NODE_2_HTTP_ADDR=rqlite-node-2:4001
RQLITE_NODE_2_RAFT_ADDR=rqlite-node-2:4002
RQLITE_NODE_2_HTTP_ADV_ADDR=rqlite-node-2:4001
RQLITE_NODE_2_RAFT_ADV_ADDR=rqlite-node-2:4002
# join
RQLITE_NODE_2_JOIN_ADDR=rqlite-node-2:4001
# RQLITE_NODE_3
RQLITE_NODE_3_HOSTNAME=rqlite-node-3
RQLITE_NODE_3_NODE_ID=rqlite-node-3
RQLITE_NODE_3_DATA_DIR=/rqlite/file/data
RQLITE_NODE_3_HTTP_ADDR=rqlite-node-3:4001
RQLITE_NODE_3_RAFT_ADDR=rqlite-node-3:4002
RQLITE_NODE_3_HTTP_ADV_ADDR=rqlite-node-3:4001
RQLITE_NODE_3_RAFT_ADV_ADDR=rqlite-node-3:4002
# join
RQLITE_NODE_3_JOIN_ADDR=rqlite-node-3:4001
Erstellen Sie nun das Netzwerk Docker :
> docker network create rqlite-cluster-network
Und starten Sie den Cluster:
> docker-compose up
Die Meldungen im Terminal zeigen, dass die rqlite -Knoten miteinander in Kontakt stehen. Ist der Cluster wirklich gestartet? Um dies zu überprüfen, öffnen Sie ein weiteres Terminal und geben Sie einen der rqlite -Container ein:
> docker exec -it rqlite-cluster_rqlite-node-3_1 sh
Verbinden Sie sich dann mit einem der Knoten:
# rqlite -H rqlite-node-1
Ergebnis:
Welcome to the rqlite CLI. Enter ".help" for usage hints.
Version v7.21.4, commit 971921f1352bdc73e4e66a1ec43be8c1028ff18b, branch master
Connected to rqlited version v7.21.4
rqlite-node-1:4001>
Geben Sie den Befehl '.nodes' ein. Ergebnis:
rqlite-node-2:
leader: false
time: 0.001115574
api_addr: http://rqlite-node-2:4001
addr: rqlite-node-2:4002
reachable: true
rqlite-node-3:
leader: false
time: 0.001581149
api_addr: http://rqlite-node-3:4001
addr: rqlite-node-3:4002
reachable: true
rqlite-node-1:
time: 0.000009044
api_addr: http://rqlite-node-1:4001
addr: rqlite-node-1:4002
reachable: true
leader: true
Das war's, der Cluster ist gestartet!
Versuchen wir nun einen SQL -Befehl von einem anderen Container aus, der mit dem Clusternetzwerk verbunden ist, hier verwenden wir das Image 'nicolaka/netshoot' :
> docker run -it --network rqlite-cluster-network nicolaka/netshoot bash
Geben Sie den Befehl zum Erstellen einer Tabelle aus:
# curl -XPOST 'rqlite-node-2:4001/db/execute?pretty&timings' -H "Content-Type: application/json" -d '[
"CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT, age INTEGER)"
]'
Ergebnis:
{
"results": [
{
"time": 0.000179355
}
],
"time": 0.018545186
}
Wiederholen Sie den Befehl und das Ergebnis ist:
{
"results": [
{
"error": "table foo already exists"
}
],
"time": 0.017034644
}
Großartig, unser rqlite -Cluster ist eingerichtet und läuft.
Zugriff auf den rqlite -Cluster mit Python
Das Projekt rqlite hat auch mehrere Clients, darunter pyrqlite, ein Client für Python. Wir verwenden das Beispiel pyrqlite auf der Seite rqlite Github . Wir nehmen zwei Änderungen vor:
- Der 'host'.
- Wir löschen die Tabelle, falls sie bereits existiert.
Auf dem Host-System erstellen Sie ein Unterverzeichnis 'code' und fügen die folgende Datei hinzu:
# rqlite_test.py
import pyrqlite.dbapi2 as dbapi2
# Connect to the database
connection = dbapi2.connect(
host='rqlite-node-2',
port=4001,
)
try:
with connection.cursor() as cursor:
cursor.execute('DROP TABLE IF EXISTS foo')
cursor.execute('CREATE TABLE foo (id integer not null primary key, name text)')
cursor.executemany('INSERT INTO foo(name) VALUES(?)', seq_of_parameters=(('a',), ('b',)))
with connection.cursor() as cursor:
# Read a single record with qmark parameter style
sql = "SELECT `id`, `name` FROM `foo` WHERE `name`=?"
cursor.execute(sql, ('a',))
result = cursor.fetchone()
print(result)
# Read a single record with named parameter style
sql = "SELECT `id`, `name` FROM `foo` WHERE `name`=:name"
cursor.execute(sql, {'name': 'b'})
result = cursor.fetchone()
print(result)
finally:
connection.close()
Starten Sie einen Python -Container, der mit dem 'rqlite-cluster-network' verbunden ist, und mounten Sie unseren Code auf dem Hostsystem unter '/code' innerhalb des Containers:
> docker run -it --net rqlite-cluster-network -v ${PWD}/code:/code python:3.11.5-slim-bullseye bash
Innerhalb des Containers installieren Sie pyrqlite:
# pip install pyrqlite
Wechseln Sie innerhalb des Containers in das Verzeichnis '/code' und führen Sie das Skript aus:
# python rqlite_test.py
Ergebnis:
OrderedDict([('id', 1), ('name', 'a')])
OrderedDict([('id', 2), ('name', 'b')])
Funktioniert!
Zusammenfassung
Obwohl es so aussieht, als ob es sehr einfach wäre, rqlite anstelle von SQLite zu verwenden, ist es das nicht! Sie müssen feststellen (lesen, lesen, lesen), ob rqlite Ihren Anforderungen entspricht, was nicht einfach ist, da die Unterschiede und Einschränkungen auf mehreren Seiten in der Dokumentation erwähnt werden.
Die Erstellung eines rqlite -Clusters ist nicht schwierig, wenn Sie Docker oder Docker Swarm verwenden. Es gibt auch einen Leitfaden für Kubernetes. Mit dem rqlite -Cluster erhalten wir eine dist-verteilte, mehr oder weniger fehlertolerante SQLite -Datenbank.
Um Fehlertoleranz auf Anwendungsebene zu erreichen, müssen wir eine Liste von rqlite -Knoten (Hosts) zu unserer Anwendung hinzufügen und etwas Code hinzufügen, um zu einem anderen rqlite -Knoten zu wechseln, wenn ein rqlite -Knoten nicht verfügbar ist. Wie auch immer, für meinen Fall ist rqlite eine perfekte Lösung!
Links / Impressum
Comparing Litestream, rqlite, and dqlite
https://gcore.com/learning/comparing-litestream-rqlite-dqlite
rqlite
https://rqlite.io
rqlite - Automatic clustering: Automatic Bootstrapping
https://rqlite.io/docs/clustering/automatic-clustering
rqlite - Developer Guide
https://rqlite.io/docs/api
rqlite/rqlite Docker image
https://hub.docker.com/r/rqlite/rqlite
Mehr erfahren
rqlite
Einen Kommentar hinterlassen
Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.
Kommentare (1)
Eine Antwort hinterlassen
Antworten Sie anonym oder melden Sie sich an, um zu antworten.
I'm the creator of rqlite -- nice article. I'm glad you find the software useful.
Philip
(https://www.philipotoole.com)
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas