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.