Docs Menu
Docs Home
/ /

Cree una aplicación resiliente con MongoDB

Para escribir código de aplicación que aproveche las capacidades de MongoDB y gestione con elegancia las elecciones de conjuntos de réplicas, debe:

  • Instalar las últimas bibliotecas de cliente.

  • Utilice una cadena de conexión que especifique todos los hosts.

  • Utilice escrituras y lecturas reintentables.

  • Utilice un majority Escriba una preocupación y lea una preocupación que tenga sentido para su aplicación.

  • Gestiona los errores en tu aplicación.

Primero, instale las bibliotecas cliente más recientes para su lenguaje desde MongoDB Client Libraries. Estas bibliotecas conectan y retransmiten consultas desde su aplicación a su base de datos. Usar las bibliotecas cliente más recientes habilita las funciones más recientes de MongoDB.

Luego, en su aplicación, importe la dependencia:

Si estás usando Maven, agregue lo siguiente a su pom.xml lista de dependencias:

<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>

Si está utilizando Gradle, agregue lo siguiente a su build.gradle lista de dependencias:

dependencies {
compile 'org.mongodb:mongodb-driver-sync:4.0.1'
}
// Latest 'mongodb' version installed with npm
const MongoClient = require('mongodb').MongoClient;

Utilice una cadena de conexión que especifique todos los hosts de su implementación para conectar su aplicación a su base de datos. Si su implementación realiza una elección de conjunto de réplicas y se elige un nuevo host principal, una cadena de conexión que especifique todos los hosts de su implementación detectará el nuevo host principal sin lógica de aplicación.

Puede especificar todos los hosts en su implementación utilizando:

La cadena de conexión también puede especificar opciones, especialmente retryWrites y writeConcern.

Tip

Para obtener ayuda para formatear su cadena de conexión, consulte Conectarse a una implementación mediante una biblioteca de cliente MongoDB.

Utilice su cadena de conexión para crear una instancia de un cliente MongoDB en su aplicación:

// Create a variable for your connection string
String uri = "mongodb://[<username>:<password>@]hostname0<:port>[,hostname1:<port1>][,hostname2:<port2>][...][,hostnameN:<portN>]";
// Instantiate the MongoDB client with the URI
MongoClient client = MongoClients.create(uri);
// Create a variable for your connection string
const uri = "mongodb://[<username>:<password>@]hostname0<:port>[,hostname1:<port1>][,hostname2:<port2>][...][,hostnameN:<portN>]";
// Instantiate the MongoDB client with the URI
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true
});

Nota

A partir de la versión de 4.2 MongoDB y con 4.2bibliotecas de cliente compatibles con, MongoDB vuelve a intentar tanto las escrituras como las lecturas una vez de manera predeterminada.

Utilice escrituras reintentables para volver a intentar ciertas operaciones de escritura una sola vez si fallan.

Reintentar la escritura una sola vez es la mejor estrategia para gestionar errores de red transitorios y elecciones de conjuntos de réplicas en las que la aplicación no puede encontrar temporalmente un nodo principal en buen estado. Si el reintento tiene éxito, la operación en su conjunto se completa correctamente y no se devuelve ningún error. Si la operación falla, probablemente se deba a:

  • Un error de red duradero, o

  • Un comando no válido.

Tip

Para obtener más información sobre cómo habilitar escrituras reintentables, consulte Habilitación de escrituras reintentables.

Cuando una operación falla, su aplicación debe gestionar el error por sí misma.

Las operaciones de lectura se reintentan automáticamente una vez si fallan al iniciarse en la versión de MongoDB 4.2 y con 4.2bibliotecas cliente compatibles con. No es necesario configurar la aplicación para reintentar las lecturas.

Puede ajustar la consistencia y la disponibilidad de su aplicación mediante preocupaciones de escritura y lectura. Unas preocupaciones más estrictas implican que las operaciones de la base de datos esperan garantías de consistencia de datos más sólidas, mientras que una mayor flexibilidad en los requisitos de consistencia proporciona una mayor disponibilidad.

