MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Tutorial PyMongo:test del failover di MongoDB nell'app Python

Python è un linguaggio di programmazione potente e flessibile utilizzato da milioni di sviluppatori in tutto il mondo per creare le proprie applicazioni. Non sorprende che gli sviluppatori Python utilizzino comunemente l'hosting MongoDB, il database NoSQL più popolare, per le loro implementazioni a causa della sua natura flessibile e della mancanza di requisiti di schema.

Quindi, qual è il modo migliore per usare MongoDB con Python? PyMongo è una distribuzione Python contenente strumenti per lavorare con MongoDB e il driver Python MongoDB consigliato. È un driver abbastanza maturo che supporta la maggior parte delle operazioni comuni con il database.

Quando si esegue il deployment in produzione, si consiglia vivamente di impostare in una configurazione del set di repliche MongoDB in modo che i dati siano distribuiti geograficamente per un'elevata disponibilità. Si consiglia inoltre di abilitare le connessioni SSL per crittografare il traffico client-database. Spesso eseguiamo test delle caratteristiche di failover di vari driver MongoDB per qualificarli per casi d'uso di produzione o quando i nostri clienti ci chiedono consiglio. In questo post, ti mostriamo come connetterti a un set di repliche MongoDB abilitato per SSL configurato con certificati autofirmati utilizzando PyMongo e come testare il comportamento di failover di MongoDB nel tuo codice.

Connessione a MongoDB SSL utilizzando certificati autofirmati

Il primo passo è assicurarsi che siano installate le versioni corrette di PyMongo e delle sue dipendenze. Questa guida ti aiuta a risolvere le dipendenze e la matrice di compatibilità dei driver è disponibile qui.

Il mongo_client.MongoClient i parametri che ci interessano sono ssl e ss_ca_cert . Per connettersi a un endpoint MongoDB abilitato per SSL che utilizza un certificato autofirmato, ssl deve essere impostato su True e ss_ca_cert deve puntare al file del certificato CA.

Se sei un cliente ScaleGrid, puoi scaricare il file del certificato CA per i tuoi cluster MongoDB dalla console ScaleGrid come mostrato qui:

Quindi, uno snippet di connessione sarebbe simile a:

>>> import pymongo
>>> MONGO_URI = 'mongodb://rwuser:@SG-example-0.servers.mongodirector.com:27017,SG-example-1.servers.mongodirector.com:27017,SG-example-2.servers.mongodirector.com:27017/admin?replicaSet=RS-example&ssl=true'
>>> client = pymongo.MongoClient(MONGO_URI, ssl = True, ssl_ca_certs = '')
>>> print("Databases - " + str(client.list_database_names()))
Databases - ['admin', 'local', 'test']
>>> client.close()
>>>

Se stai utilizzando i tuoi certificati autofirmati in cui la verifica del nome host potrebbe non riuscire, dovrai anche impostare ssl_match_hostname parametro su Falso . Come dice la documentazione del driver, questo non è raccomandato in quanto rende la connessione suscettibile agli attacchi man-in-the-middle.

Test del comportamento di failover

Con le implementazioni di MongoDB, i failover non sono considerati eventi importanti come lo erano con i tradizionali sistemi di gestione dei database. Sebbene la maggior parte dei driver MongoDB tenti di astrarre questo evento, gli sviluppatori dovrebbero comprendere e progettare le proprie applicazioni per tale comportamento, poiché le applicazioni dovrebbero aspettarsi errori di rete transitori e riprovare prima di filtrare gli errori.

Puoi testare la resilienza delle tue applicazioni inducendo failover durante l'esecuzione del carico di lavoro. Il modo più semplice per indurre il failover è eseguire il comando rs.stepDown():

RS-example-0:PRIMARY> rs.stepDown()
2019-04-18T19:44:42.257+0530 E QUERY [thread1] Error: error doing query: failed: network error while attempting to run command 'replSetStepDown' on host 'SG-example-1.servers.mongodirector.com:27017' :
DB.prototype.runCommand@src/mongo/shell/db.js:168:1
DB.prototype.adminCommand@src/mongo/shell/db.js:185:1
rs.stepDown@src/mongo/shell/utils.js:1305:12
@(shell):1:1
2019-04-18T19:44:42.261+0530 I NETWORK [thread1] trying reconnect to SG-example-1.servers.mongodirector.com:27017 (X.X.X.X) failed
2019-04-18T19:44:43.267+0530 I NETWORK [thread1] reconnect SG-example-1.servers.mongodirector.com:27017 (X.X.X.X) ok
RS-example-0:SECONDARY>

Uno dei modi in cui mi piace testare il comportamento dei conducenti è scrivere una semplice app di scrittura "perpetua". Si tratterebbe di un semplice codice che continua a scrivere nel database a meno che non venga interrotto dall'utente e stamperebbe tutte le eccezioni che incontra per aiutarci a comprendere il comportamento del driver e del database. Tengo anche traccia dei dati che scrive per assicurarmi che non ci siano perdite di dati non segnalate nel test. Ecco la parte rilevante del codice di test che useremo per testare il nostro comportamento di failover di MongoDB:

