MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Samouczek PyMongo:Testowanie przełączania awaryjnego MongoDB w Twojej aplikacji w Pythonie

Python to potężny i elastyczny język programowania używany przez miliony programistów na całym świecie do tworzenia aplikacji. Nic dziwnego, że programiści Pythona często wykorzystują hosting MongoDB, najpopularniejszą bazę danych NoSQL, do swoich wdrożeń ze względu na jej elastyczny charakter i brak wymagań dotyczących schematu.

Jak więc najlepiej używać MongoDB z Pythonem? PyMongo to dystrybucja Pythona zawierająca narzędzia do pracy z MongoDB oraz zalecany sterownik Python MongoDB. Jest to dość dojrzały sterownik, który obsługuje większość typowych operacji na bazie danych.

Podczas wdrażania w środowisku produkcyjnym zdecydowanie zaleca się skonfigurowanie zestawu replik MongoDB, aby dane były rozproszone geograficznie w celu zapewnienia wysokiej dostępności. Zaleca się również włączenie połączeń SSL w celu szyfrowania ruchu klient-baza danych. Często podejmujemy się testowania charakterystyk przełączania awaryjnego różnych sterowników MongoDB, aby zakwalifikować je do przypadków użycia produkcyjnego lub gdy nasi klienci proszą nas o poradę. W tym poście pokazujemy, jak za pomocą PyMongo połączyć się z zestawem replik MongoDB z włączoną obsługą SSL, skonfigurowanym za pomocą certyfikatów z podpisem własnym, oraz jak przetestować zachowanie awaryjne MongoDB w kodzie.

Łączenie z MongoDB SSL przy użyciu certyfikatów z podpisem własnym

Pierwszym krokiem jest upewnienie się, że zainstalowano odpowiednie wersje PyMongo i jego zależności. Ten przewodnik pomaga w uporządkowaniu zależności, a macierz zgodności sterowników można znaleźć tutaj.

mongo_client.MongoClient interesujące nas parametry to ssl i ss_ca_cert . Aby połączyć się z punktem końcowym MongoDB z włączoną obsługą SSL, który używa certyfikatu z podpisem własnym, ssl musi być ustawiony na Prawda i ss_ca_cert musi wskazywać na plik certyfikatu CA.

Jeśli jesteś klientem ScaleGrid, możesz pobrać plik certyfikatu CA dla swoich klastrów MongoDB z konsoli ScaleGrid, jak pokazano tutaj:

Tak więc fragment połączenia wyglądałby tak:

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

Jeśli używasz własnych certyfikatów z podpisem własnym, w przypadku których weryfikacja nazwy hosta może się nie powieść, musisz również ustawić ssl_match_hostname parametr na Fałsz . Jak mówi dokumentacja sterownika, nie jest to zalecane, ponieważ sprawia, że ​​połączenie jest podatne na ataki typu man-in-the-middle.

Testowanie zachowania awaryjnego

W przypadku wdrożeń MongoDB przełączanie awaryjne nie jest uważane za poważne zdarzenia, jak miało to miejsce w przypadku tradycyjnych systemów zarządzania bazami danych. Chociaż większość sterowników MongoDB próbuje abstrahować to zdarzenie, programiści powinni rozumieć i projektować swoje aplikacje pod kątem takiego zachowania, ponieważ aplikacje powinny spodziewać się przejściowych błędów sieciowych i ponawiać próbę, zanim pojawią się błędy.

Możesz przetestować odporność swoich aplikacji, wywołując przełączanie awaryjne podczas działania obciążenia. Najłatwiejszym sposobem wywołania przełączenia awaryjnego jest uruchomienie polecenia 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>

Jednym ze sposobów, w jaki lubię testować zachowanie sterowników, jest napisanie prostej „wiecznej” aplikacji do pisania. Byłby to prosty kod, który zapisuje do bazy danych, o ile nie zostanie przerwany przez użytkownika, i wydrukuje wszystkie napotkane wyjątki, aby pomóc nam zrozumieć zachowanie sterownika i bazy danych. Śledzę również dane, które zapisuje, aby upewnić się, że w teście nie ma niezgłoszonych utraty danych. Oto odpowiednia część kodu testowego, której użyjemy do przetestowania naszego zachowania awaryjnego 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()

Rodzaj wpisów, które to pisze:

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

Obsługa wyjątku błędu połączenia

Zwróć uwagę, że przechwytujemy wyjątek ConnectionFailure, aby poradzić sobie ze wszystkimi problemami związanymi z siecią, które możemy napotkać w wyniku przełączeń awaryjnych — drukujemy wyjątek i kontynuujemy próbę zapisu do bazy danych. Dokumentacja sterownika zaleca, aby:

Jeśli operacja nie powiedzie się z powodu błędu sieciowego, zgłaszany jest błąd ConnectionFailure i klient ponownie łączy się w tle. Kod aplikacji powinien obsłużyć ten wyjątek (rozpoznając, że operacja nie powiodła się), a następnie kontynuować wykonywanie.