Ejemplo

Si su aplicación gestiona saldos monetarios, la consistencia es fundamental. Puede usar las consultas de escritura y lectura majority para asegurarse de no leer datos obsoletos o susceptibles de reversión.

Por otro lado, si su aplicación registra datos de temperatura de cientos de sensores cada segundo, no debería preocuparse si lee datos que no incluyan las lecturas más recientes. Puede flexibilizar los requisitos de consistencia para facilitar el acceso a esos datos.

Puede configurar el nivel de preocupación de escritura de su conjunto de réplicas mediante la URI de la cadena de conexión. Utilice una majority preocupación de escritura para garantizar que sus datos se escriban correctamente en su base de datos y se conserven. Este es el valor predeterminado recomendado y suficiente para la mayoría de los casos de uso.

Cuando utiliza una preocupación de escritura que requiere reconocimiento, como majority, también puede especificar un límite de tiempo máximo para que las escrituras alcancen ese nivel de reconocimiento:

  • El parámetro de cadena de conexión wtimeoutMS para todas las escrituras, o

  • La opción wtimeout para una sola operación de escritura.

El uso o no de un límite de tiempo y el valor que utilice dependerán del contexto de su aplicación.

Tip

Para obtener más información sobre cómo configurar los niveles de preocupación de escritura, consulte Opciones de preocupación de escritura.

Importante

Si no especifica un límite de tiempo para las escrituras y el nivel de preocupación por la escritura es inalcanzable, la operación de escritura nunca se completará.

Puede configurar el nivel de preocupación de lectura de su conjunto de réplicas mediante el URI de la cadena de conexión. La preocupación de lectura ideal depende de los requisitos de su aplicación, pero el valor predeterminado es suficiente para la mayoría de los casos de uso. No se requiere ningún parámetro de cadena de conexión para usar las preocupaciones de lectura predeterminadas.

Especificar una preocupación de lectura puede mejorar las garantías en torno a los datos que su aplicación recibe de su base de datos.

Tip

Para obtener más información sobre cómo configurar los niveles de preocupación de lectura, consulte Opciones de preocupación de lectura.

Nota

La combinación específica de problemas de escritura y lectura que utiliza su aplicación afecta las garantías de orden de operación. Esto se denomina consistencia causal. Para más información sobre las garantías de consistencia causal, consulte Consistencia causal y Problemas de lectura y escritura.

Los comandos no válidos, las interrupciones de red y los errores de red que no se gestionan mediante escrituras reintentables generan errores. Consulte la biblioteca de su cliente. Documentación deAPI para detalles de errores.

Por ejemplo, si una aplicación intenta insertar un documento con un duplicado _id, su biblioteca cliente devuelve un error que incluye:

Unable to insert due to an error: com.mongodb.MongoWriteException:
E11000 duplicate key error collection: <db>.<collection> ...
{
"name": : "MongoError",
"message": "E11000 duplicate key error collection on: <db>.<collection> ... ",
...
}

Sin una gestión de errores adecuada, un error podría impedir que su aplicación procese solicitudes hasta que se reinicie.

Su aplicación debería gestionar los errores sin bloquearse ni tener efectos secundarios. En el ejemplo anterior, una aplicación que insertaba un _id duplicado podría gestionar los errores de la siguiente manera:

// Declare a logger instance from java.util.logging.Logger
private static final Logger LOGGER = ...
...
try {
InsertOneResult result = collection.insertOne(new Document()
.append("_id", 1)
.append("body", "I'm a goofball trying to insert a duplicate _id"));
// Everything is OK
LOGGER.info("Inserted document id: " + result.getInsertedId());
// Refer to the API documentation for specific exceptions to catch
} catch (MongoException me) {
// Report the error
LOGGER.severe("Failed due to an error: " + me);
}
...
collection.insertOne({
_id: 1,
body: "I'm a goofball trying to insert a duplicate _id"
})
.then(result => {
response.sendStatus(200) // send "OK" message to the client
},
err => {
response.sendStatus(400); // send "Bad Request" message to the client
});

