sql >> Databasteknik >  >> NoSQL >> MongoDB

PyMongo Tutorial:Testa MongoDB-failover i din Python-app

Python är ett kraftfullt och flexibelt programmeringsspråk som används av miljontals utvecklare runt om i världen för att bygga sina applikationer. Det kommer inte som någon överraskning att Python-utvecklare vanligtvis använder MongoDB-värd, den mest populära NoSQL-databasen, för sina implementeringar på grund av dess flexibla karaktär och bristen på schemakrav.

Så, vad är det bästa sättet att använda MongoDB med Python? PyMongo är en Python-distribution som innehåller verktyg för att arbeta med MongoDB och den rekommenderade Python MongoDB-drivrutinen. Det är en ganska mogen drivrutin som stöder de flesta vanliga operationerna med databasen.

När du implementerar i produktion, rekommenderas det starkt att du konfigurerar i en MongoDB-replikuppsättningskonfiguration så att din data är geografiskt fördelad för hög tillgänglighet. Det rekommenderas också att SSL-anslutningar aktiveras för att kryptera klientdatabastrafiken. Vi utför ofta tester av failover egenskaper hos olika MongoDB drivrutiner för att kvalificera dem för produktionsanvändningsfall, eller när våra kunder ber oss om råd. I det här inlägget visar vi hur du ansluter till en SSL-aktiverad MongoDB-replikuppsättning konfigurerad med självsignerade certifikat med PyMongo, och hur du testar MongoDB-failoverbeteende i din kod.

Ansluta till MongoDB SSL med självsignerade certifikat

Det första steget är att se till att rätt versioner av PyMongo och dess beroenden är installerade. Den här guiden hjälper dig att reda ut beroenden, och drivrutinskompatibilitetsmatrisen hittar du här.

mongo_client.MongoClient parametrar som är av intresse för oss är ssl och ss_ca_cert . För att ansluta till en SSL-aktiverad MongoDB-slutpunkt som använder ett självsignerat certifikat, ssl måste vara inställd på True och ss_ca_cert måste peka på CA-certifikatfilen.

Om du är en ScaleGrid-kund kan du ladda ner CA-certifikatfilen för dina MongoDB-kluster från ScaleGrid-konsolen som visas här:

Så ett anslutningsutdrag skulle se ut så här:

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

Om du använder dina egna självsignerade certifikat där verifiering av värdnamn kan misslyckas, måste du också ställa in ssl_match_hostname parameter till False . Som drivrutinsdokumentationen säger, rekommenderas inte detta eftersom det gör anslutningen mottaglig för man-in-the-middle-attacker.

Testar failover-beteende

Med MongoDB-distributioner betraktas inte failovers som stora händelser som de var med traditionella databashanteringssystem. Även om de flesta MongoDB-drivrutiner försöker abstrahera denna händelse, bör utvecklare förstå och designa sina applikationer för sådant beteende, eftersom applikationer bör förvänta sig övergående nätverksfel och försöka igen innan de tränger igenom fel.

Du kan testa motståndskraften hos dina appar genom att inducera failovers medan din arbetsbelastning körs. Det enklaste sättet att inducera failover är att köra kommandot 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>

Ett av sätten jag gillar att testa förares beteende är att skriva en enkel "evig" skrivapp. Detta skulle vara enkel kod som fortsätter att skriva till databasen om den inte avbryts av användaren, och som skulle skriva ut alla undantag den stöter på för att hjälpa oss förstå drivrutinen och databasens beteende. Jag håller också reda på data som den skriver för att säkerställa att det inte finns någon orapporterad dataförlust i testet. Här är den relevanta delen av testkoden som vi kommer att använda för att testa vårt MongoDB failover-beteende:

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

Sorten av poster som det här skriver ser ut som:

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

Hantera undantaget anslutningsfel

Lägg märke till att vi fångar ConnectionFailure-undantaget för att hantera alla nätverksrelaterade problem som vi kan stöta på på grund av failovers – vi skriver ut undantaget och fortsätter att försöka skriva till databasen. Drivrutinens dokumentation rekommenderar att:

