sql >> Base de Datos >  >> NoSQL >> MongoDB

Tutorial de PyMongo:Probar la conmutación por error de MongoDB en su aplicación de Python

Python es un lenguaje de programación potente y flexible utilizado por millones de desarrolladores de todo el mundo para construir sus aplicaciones. No sorprende que los desarrolladores de Python aprovechen comúnmente el alojamiento de MongoDB, la base de datos NoSQL más popular, para sus implementaciones debido a su naturaleza flexible y la falta de requisitos de esquema.

Entonces, ¿cuál es la mejor manera de usar MongoDB con Python? PyMongo es una distribución de Python que contiene herramientas para trabajar con MongoDB y el controlador de Python MongoDB recomendado. Es un controlador bastante maduro que admite la mayoría de las operaciones comunes con la base de datos.

Al implementar en producción, se recomienda encarecidamente configurar una configuración de conjunto de réplicas de MongoDB para que sus datos estén distribuidos geográficamente para una alta disponibilidad. También se recomienda habilitar las conexiones SSL para cifrar el tráfico cliente-base de datos. A menudo realizamos pruebas de las características de conmutación por error de varios controladores MongoDB para calificarlos para casos de uso de producción, o cuando nuestros clientes nos piden consejo. En esta publicación, le mostramos cómo conectarse a un conjunto de réplicas de MongoDB habilitado para SSL configurado con certificados autofirmados mediante PyMongo, y cómo probar el comportamiento de conmutación por error de MongoDB en su código.

Conectarse a MongoDB SSL mediante certificados autofirmados

El primer paso es asegurarse de que estén instaladas las versiones correctas de PyMongo y sus dependencias. Esta guía lo ayuda a clasificar las dependencias y la matriz de compatibilidad de controladores se puede encontrar aquí.

El mongo_client.MongoClient Los parámetros que nos interesan son ssl y ss_ca_cert . Para conectarse a un punto final de MongoDB habilitado para SSL que utiliza un certificado autofirmado, ssl debe establecerse en Verdadero y ss_ca_cert debe apuntar al archivo de certificado de CA.

Si es cliente de ScaleGrid, puede descargar el archivo de certificado de CA para sus clústeres de MongoDB desde la consola de ScaleGrid como se muestra aquí:

Entonces, un fragmento de conexión se vería así:

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

Si está utilizando sus propios certificados autofirmados donde la verificación del nombre de host puede fallar, también deberá configurar ssl_match_hostname parámetro a Falso . Como dice la documentación del controlador, esto no se recomienda ya que hace que la conexión sea susceptible a ataques de intermediarios.

Comportamiento de prueba de conmutación por error

Con las implementaciones de MongoDB, las conmutaciones por error no se consideran eventos importantes como ocurría con los sistemas tradicionales de administración de bases de datos. Aunque la mayoría de los controladores de MongoDB intentan abstraer este evento, los desarrolladores deben comprender y diseñar sus aplicaciones para tal comportamiento, ya que las aplicaciones deben esperar errores de red transitorios y volver a intentarlo antes de filtrar los errores.

Puede probar la resiliencia de sus aplicaciones induciendo conmutaciones por error mientras se ejecuta su carga de trabajo. La forma más fácil de inducir la conmutación por error es ejecutar el 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>

Una de las formas en que me gusta probar el comportamiento de los controladores es escribiendo una aplicación de escritura 'perpetua' simple. Este sería un código simple que sigue escribiendo en la base de datos a menos que el usuario lo interrumpa, e imprimiría todas las excepciones que encuentre para ayudarnos a comprender el comportamiento del controlador y la base de datos. También realizo un seguimiento de los datos que escribe para asegurarme de que no haya pérdida de datos no notificados en la prueba. Aquí está la parte relevante del código de prueba que usaremos para probar nuestro comportamiento de conmutación por error de 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()

El tipo de entradas que esto escribe se parece 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" }
..

Manejo de la excepción ConnectionFailure

Observe que capturamos la excepción ConnectionFailure para tratar todos los problemas relacionados con la red que podemos encontrar debido a las conmutaciones por error:imprimimos la excepción y continuamos intentando escribir en la base de datos. La documentación del controlador recomienda que:

Si una operación falla debido a un error de red, se genera ConnectionFailure y el cliente se vuelve a conectar en segundo plano. El código de la aplicación debe manejar esta excepción (reconociendo que la operación falló) y luego continuar con la ejecución.

Ejecutemos esto y hagamos una conmutación por error de la base de datos mientras se ejecuta. Esto es lo que sucede:

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

Observe que el controlador tarda aproximadamente 12 segundos en comprender la nueva topología, conectarse a la nueva principal y continuar escribiendo. La excepción planteada es errores . Reconexión automática que es una subclase de ConnectionFailure .

Tutorial de PyMongo:prueba de conmutación por error de MongoDB en su aplicación de PythonHaga clic para twittear

Podría hacer algunas ejecuciones más para ver qué otras excepciones se ven. Por ejemplo, aquí hay otro rastro de excepción que encontré:

    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

Esta excepción también es una subclase de ConnectionFailure.

Parámetro 'reintentar escritura'

Otra área para probar el comportamiento de conmutación por error de MongoDB sería ver cómo otras variaciones de parámetros afectan los resultados. Un parámetro que es relevante es 'retryWrites ':

retryWrites:(booleano) Si las operaciones de escritura admitidas ejecutadas dentro de este MongoClient se volverán a intentar una vez después de un error de red en MongoDB 3.6+. El valor predeterminado es falso.

Veamos cómo funciona este parámetro con una conmutación por error. El único cambio realizado en el código es:

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

Ejecutémoslo ahora y luego hagamos una conmutación por error del sistema de la base de datos:

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

Observe cómo la inserción después de la conmutación por error tarda unos 12 segundos, pero se realiza correctamente como retryWrites El parámetro garantiza que se vuelva a intentar la escritura fallida. Recuerde que configurar este parámetro no lo exime de manejar el ConnectionFailure excepción:debe preocuparse por las lecturas y otras operaciones cuyo comportamiento no se ve afectado por este parámetro. Tampoco resuelve completamente el problema, incluso para las operaciones admitidas; a veces, las conmutaciones por error pueden tardar más en completarse y retryWrites solo no será suficiente.

Configuración de los valores de tiempo de espera de la red

rs.stepDown() induce una conmutación por error bastante rápida, ya que el conjunto de réplicas principal se instruye para convertirse en secundario, y los secundarios realizan una elección para determinar el nuevo principal. En las implementaciones de producción, la carga de la red, la partición y otros problemas similares retrasan la detección de la falta de disponibilidad del servidor principal, lo que prolonga el tiempo de conmutación por error. También te encontrarías a menudo con errores de PyMongo como errors.ServerSelectionTimeoutError , errores.NetworkTimeout, etc. durante problemas de red y conmutación por error.

Si esto ocurre con mucha frecuencia, debe modificar los parámetros de tiempo de espera. El MongoClient relacionado los parámetros de tiempo de espera son serverSelectionTimeoutMS , conexiónTiempo de espera MS, y socketTimeoutMS . De estos, seleccionar un valor mayor para serverSelectionTimeoutMS la mayoría de las veces ayuda a lidiar con errores durante las conmutaciones por error:

serverSelectionTimeoutMS:(entero) Controla cuánto tiempo (en milisegundos) esperará el controlador para encontrar un servidor apropiado disponible para llevar a cabo una operación de base de datos; mientras espera, se pueden llevar a cabo múltiples operaciones de monitoreo del servidor, cada una controlada por connectTimeoutMS. El valor predeterminado es 30000 (30 segundos).

¿Listo para usar MongoDB en su aplicación de Python? Consulte nuestro artículo Primeros pasos con Python y MongoDB para ver cómo puede comenzar a trabajar en solo 5 sencillos pasos. ScaleGrid es el único proveedor de MongoDB DBaaS que le brinda acceso SSH completo a sus instancias para que pueda ejecutar su servidor Python en la misma máquina que su servidor MongoDB. Automatice sus implementaciones en la nube de MongoDB en AWS, Azure o DigitalOcean con servidores dedicados, alta disponibilidad y recuperación ante desastres para que pueda concentrarse en desarrollar su aplicación de Python.