MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

PyMongo-Tutorial:MongoDB-Failover in Ihrer Python-App testen

Python ist eine leistungsstarke und flexible Programmiersprache, die von Millionen von Entwicklern auf der ganzen Welt zum Erstellen ihrer Anwendungen verwendet wird. Es ist keine Überraschung, dass Python-Entwickler aufgrund ihrer Flexibilität und fehlenden Schemaanforderungen häufig MongoDB-Hosting, die beliebteste NoSQL-Datenbank, für ihre Bereitstellungen nutzen.

Also, was ist der beste Weg, MongoDB mit Python zu verwenden? PyMongo ist eine Python-Distribution, die Tools zum Arbeiten mit MongoDB und den empfohlenen Python-MongoDB-Treiber enthält. Es ist ein ziemlich ausgereifter Treiber, der die meisten gängigen Operationen mit der Datenbank unterstützt.

Bei der Bereitstellung in der Produktion wird dringend empfohlen, eine MongoDB-Replikatsatzkonfiguration einzurichten, damit Ihre Daten für Hochverfügbarkeit geografisch verteilt sind. Es wird außerdem empfohlen, SSL-Verbindungen zu aktivieren, um den Datenverkehr zwischen Client und Datenbank zu verschlüsseln. Wir führen häufig Tests der Failover-Eigenschaften verschiedener MongoDB-Treiber durch, um sie für Anwendungsfälle in der Produktion zu qualifizieren, oder wenn unsere Kunden uns um Rat fragen. In diesem Beitrag zeigen wir Ihnen, wie Sie mithilfe von PyMongo eine Verbindung zu einem SSL-fähigen MongoDB-Replikatsatz herstellen, der mit selbstsignierten Zertifikaten konfiguriert ist, und wie Sie das MongoDB-Failover-Verhalten in Ihrem Code testen.

Verbindung zu MongoDB SSL mit selbstsignierten Zertifikaten herstellen

Der erste Schritt besteht darin, sicherzustellen, dass die richtigen Versionen von PyMongo und seinen Abhängigkeiten installiert sind. Dieser Leitfaden hilft Ihnen, die Abhängigkeiten zu klären, und die Treiberkompatibilitätsmatrix finden Sie hier.

Der mongo_client.MongoClient Parameter, die uns interessieren, sind ssl und ss_ca_cert . Um eine Verbindung zu einem SSL-fähigen MongoDB-Endpunkt herzustellen, der ein selbstsigniertes Zertifikat verwendet, muss ssl muss auf True gesetzt werden und ss_ca_cert muss auf die CA-Zertifikatsdatei verweisen.

Wenn Sie ein ScaleGrid-Kunde sind, können Sie die CA-Zertifikatsdatei für Ihre MongoDB-Cluster wie hier gezeigt von der ScaleGrid-Konsole herunterladen:

Ein Verbindungs-Snippet würde also so aussehen:

>>> 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()
>>>

Wenn Sie Ihre eigenen selbstsignierten Zertifikate verwenden, bei denen die Überprüfung des Hostnamens möglicherweise fehlschlägt, müssen Sie auch ssl_match_hostname Parameter auf False . Wie die Treiberdokumentation sagt, wird dies nicht empfohlen, da es die Verbindung anfällig für Man-in-the-Middle-Angriffe macht.

Failover-Verhalten testen

Bei MongoDB-Bereitstellungen werden Failover nicht als wichtige Ereignisse betrachtet, wie dies bei herkömmlichen Datenbankverwaltungssystemen der Fall war. Obwohl die meisten MongoDB-Treiber versuchen, dieses Ereignis zu abstrahieren, sollten Entwickler ihre Anwendungen für ein solches Verhalten verstehen und entwerfen, da Anwendungen mit vorübergehenden Netzwerkfehlern rechnen und es erneut versuchen sollten, bevor Fehler durchsickern.

Sie können die Belastbarkeit Ihrer Anwendungen testen, indem Sie Failover einleiten, während Ihre Arbeitslast ausgeführt wird. Der einfachste Weg, ein Failover herbeizuführen, ist die Ausführung des Befehls 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>

Eine der Möglichkeiten, wie ich das Verhalten von Treibern teste, ist das Schreiben einer einfachen „unbefristeten“ Writer-App. Dies wäre ein einfacher Code, der weiter in die Datenbank schreibt, sofern er nicht vom Benutzer unterbrochen wird, und alle Ausnahmen ausgibt, auf die er stößt, um uns zu helfen, das Treiber- und Datenbankverhalten zu verstehen. Ich verfolge auch die Daten, die es schreibt, um sicherzustellen, dass es im Test keinen nicht gemeldeten Datenverlust gibt. Hier ist der relevante Teil des Testcodes, den wir verwenden werden, um unser MongoDB-Failover-Verhalten zu testen:

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()

Die Art von Einträgen, die dies schreibt, sieht folgendermaßen aus:

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" }
..

Handhabung der ConnectionFailure-Ausnahme

Beachten Sie, dass wir die ConnectionFailure-Ausnahme abfangen, um alle netzwerkbezogenen Probleme zu behandeln, die aufgrund von Failovern auftreten können – wir geben die Ausnahme aus und versuchen weiterhin, in die Datenbank zu schreiben. Die Treiberdokumentation empfiehlt Folgendes:

Wenn ein Vorgang aufgrund eines Netzwerkfehlers fehlschlägt, wird ConnectionFailure ausgelöst und der Client stellt im Hintergrund eine erneute Verbindung her. Anwendungscode sollte diese Ausnahme behandeln (erkennt, dass der Vorgang fehlgeschlagen ist) und dann mit der Ausführung fortfahren.

Lassen Sie uns dies ausführen und während der Ausführung ein Datenbank-Failover durchführen. Folgendes passiert:

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

Beachten Sie, dass der Treiber etwa 12 Sekunden braucht, um die neue Topologie zu verstehen, sich mit der neuen primären Topologie zu verbinden und mit dem Schreiben fortzufahren. Die ausgelöste Ausnahme ist errors . AutoReconnect Dies ist eine Unterklasse von ConnectionFailure .

PyMongo-Tutorial:MongoDB-Failover in Ihrer Python-Anwendung testenClick To Tweet

Sie könnten noch ein paar Durchläufe machen, um zu sehen, welche anderen Ausnahmen zu sehen sind. Hier ist zum Beispiel ein weiterer Ausnahme-Trace, auf den ich gestoßen bin:

    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

Diese Ausnahme ist auch eine Unterklasse von ConnectionFailure.

'retryWrites'-Parameter

Ein weiterer Bereich zum Testen des MongoDB-Failover-Verhaltens wäre zu sehen, wie sich andere Parametervariationen auf die Ergebnisse auswirken. Ein relevanter Parameter ist „retryWrites ‘:

retryWrites:(boolean) Ob unterstützte Schreibvorgänge, die in diesem MongoClient ausgeführt werden, nach einem Netzwerkfehler auf MongoDB 3.6+ einmal wiederholt werden. Standardmäßig falsch.

Mal sehen, wie dieser Parameter bei einem Failover funktioniert. Die einzige am Code vorgenommene Änderung ist:

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

Lass es uns jetzt ausführen und dann ein Datenbanksystem-Failover durchführen:

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
...

Beachten Sie, dass das Einfügen nach dem Failover etwa 12 Sekunden dauert, aber als retryWrites erfolgreich durchläuft stellt sicher, dass der fehlgeschlagene Schreibvorgang wiederholt wird. Denken Sie daran, dass das Setzen dieses Parameters Sie nicht von der Handhabung des ConnectionFailure entbindet Ausnahme – Sie müssen sich um Lesevorgänge und andere Vorgänge kümmern, deren Verhalten nicht von diesem Parameter beeinflusst wird. Es löst das Problem auch nicht vollständig, selbst für unterstützte Vorgänge – manchmal können Failover länger dauern und retryWrites dauern allein wird nicht ausreichen.

Konfigurieren der Netzwerk-Timeout-Werte

rs.stepDown() führt zu einem ziemlich schnellen Failover, da der primäre Replikatsatz angewiesen wird, ein sekundärer zu werden, und die sekundären eine Wahl abhalten, um den neuen primären zu bestimmen. Bei Produktionsbereitstellungen verzögern Netzwerklast, Partition und andere derartige Probleme die Erkennung der Nichtverfügbarkeit des primären Servers und verlängern somit Ihre Failover-Zeit. Sie würden auch häufig auf PyMongo-Fehler wie errors.ServerSelectionTimeoutError stoßen , Fehler.NetworkTimeout, usw. bei Netzwerkproblemen und Failover.

Wenn dies sehr häufig vorkommt, müssen Sie versuchen, die Timeout-Parameter zu optimieren. Der zugehörige MongoClient Zeitüberschreitungsparameter sind serverSelectionTimeoutMS , connectTimeoutMS, und socketTimeoutMS . Wählen Sie von diesen einen größeren Wert für serverSelectionTimeoutMS hilft am häufigsten beim Umgang mit Fehlern während Failover:

serverSelectionTimeoutMS:(Ganzzahl) Steuert, wie lange (in Millisekunden) der Treiber wartet, um einen verfügbaren, geeigneten Server zu finden, um eine Datenbankoperation auszuführen; Während er wartet, können mehrere Serverüberwachungsoperationen ausgeführt werden, die jeweils von connectTimeoutMS gesteuert werden. Standardmäßig 30000 (30 Sekunden).

Sind Sie bereit, MongoDB in Ihrer Python-Anwendung zu verwenden? Sehen Sie sich unseren Artikel Erste Schritte mit Python und MongoDB an, um zu erfahren, wie Sie in nur fünf einfachen Schritten loslegen können. ScaleGrid ist der einzige MongoDB-DBaaS-Anbieter, der Ihnen vollständigen SSH-Zugriff auf Ihre Instanzen gewährt, sodass Sie Ihren Python-Server auf demselben Computer wie Ihren MongoDB-Server ausführen können. Automatisieren Sie Ihre MongoDB-Cloud-Bereitstellungen auf AWS, Azure oder DigitalOcean mit dedizierten Servern, Hochverfügbarkeit und Notfallwiederherstellung, damit Sie sich auf die Entwicklung Ihrer Python-Anwendung konzentrieren können.