rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Er zijn veel beperkingen, maar er zijn ook veel gebruiksmogelijkheden voor rqlite in plaats van SQlite.
In een project gebruik ik een SQLite database. De gegevens zijn niet kritisch, ze kunnen op elk moment opnieuw worden geladen. Toch wil ik niet dat een deel van de applicatie niet meer reageert als de SQLite database tijdelijk niet beschikbaar is.
Ik was op zoek naar een snelle, min of meer fouttolerante database, en ook naar een distributed, zodat ik sommige leesmodules kan repliceren. Er kwamen een paar oplossingen naar voren bij het zoeken op internet en rqlite leek een goede keuze.
In deze post zet ik een rqlite cluster met drie nodes op met Docker-Compose en benader ik de nodes met een Python applicatie.
Zoals altijd draai ik Ubuntu 22.04.
Beperkingen van rqlite
Ik begin met de beperkingen, omdat ze misschien niet geschikt zijn voor jouw geval. Hier zijn ze:
- Transacties worden niet ondersteund.
- Er is een klein risico op gegevensverlies in het geval dat het knooppunt crasht voordat gegevens in de wachtrij zijn opgeslagen.
- Snelheid. Verwacht niet dezelfde tijden als bij het rechtstreeks benaderen van een SQLite database. Niet alleen is er de netwerkoverhead, ook de schrijflatentie is veel hoger dan die van SQLite , veroorzaakt door het Raft consensusalgoritme. Het gebruik van bulk writes verbetert de prestaties aanzienlijk.
- Niet-deterministische functies en andere, zie de rqlite 'Developer Guide'.
Er staat meer informatie in het document 'Comparing Litestream, rqlite, and dqlite', zie onderstaande links.
De bestanden docker-compose.yml en '.env
Er zijn veel manieren om een rqlite cluster op te zetten. Hier gebruiken we de rqlite Docker image om een cluster van drie rqlite knooppunten te maken volgens de instructies voor 'Automatisch Bootstrapping', zie onderstaande koppelingen.
We stellen geen poorten bloot aan het hostsysteem, maar maken de nodes (alleen) beschikbaar op een Docker netwerk. Essentieel is het instellen van de hostnaam voor de nodes! De hostnaam wordt door de rqlite nodes gebruikt voor ontdekking en andere applicaties gebruiken de hostnamen om toegang te krijgen tot het cluster van nodes. En we gebruiken Volumes omdat we de gegevens die zijn opgeslagen in Raft willen behouden, zelfs als het cluster voor korte tijd uitvalt.
Belangrijk: Verwijder na het wijzigen van het bestand docker-compose.yml en/of '.env' de gegevens uit de gekoppelde mappen (./data/rqlite-node-1, ./data/rqlite-node-2, ./data/rqlite-node-3). Als je dit niet doet, kun je allerlei vreemd gedrag krijgen!
Het bestand 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
Maak nu het Docker netwerk aan:
> docker network create rqlite-cluster-network
En start het cluster:
> docker-compose up
De berichten in de terminal laten zien dat de knooppunten rqlite contact met elkaar maken. Staat het cluster echt aan? Om dit te controleren opent u een andere terminal en voert u een van de rqlite containers in:
> docker exec -it rqlite-cluster_rqlite-node-3_1 sh
Maak vervolgens verbinding met een van de knooppunten:
# rqlite -H rqlite-node-1
Resultaat:
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>
Geef het commando '.nodes'. Resultaat:
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
Ziezo, het cluster is up!
Laten we nu een SQL commando proberen vanuit een andere container die verbonden is met het clusternetwerk, hier gebruiken we de 'nicolaka/netshoot' image:
> docker run -it --network rqlite-cluster-network nicolaka/netshoot bash
Geef het commando om een tabel te maken:
# 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)"
]'
Resultaat:
{
"results": [
{
"time": 0.000179355
}
],
"time": 0.018545186
}
Herhaal het commando en het resultaat is:
{
"results": [
{
"error": "table foo already exists"
}
],
"time": 0.017034644
}
Geweldig, ons rqlite cluster is up and running.
Toegang tot het rqlite cluster met Python
Het rqlite project heeft ook verschillende clients, waaronder pyrqlite, een client voor Python. We gebruiken het voorbeeld van pyrqlite op de rqlite Github pagina. We brengen twee wijzigingen aan:
- De 'host'.
- We laten de tabel vallen als deze al bestaat.
Maak op het hostsysteem een submap 'code' en voeg het volgende bestand toe:
# 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()
Start en voer een Python container in, verbonden met de 'rqlite-cluster-network', en monteer onze code op het hostsysteem op '/code' in de container:
> docker run -it --net rqlite-cluster-network -v ${PWD}/code:/code python:3.11.5-slim-bullseye bash
In de container installeert u pyrqlite:
# pip install pyrqlite
Ga in de container naar de map '/code' en voer het script uit:
# python rqlite_test.py
Resultaat:
OrderedDict([('id', 1), ('name', 'a')])
OrderedDict([('id', 2), ('name', 'b')])
Werkt!
Samenvatting
Hoewel het heel eenvoudig lijkt om rqlite te gebruiken in plaats van SQLite, is dat niet zo! U moet bepalen (lezen, lezen, lezen) of rqlite aan uw eisen voldoet, wat niet eenvoudig is omdat de verschillen en beperkingen op verschillende pagina's in de documentatie worden vermeld.
Het maken van een rqlite cluster is niet moeilijk als je Docker of Docker Swarm gebruikt. Er is ook een handleiding voor Kubernetes. Het rqlite cluster geeft ons een dist gedistribueerde, min of meer fouttolerante, SQLite database.
Om fouttolerantie op applicatieniveau te krijgen, moeten we een lijst van rqlite knooppunten (hosts) aan onze applicatie toevoegen en code toevoegen om naar een ander rqlite knooppunt te schakelen als er geen rqlite knooppunt beschikbaar is. Hoe dan ook, voor mijn geval is rqlite een perfecte oplossing!
Links / credits
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
Lees meer
rqlite
Laat een reactie achter
Reageer anoniem of log in om commentaar te geven.
Opmerkingen (1)
Laat een antwoord achter
Antwoord anoniem of log in om te antwoorden.
I'm the creator of rqlite -- nice article. I'm glad you find the software useful.
Philip
(https://www.philipotoole.com)
Recent
- Database UUID primaire sleutels van je webapplicatie verbergen
- Don't Repeat Yourself (DRY) met Jinja2
- SQLAlchemy, PostgreSQL, maximum aantal rijen per user
- Toon de waarden in SQLAlchemy dynamische filters
- Veilige gegevensoverdracht met Public Key versleuteling en pyNaCl
- rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Meest bekeken
- Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
- Gebruik van UUIDs in plaats van Integer Autoincrement Primary Keys met SQLAlchemy en MariaDb
- Maak verbinding met een dienst op een Docker host vanaf een Docker container
- PyInstaller en Cython gebruiken om een Python executable te maken
- SQLAlchemy: Gebruik van Cascade Deletes om verwante objecten te verwijderen
- Flask RESTful API verzoekparametervalidatie met Marshmallow-schema's