Python je výkonný a flexibilní programovací jazyk, který používají miliony vývojářů po celém světě k vytváření aplikací. Není žádným překvapením, že vývojáři Pythonu běžně využívají hosting MongoDB, nejoblíbenější databázi NoSQL, pro svá nasazení kvůli její flexibilní povaze a nedostatku požadavků na schéma.
Jaký je tedy nejlepší způsob použití MongoDB s Pythonem? PyMongo je distribuce Pythonu obsahující nástroje pro práci s MongoDB a doporučený ovladač Python MongoDB. Je to poměrně vyspělý ovladač, který podporuje většinu běžných operací s databází.
Při nasazení v produkčním prostředí se důrazně doporučuje nastavit v konfiguraci sady replik MongoDB, aby byla vaše data geograficky distribuována pro vysokou dostupnost. Je také doporučeno povolit připojení SSL pro šifrování provozu klient-databáze. Často provádíme testování charakteristik převzetí služeb při selhání různých ovladačů MongoDB, abychom je kvalifikovali pro případy produkčního použití nebo když nás naši zákazníci žádají o radu. V tomto příspěvku vám ukážeme, jak se pomocí PyMongo připojit k sadě replik MongoDB s povoleným SSL konfigurovaným s certifikáty s vlastním podpisem, a jak otestovat chování při převzetí služeb při selhání MongoDB ve vašem kódu.
Připojení k MongoDB SSL pomocí certifikátů s vlastním podpisem
Prvním krokem je zajistit, aby byly nainstalovány správné verze PyMonga a jeho závislostí. Tato příručka vám pomůže vyřešit závislosti a matici kompatibility ovladačů najdete zde.
mongo_client.MongoClient parametry, které nás zajímají, jsou ssl a ss_ca_cert . Chcete-li se připojit ke koncovému bodu MongoDB s povoleným SSL, který používá certifikát s vlastním podpisem, ssl musí být nastaveno na True a ss_ca_cert musí ukazovat na soubor certifikátu CA.
Pokud jste zákazníkem ScaleGrid, můžete si stáhnout soubor certifikátu CA pro své clustery MongoDB z konzoly ScaleGrid, jak je znázorněno zde:
Fragment připojení by tedy vypadal takto:
>>> 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() >>>
Pokud používáte vlastní certifikáty podepsané sebou samým, kde by ověření názvu hostitele mohlo selhat, budete muset také nastavit ssl_match_hostname parametr na False . Jak uvádí dokumentace k ovladači, toto se nedoporučuje, protože připojení je náchylné k útokům typu man-in-the-middle.
Testování chování při selhání
U nasazení MongoDB nejsou výpadky považovány za hlavní události, jako tomu bylo u tradičních systémů pro správu databází. Ačkoli se většina ovladačů MongoDB snaží tuto událost abstrahovat, vývojáři by měli rozumět a navrhovat své aplikace pro takové chování, protože aplikace by měly očekávat přechodné síťové chyby a opakovat to, než budou chyby perkolovat.
Odolnost svých aplikací můžete otestovat vyvoláním převzetí služeb při selhání, zatímco vaše pracovní zatížení běží. Nejjednodušší způsob, jak vyvolat převzetí služeb při selhání, je spustit příkaz 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>
Jedním ze způsobů, jak rád testuji chování řidičů, je psaní jednoduché aplikace pro „věčné“ psaní. Jednalo by se o jednoduchý kód, který pokračuje v zápisu do databáze, pokud jej uživatel nepřeruší, a vypíše všechny výjimky, na které narazí, aby nám pomohl porozumět chování ovladače a databáze. Také sleduji data, která zapisuje, abych zajistil, že během testu nedojde k žádné nenahlášené ztrátě dat. Zde je relevantní část testovacího kódu, kterou použijeme k testování našeho chování MongoDB při selhání:
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()
Typ záznamů, které toto zapisuje, vypadá takto:
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" } ..
Zpracování výjimky ConnectionFailure
Všimněte si, že jsme zachytili výjimku ConnectionFailure, abychom se vypořádali se všemi problémy souvisejícími se sítí, se kterými se můžeme setkat v důsledku převzetí služeb při selhání – výjimku vytiskneme a nadále se pokoušíme zapisovat do databáze. Dokumentace ovladače doporučuje:
Pokud se operace nezdaří kvůli chybě sítě, vyvolá se ConnectionFailure a klient se znovu připojí na pozadí. Aplikační kód by měl zpracovat tuto výjimku (rozpoznat, že operace selhala) a poté pokračovat v provádění.
Spusťte to a proveďte obnovení databáze při selhání. Zde je to, co se stane:
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
Všimněte si, že ovladači trvá asi 12 sekund, než pochopí novou topologii, připojí se k nové primární a bude pokračovat v psaní. Výjimkou jsou chyby . Automatické opětovné připojení což je podtřída ConnectionFailure .
Výukový program PyMongo:Testování selhání MongoDB ve vaší aplikaci Python Kliknutím na Tweet
Mohli byste provést ještě několik běhů, abyste viděli, jaké další výjimky jsou vidět. Zde je například další trasování výjimky, na které jsem narazil:
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
Tato výjimka je také podtřídou ConnectionFailure.
Parametr ‘retryWrites’
Další oblastí pro testování chování MongoDB při selhání při selhání by bylo zjištění, jak jiné variace parametrů ovlivňují výsledky. Jedním z relevantních parametrů je „retryWrites ‘:
retryWrites:(boolean) Zda budou podporované operace zápisu prováděné v rámci tohoto MongoClient jednou zopakovány po chybě sítě na MongoDB 3.6+. Výchozí hodnota je False.
Podívejme se, jak tento parametr funguje při převzetí služeb při selhání. Jediná změna provedená v kódu je:
client = pymongo.MongoClient(MONGO_URI, ssl = True, ssl_ca_certs = 'path-to-cacert.pem', retryWrites = True)
Spusťte to nyní a poté proveďte převzetí služeb při selhání databázového systému:
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 ...
Všimněte si, jak vložení po převzetí služeb při selhání trvá asi 12 sekund, ale proběhne úspěšně jako retryWrites parametr zajišťuje opakování neúspěšného zápisu. Pamatujte, že nastavení tohoto parametru vás nezbavuje nutnosti zpracovávat ConnectionFailure výjimka – musíte se starat o čtení a další operace, jejichž chování není tímto parametrem ovlivněno. Problém také zcela nevyřeší, a to ani u podporovaných operací – někdy může dokončení převzetí služeb při selhání trvat déle a retryWrites sám nebude stačit.
Konfigurace hodnot časového limitu sítě
rs.stepDown() vyvolává poměrně rychlé převzetí služeb při selhání, protože primární sada replik je instruována, aby se stala sekundární, a sekundární stanice uspořádají volby, aby určily nové primární položky. Při produkčních nasazeních zatížení sítě, oddíly a další podobné problémy zpožďují detekci nedostupnosti primárního serveru, čímž se prodlužuje doba převzetí služeb při selhání. Často byste také narazili na chyby PyMongo jako errors.ServerSelectionTimeoutError , errors.NetworkTimeout, atd. během síťových problémů a převzetí služeb při selhání.
Pokud k tomu dochází velmi často, musíte vyladit parametry časového limitu. Související MongoClient parametry časového limitu jsou serverSelectionTimeoutMS , connectTimeoutMS, a socketTimeoutMS . Z nich vyberte větší hodnotu pro serverSelectionTimeoutMS nejčastěji pomáhá při řešení chyb během převzetí služeb při selhání:
serverSelectionTimeoutMS:(celé číslo) Řídí, jak dlouho (v milisekundách) bude ovladač čekat na nalezení dostupného vhodného serveru k provedení databázové operace; zatímco čeká, může být prováděno několik operací monitorování serveru, z nichž každá je řízena connectTimeoutMS. Výchozí hodnota je 30 000 (30 sekund).
Jste připraveni používat MongoDB ve své aplikaci Python? Podívejte se na náš článek Začínáme s Pythonem a MongoDB a zjistěte, jak můžete začít pracovat v pouhých 5 snadných krocích. ScaleGrid je jediný poskytovatel MongoDB DBaaS, který vám poskytuje plný přístup SSH k vašim instancím, takže můžete svůj Python server provozovat na stejném počítači jako váš server MongoDB. Automatizujte své cloudové nasazení MongoDB na AWS, Azure nebo DigitalOcean pomocí dedikovaných serverů, vysoké dostupnosti a obnovy po havárii, abyste se mohli soustředit na vývoj své aplikace Python.