import logging
import traceback
...
import pymongo
...
logger = logging.getLogger("test")

MONGO_URI = 'mongodb://rwuser:@SG-example-0.servers.mongodirector.com:48273,SG-example-1.servers.mongodirector.com:27017,SG-example-2.servers.mongodirector.com:27017/admin?replicaSet=RS-example-0&ssl=true'

try:
    logger.info("Attempting to connect...")
    client = pymongo.MongoClient(MONGO_URI, ssl = True, ssl_ca_certs = 'path-to-cacert.pem')
    db = client['test']
    collection = db['test']
    i = 0
    while True:
        try:
            text = ''.join(random.choices(string.ascii_uppercase + string.digits, k = 3))
            doc = { "idx": i, "date" : datetime.utcnow(), "text" : text}
            i += 1
            id = collection.insert_one(doc).inserted_id
            logger.info("Record inserted - id: " + str(id))
            sleep(3)
        except pymongo.errors.ConnectionFailure as e:
            logger.error("ConnectionFailure seen: " + str(e))
            traceback.print_exc(file = sys.stdout)
            logger.info("Retrying...")

    logger.info("Done...")
except Exception as e:
    logger.error("Exception seen: " + str(e))
    traceback.print_exc(file = sys.stdout)
finally:
    client.close()

Il tipo di voci che questo scrive assomiglia a:

RS-example-0:PRIMARY> db.test.find()
{ "_id" : ObjectId("5cb6d6269ece140f18d05438"), "idx" : 0, "date" : ISODate("2019-04-17T07:30:46.533Z"), "text" : "400" }
{ "_id" : ObjectId("5cb6d6299ece140f18d05439"), "idx" : 1, "date" : ISODate("2019-04-17T07:30:49.755Z"), "text" : "X63" }
{ "_id" : ObjectId("5cb6d62c9ece140f18d0543a"), "idx" : 2, "date" : ISODate("2019-04-17T07:30:52.976Z"), "text" : "5BX" }
{ "_id" : ObjectId("5cb6d6329ece140f18d0543c"), "idx" : 4, "date" : ISODate("2019-04-17T07:30:58.001Z"), "text" : "TGQ" }
{ "_id" : ObjectId("5cb6d63f9ece140f18d0543d"), "idx" : 5, "date" : ISODate("2019-04-17T07:31:11.417Z"), "text" : "ZWA" }
{ "_id" : ObjectId("5cb6d6429ece140f18d0543e"), "idx" : 6, "date" : ISODate("2019-04-17T07:31:14.654Z"), "text" : "WSR" }
..

Gestione dell'eccezione ConnectionFailure

Nota che catturiamo l'eccezione ConnectionFailure per affrontare tutti i problemi relativi alla rete che potremmo incontrare a causa di failover:stampiamo l'eccezione e continuiamo a tentare di scrivere nel database. La documentazione del driver consiglia che:

Se un'operazione non riesce a causa di un errore di rete, viene generato ConnectionFailure e il client si riconnette in background. Il codice dell'applicazione dovrebbe gestire questa eccezione (riconoscendo che l'operazione non è riuscita) e quindi continuare a essere eseguita.

Eseguiamo questo ed eseguiamo un failover del database durante l'esecuzione. Ecco cosa succede:

04/17/2019 12:49:17 PM INFO Attempting to connect...
04/17/2019 12:49:20 PM INFO Record inserted - id: 5cb6d3789ece145a2408cbc7
04/17/2019 12:49:23 PM INFO Record inserted - id: 5cb6d37b9ece145a2408cbc8
04/17/2019 12:49:27 PM INFO Record inserted - id: 5cb6d37e9ece145a2408cbc9
04/17/2019 12:49:30 PM ERROR PyMongoError seen: connection closed
Traceback (most recent call last):
    id = collection.insert_one(doc).inserted_id
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\collection.py", line 693, in insert_one
    session=session),
...
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\network.py", line 173, in receive_message
    _receive_data_on_socket(sock, 16))
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\network.py", line 238, in _receive_data_on_socket
    raise AutoReconnect("connection closed")
pymongo.errors.AutoReconnect: connection closed
04/17/2019 12:49:30 PM INFO Retrying...
04/17/2019 12:49:42 PM INFO Record inserted - id: 5cb6d3829ece145a2408cbcb
04/17/2019 12:49:45 PM INFO Record inserted - id: 5cb6d3919ece145a2408cbcc
04/17/2019 12:49:49 PM INFO Record inserted - id: 5cb6d3949ece145a2408cbcd
04/17/2019 12:49:52 PM INFO Record inserted - id: 5cb6d3989ece145a2408cbce

Si noti che il driver impiega circa 12 secondi per comprendere la nuova topologia, connettersi alla nuova primaria e continuare a scrivere. L'eccezione sollevata è errori . Riconnessione automatica che è una sottoclasse di ConnectionFailure .