Uruchommy to i wykonajmy przełączanie awaryjne bazy danych podczas jego wykonywania. Oto, co się dzieje:

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

Zauważ, że sterownik potrzebuje około 12 sekund, aby zrozumieć nową topologię, połączyć się z nową podstawową i kontynuować pisanie. Zgłoszony wyjątek to błędy . Automatyczne ponowne łączenie która jest podklasą ConnectionFailure .

Samouczek PyMongo:Testowanie przełączania awaryjnego MongoDB w aplikacji Pythona Kliknij, aby tweetować

Możesz wykonać jeszcze kilka przebiegów, aby zobaczyć, jakie inne wyjątki są widoczne. Na przykład, oto kolejny ślad wyjątku, który napotkałem:

    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

Ten wyjątek jest również podklasą ConnectionFailure.

Parametr „retryWrites”

Innym obszarem do testowania zachowania awaryjnego MongoDB jest sprawdzenie, jak inne zmiany parametrów wpływają na wyniki. Jednym z istotnych parametrów jest „retryWrites ‘:

retryWrites:(boolean) Określa, czy obsługiwane operacje zapisu wykonywane w tym MongoClient zostaną ponowione raz po błędzie sieciowym w MongoDB 3.6+. Domyślnie Fałsz.

Zobaczmy, jak ten parametr działa w przypadku przełączania awaryjnego. Jedyna zmiana dokonana w kodzie to:

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

Uruchommy to teraz, a następnie przeprowadźmy awaryjne przełączanie systemu bazy danych:

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

Zwróć uwagę, że wstawianie po przełączeniu awaryjnym zajmuje około 12 sekund, ale przechodzi pomyślnie jako retryWrites parametr zapewnia, że ​​nieudana próba zapisu jest ponawiana. Pamiętaj, że ustawienie tego parametru nie zwalnia Cię z obsługi ConnectionFailure wyjątek — musisz się martwić o odczyty i inne operacje, na których zachowanie nie ma wpływu ten parametr. Nie rozwiązuje to również całkowicie problemu, nawet w przypadku obsługiwanych operacji – czasami przełączanie awaryjne może trwać dłużej i ponowne zapisywanie sam nie wystarczy.

Konfigurowanie wartości limitu czasu sieci

rs.stepDown() indukuje raczej szybkie przełączenie awaryjne, ponieważ podstawowy zestaw replik ma stać się drugorzędnym, a drugorzędne przeprowadzają wybory w celu określenia nowego podstawowego. We wdrożeniach produkcyjnych obciążenie sieci, partycje i inne tego typu problemy opóźniają wykrycie niedostępności serwera podstawowego, a tym samym wydłużają czas przełączania awaryjnego. Często napotykasz również błędy PyMongo, takie jak errors.ServerSelectionTimeoutError , błędy.NetworkTimeout, itp. podczas problemów z siecią i przełączania awaryjnego.

Jeśli zdarza się to bardzo często, musisz poprawić parametry limitu czasu. Powiązany MongoClient Parametry limitu czasu to serverSelectionTimeoutMS , connectTimeoutMS, i socketTimeoutMS . Spośród nich wybranie większej wartości dla serverSelectionTimeoutMS najczęściej pomaga w radzeniu sobie z błędami podczas przełączania awaryjnego:

serverSelectionTimeoutMS:(integer) Kontroluje jak długo (w milisekundach) sterownik będzie czekał na znalezienie dostępnego, odpowiedniego serwera do przeprowadzenia operacji na bazie danych; podczas oczekiwania można wykonać wiele operacji monitorowania serwera, z których każda jest kontrolowana przez connectTimeoutMS. Domyślnie 30000 (30 sekund).

Gotowy do użycia MongoDB w swojej aplikacji w Pythonie? Zapoznaj się z naszym artykułem Pierwsze kroki z Pythonem i MongoDB, aby dowiedzieć się, jak rozpocząć pracę w zaledwie 5 prostych krokach. ScaleGrid jest jedynym dostawcą MongoDB DBaaS, który zapewnia pełny dostęp SSH do Twoich instancji, dzięki czemu możesz uruchomić serwer Pythona na tym samym komputerze, co serwer MongoDB. Zautomatyzuj swoje wdrożenia w chmurze MongoDB na AWS, Azure lub DigitalOcean z dedykowanymi serwerami, wysoką dostępnością i odzyskiwaniem po awarii, dzięki czemu możesz skupić się na tworzeniu aplikacji Python.


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Sprawdź, czy ktoś ma urodziny w ciągu najbliższych 30 dni z mongo

  2. Jak korzystać z MongoDB Stitch w aplikacjach na Androida

  3. Jak przechowywać pole Date jako ISODate() za pomocą jackson w MongoDb

  4. jak wysłać dowolny obiekt json do webapi

  5. Zainstaluj MongoDB w systemie Windows