La operación de inserción de este ejemplo genera un error de "clave duplicada" la segunda vez que se invoca, ya que el campo _id debe ser único. La aplicación detecta el error, se notifica al cliente y la aplicación continúa ejecutándose. Sin embargo, la operación de inserción falla, y usted decide si mostrar un mensaje al usuario, reintentar la operación o realizar otra acción.

Siempre se deben registrar los errores. Las estrategias comunes para el procesamiento adicional de errores incluyen:

  • Devolver el error al cliente con un mensaje de error. Esta es una buena estrategia cuando no se puede resolver el error y se necesita informar al usuario que una acción no se puede completar.

  • Escribir en una base de datos de respaldo. Esta es una buena estrategia cuando no se puede resolver el error, pero no se quiere arriesgar a perder los datos de la solicitud.

  • Reintente la operación más allá del único reintento predeterminado. Esta es una buena estrategia cuando puede resolver la causa de un error mediante programación y luego reintentarla.

Debe seleccionar las mejores estrategias para el contexto de su aplicación.

Ejemplo

En el ejemplo de un error de clave duplicada, debe registrar el error, pero no reintentar la operación, ya que no se ejecutará correctamente. En su lugar, puede escribir en una base de datos de respaldo y revisar su contenido posteriormente para garantizar que no se pierda información. El usuario no necesita hacer nada más y los datos se registran, por lo que puede optar por no enviar un mensaje de error al cliente.

Devolver un error puede ser una buena idea cuando una operación nunca se completaría e impediría que la aplicación ejecute nuevas operaciones. Puede usar el método maxTimeMS para establecer un límite de tiempo para operaciones individuales y devolver un error que la aplicación deberá gestionar si se excede dicho límite.

El límite de tiempo que establezca para cada operación dependerá del contexto de esa operación.

Ejemplo

Si tu aplicación lee y muestra información de producto sencilla desde una colección inventory, puedes tener la confianza razonable de que esas operaciones de lectura solo toman un momento. Una query que se ejecuta inusualmente durante mucho tiempo es un buen indicador de que hay un problema de red persistente. Configurar maxTimeMS en esa operación a 5000, o 5 segundos, significa que tu aplicación recibe retroalimentación tan pronto como estés seguro de que hay un problema de red.

La siguiente aplicación de ejemplo reúne las recomendaciones para crear aplicaciones resilientes.

La aplicación es una API de registros de usuario simple que expone dos puntos finales en http://localhost::3000

Método
Endpoint
Descripción

GET

/users

Obtiene una lista de nombres de usuario de una colección users.

POST

/users

Requiere un name en el cuerpo de la solicitud. Agrega un nuevo usuario a una colección users.

Nota

La siguiente aplicación de servidor utiliza NanoHTTPD y json, que debe agregar a su proyecto como dependencias antes de poder ejecutarlo.