Om en operation misslyckas på grund av ett nätverksfel, höjs ConnectionFailure och klienten återansluter i bakgrunden. Applikationskoden bör hantera detta undantag (som erkänner att operationen misslyckades) och sedan fortsätta att köras.

Låt oss köra detta och göra en databas-failover medan den körs. Så här händer:

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

Lägg märke till att det tar cirka 12 sekunder för drivrutinen att förstå den nya topologin, ansluta till den nya primära och fortsätta skriva. Undantaget är fel . Återanslut automatiskt som är en underklass till ConnectionFailure .

PyMongo-handledning:Testa MongoDB-failover i din Python-appKlicka för att tweeta

Du kan göra några fler körningar för att se vilka andra undantag som finns. Till exempel, här är ett annat undantagsspår jag stötte på:

    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

Detta undantag är också en underklass till ConnectionFailure.

'retryWrites'-parameter

Ett annat område för att testa MongoDB failover-beteende skulle vara att se hur andra parametervariationer påverkar resultaten. En parameter som är relevant är 'retryWrites ':

retryWrites:(boolean) Huruvida stödda skrivoperationer utförda inom denna MongoClient kommer att försökas igen en gång efter ett nätverksfel på MongoDB 3.6+. Standardinställningen är Falsk.

Låt oss se hur den här parametern fungerar med en failover. Den enda ändring som gjorts i koden är:

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

Låt oss köra det nu och sedan göra en databassystem-failover:

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

Lägg märke till hur infogningen efter failover tar cirka 12 sekunder, men går igenom framgångsrikt när retryWrites parametern säkerställer att den misslyckade skrivningen görs om. Kom ihåg att inställning av denna parameter inte fritar dig från att hantera Anslutningsfel undantag – du behöver oroa dig för läsningar och andra operationer vars beteende inte påverkas av den här parametern. Det löser inte heller problemet helt, inte ens för operationer som stöds – ibland kan failovers ta längre tid att slutföra och TryWrites igen ensam kommer inte att räcka.

Konfigurera nätverkets timeout-värden

rs.stepDown() inducerar en ganska snabb failover, eftersom replikuppsättningen primära instrueras för att bli en sekundär, och sekundärerna håller ett val för att bestämma den nya primära. I produktionsinstallationer fördröjer nätverksbelastning, partitioner och andra sådana problem upptäckten av att den primära servern inte är tillgänglig, vilket förlänger din failover-tid. Du stöter också ofta på PyMongo-fel som errors.ServerSelectionTimeoutError , errors.NetworkTimeout, etc. under nätverksproblem och failovers.

Om detta inträffar väldigt ofta måste du se över att justera timeoutparametrarna. Den relaterade MongoClient timeoutparametrar är serverSelectionTimeoutMS , connectTimeoutMS, och socketTimeoutMS . Av dessa väljer du ett större värde för serverSelectionTimeoutMS hjälper oftast med att hantera fel under failovers:

serverSelectionTimeoutMS:(heltal) Styr hur länge (i millisekunder) drivrutinen väntar på att hitta en tillgänglig, lämplig server för att utföra en databasoperation; medan den väntar kan flera serverövervakningsoperationer utföras, var och en kontrollerad av connectTimeoutMS. Standard är 30 000 (30 sekunder).

Är du redo att använda MongoDB i din Python-applikation? Kolla in vår Komma igång med Python och MongoDB-artikeln för att se hur du kan komma igång med bara fem enkla steg. ScaleGrid är den enda MongoDB DBaaS-leverantören som ger dig full SSH-åtkomst till dina instanser så att du kan köra din Python-server på samma maskin som din MongoDB-server. Automatisera dina MongoDB-molninstallationer på AWS, Azure eller DigitalOcean med dedikerade servrar, hög tillgänglighet och katastrofåterställning så att du kan fokusera på att utveckla din Python-applikation.


  1. Redis - Befordra en slav till master manuellt

  2. Så här löser du MongoError:pool förstördes när du ansluter till CosmosDB

  3. NodeJS + ExpressJS + RedisStore-session är odefinierad

  4. Server Discovery and Monitoring-motorn är utfasad