Tutorial PyMongo:test del failover di MongoDB nell'app PythonFai clic per twittare

Potresti fare qualche corsa in più per vedere quali altre eccezioni vengono visualizzate. Ad esempio, ecco un'altra traccia di eccezione che ho riscontrato:

    id = collection.insert_one(doc).inserted_id
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\collection.py", line 693, in insert_one
    session=session),
...
  File "C:\Users\Randome\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\network.py", line 150, in command
    parse_write_concern_error=parse_write_concern_error)
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\helpers.py", line 132, in _check_command_response
    raise NotMasterError(errmsg, response)
pymongo.errors.NotMasterError: not master

Questa eccezione è anche una sottoclasse di ConnectionFailure.

Parametro "retryWrites"

Un'altra area per testare il comportamento di failover di MongoDB sarebbe vedere come altre variazioni dei parametri influiscono sui risultati. Un parametro rilevante è "retryWrites ':

retryWrites:(boolean) Se le operazioni di scrittura supportate eseguite all'interno di questo MongoClient verranno ritentate una volta dopo un errore di rete su MongoDB 3.6+. L'impostazione predefinita è False.

Vediamo come funziona questo parametro con un failover. L'unica modifica apportata al codice è:

client = pymongo.MongoClient(MONGO_URI, ssl = True, ssl_ca_certs = 'path-to-cacert.pem', retryWrites = True)

Eseguiamolo ora, quindi eseguiamo un failover del sistema di database:

04/18/2019 08:49:30 PM INFO Attempting to connect...
04/18/2019 08:49:35 PM INFO Record inserted - id: 5cb895869ece146554010c77
04/18/2019 08:49:38 PM INFO Record inserted - id: 5cb8958a9ece146554010c78
04/18/2019 08:49:41 PM INFO Record inserted - id: 5cb8958d9ece146554010c79
04/18/2019 08:49:44 PM INFO Record inserted - id: 5cb895909ece146554010c7a
04/18/2019 08:49:48 PM INFO Record inserted - id: 5cb895939ece146554010c7b <<< Failover around this time
04/18/2019 08:50:04 PM INFO Record inserted - id: 5cb895979ece146554010c7c
04/18/2019 08:50:07 PM INFO Record inserted - id: 5cb895a79ece146554010c7d
04/18/2019 08:50:10 PM INFO Record inserted - id: 5cb895aa9ece146554010c7e
04/18/2019 08:50:14 PM INFO Record inserted - id: 5cb895ad9ece146554010c7f
...

Nota come l'inserimento dopo il failover impiega circa 12 secondi, ma va a buon fine come retryWrites il parametro assicura che la scrittura non riuscita venga ripetuta. Ricorda che l'impostazione di questo parametro non ti esonera dalla gestione del ConnectionFailure eccezione - devi preoccuparti delle letture e di altre operazioni il cui comportamento non è influenzato da questo parametro. Inoltre, non risolve completamente il problema, anche per le operazioni supportate:a volte i failover possono richiedere più tempo per essere completati e retryWrites da solo non basterà.

Configurazione dei valori di timeout della rete

rs.stepDown() induce un failover piuttosto rapido, poiché il set di repliche primario viene istruito per diventare un secondario e i secondari tengono un'elezione per determinare il nuovo primario. Nelle distribuzioni di produzione, il carico di rete, la partizione e altri problemi simili ritardano il rilevamento dell'indisponibilità del server principale, prolungando così il tempo di failover. Ti capita spesso anche di imbatterti in errori PyMongo come errors.ServerSelectionTimeoutError , errori.Timeout di rete, ecc. durante problemi di rete e failover.

Se ciò si verifica molto spesso, devi cercare di modificare i parametri di timeout. Il relativo MongoClient i parametri di timeout sono serverSelectionTimeoutMS , connectTimeoutMS, e socketTimeoutMS . Di questi, selezionando un valore maggiore per serverSelectionTimeoutMS molto spesso aiuta a gestire gli errori durante i failover:

serverSelectionTimeoutMS:(intero) Controlla per quanto tempo (in millisecondi) il driver attenderà per trovare un server appropriato disponibile per eseguire un'operazione di database; durante l'attesa possono essere eseguite più operazioni di monitoraggio del server, ciascuna controllata da connectTimeoutMS. Il valore predefinito è 30000 (30 secondi).

Pronto per utilizzare MongoDB nella tua applicazione Python? Consulta il nostro articolo Guida introduttiva a Python e MongoDB per vedere come puoi iniziare a lavorare in soli 5 semplici passaggi. ScaleGrid è l'unico provider MongoDB DBaaS che ti dà accesso SSH completo alle tue istanze in modo da poter eseguire il tuo server Python sulla stessa macchina del tuo server MongoDB. Automatizza le tue distribuzioni cloud MongoDB su AWS, Azure o DigitalOcean con server dedicati, alta disponibilità e ripristino di emergenza in modo da poterti concentrare sullo sviluppo della tua applicazione Python.