angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

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.

17 Oktober 2023
In
post main image
https://unsplash.com/@_christianlambert

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.

avatar

I'm the creator of rqlite -- nice article. I'm glad you find the software useful.
Philip
(https://www.philipotoole.com)