1// File: App.java
2
3import java.util.Map;
4import java.util.logging.Logger;
5
6import org.bson.Document;
7import org.json.JSONArray;
8
9import com.mongodb.MongoException;
10import com.mongodb.client.MongoClient;
11import com.mongodb.client.MongoClients;
12import com.mongodb.client.MongoCollection;
13import com.mongodb.client.MongoDatabase;
14
15import fi.iki.elonen.NanoHTTPD;
16
17public class App extends NanoHTTPD {
18 private static final Logger LOGGER = Logger.getLogger(App.class.getName());
19
20 static int port = 3000;
21 static MongoClient client = null;
22
23 public App() throws Exception {
24 super(port);
25
26 // Replace the uri string with your MongoDB deployment's connection string
27 String uri = "mongodb://<username>:<password>@hostname0:27017,hostname1:27017,hostname2:27017/?retryWrites=true&w=majority";
28 client = MongoClients.create(uri);
29
30 start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
31 LOGGER.info("\nStarted the server: http://localhost:" + port + "/ \n");
32 }
33
34 public static void main(String[] args) {
35 try {
36 new App();
37 } catch (Exception e) {
38 LOGGER.severe("Couldn't start server:\n" + e);
39 }
40 }
41
42 @Override
43 public Response serve(IHTTPSession session) {
44 StringBuilder msg = new StringBuilder();
45 Map<String, String> params = session.getParms();
46
47 Method reqMethod = session.getMethod();
48 String uri = session.getUri();
49
50 if (Method.GET == reqMethod) {
51 if (uri.equals("/")) {
52 msg.append("Welcome to my API!");
53 } else if (uri.equals("/users")) {
54 msg.append(listUsers(client));
55 } else {
56 msg.append("Unrecognized URI: ").append(uri);
57 }
58 } else if (Method.POST == reqMethod) {
59 try {
60 String name = params.get("name");
61 if (name == null) {
62 throw new Exception("Unable to process POST request: 'name' parameter required");
63 } else {
64 insertUser(client, name);
65 msg.append("User successfully added!");
66 }
67 } catch (Exception e) {
68 msg.append(e);
69 }
70 }
71
72 return newFixedLengthResponse(msg.toString());
73 }
74
75 static String listUsers(MongoClient client) {
76 MongoDatabase database = client.getDatabase("test");
77 MongoCollection<Document> collection = database.getCollection("users");
78
79 final JSONArray jsonResults = new JSONArray();
80 collection.find().forEach((result) -> jsonResults.put(result.toJson()));
81
82 return jsonResults.toString();
83 }
84
85 static String insertUser(MongoClient client, String name) throws MongoException {
86 MongoDatabase database = client.getDatabase("test");
87 MongoCollection<Document> collection = database.getCollection("users");
88
89 collection.insertOne(new Document().append("name", name));
90 return "Successfully inserted user: " + name;
91 }
92}

Nota

La siguiente aplicación de servidor utiliza Express, que debe agregar a su proyecto como dependencia antes de poder ejecutarlo.

1const express = require('express');
2const bodyParser = require('body-parser');
3
4// Use the latest client libraries by installing & importing them
5const MongoClient = require('mongodb').MongoClient;
6
7const app = express();
8app.use(bodyParser.json());
9app.use(bodyParser.urlencoded({ extended: true }));
10
11// Use a connection string that lists all hosts
12// with retryable writes & majority write concern
13const uri = "mongodb://<username>:<password>@hostname0:27017,hostname1:27017,hostname2:27017/?retryWrites=true&w=majority";
14
15const client = new MongoClient(uri, {
16 useNewUrlParser: true,
17 useUnifiedTopology: true
18});
19
20// ----- API routes ----- //
21app.get('/', (req, res) => res.send('Welcome to my API!'));
22
23app.get('/users', (req, res) => {
24 const collection = client.db("test").collection("users");
25
26 collection
27 .find({})
28 // In this example, 'maxTimeMS' throws an error after 5 seconds,
29 // alerting the application to a lasting network outage
30 .maxTimeMS(5000)
31 .toArray((err, data) => {
32 if (err) {
33 // Handle errors in your application
34 // In this example, by sending the client a message
35 res.send("The request has timed out. Please check your connection and try again.");
36 }
37 return res.json(data);
38 });
39});
40
41app.post('/users', (req, res) => {
42 const collection = client.db("test").collection("users");
43 collection.insertOne({ name: req.body.name })
44 .then(result => {
45 res.send("User successfully added!");
46 }, err => {
47 // Handle errors in your application
48 // In this example, by sending the client a message
49 res.send("An application error has occurred. Please try again.");
50 })
51});
52// ----- End of API routes ----- //
53
54app.listen(3000, () => {
55 console.log(`Listening on port 3000.`);
56 client.connect(err => {
57 if (err) {
58 console.log("Not connected: ", err);
59 process.exit(0);
60 }
61 console.log('Connected.');
62 });
63});

Volver

Métricas de supervisión

En esta página