Menu Docs
Página inicial do Docs
/
MongoDB Ops Manager
/

Criar um aplicativo resiliente com MongoDB

Nesta página

  • Instale os drivers mais recentes
  • Cadeias de conexão
  • Escrituras e leituras retráteis
  • Escreva e leia a preocupação
  • Error Handling
  • Aplicação de exemplo resiliente

Para escrever código de aplicativo que aproveite os recursos do MongoDB e lide normalmente com eleições de conjuntos de réplicas, você deve:

  • Instale os drivers mais recentes.

  • Use uma string de conexão que especifique todos os hosts.

  • Use leituras e gravações repetíveis.

  • Use uma write concern majority e uma write concern que faça sentido para seu aplicativo.

  • Lide com erros em seu aplicativo.

Primeiro, instale os drivers mais recentes para seu idioma em MongoDBDrivers. Os drivers conectam e retransmitem queries da sua aplicação para o seu banco de dados. O uso dos drivers mais recentes ativa os recursos mais recentes do MongoDB.

Em seguida, em seu aplicativo, importe a dependência:

Se você estiver usando o Maven, adicione o seguinte à sua pom.xml lista de dependências do :

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

Se você estiver usando o Gradle, adicione o seguinte à sua lista de dependências build.gradle:

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

Use uma connection string que especifica todos os hosts em seu sistema para conectar seu aplicativo ao seu banco de dados. Se o sistema executar uma eleição de conjunto de réplicas e um novo primário for eleito, uma connection string que especifica todos os hosts em seu sistema descobrirá o novo primário sem lógica de aplicativo.

Você pode especificar todos os hosts em seu sistema usando:

A cadeia de conexão também pode especificar opções, principalmente retryWrites e writeConcern.

Dica

Veja também:

Para obter ajuda para formatar sua connection string, consulte Conectar a uma implantação usando um driver MongoDB.

Use sua cadeia de conexão para instanciar um cliente MongoDB em seu aplicativo:

// 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
});

Observação

A partir da versão 3.6 do MongoDB e com drivers compatíveis com4.2, o MongoDB tenta novamente gravações e leituras uma vez por padrão.

Use gravações repetíveis para repetir determinadas operações de gravação uma única vez se elas falharem.

Repetir gravações exatamente uma vez é a melhor estratégia para lidar com erros transitórios de rede e eleições de conjunto de réplicas nas quais o aplicativo não consegue encontrar temporariamente um nó primário íntegro. Se a nova tentativa for bem-sucedida, a operação como um todo será bem-sucedida e nenhum erro será retornado. Se a operação falhar, provavelmente será devido a:

  • Um erro de rede duradouro, ou

  • Um comando inválido.

Dica

Veja também:

Para obter mais informações sobre como habilitar gravações repetíveis, consulte Habilitar gravações repetíveis.

Quando uma operação falha, seu aplicativo precisa lidar com o próprio erro.

As operações de leitura são automaticamente repetidas uma única vez se falharem a partir da versão 3.6 do MongoDB e com motores compatíveis com4.2. Você não precisa configurar seu aplicativo para tentar ler novamente.

Você pode ajustar a constância e a disponibilidade do seu aplicativo usando write concern e read concern. Preocupações mais rigorosas implicam que as operações de banco de dados aguardam por garantias mais fortes de consistência de dados, enquanto o afrouxamento dos requisitos de consistência fornece maior disponibilidade.

Exemplo

Se a sua aplicação lida com saldos monetários, a consistência é extremamente importante. Você pode usar majority referência de leitura e gravação para garantir que nunca leia dados obsoletos ou dados que possam ser revertidos.

Como alternativa, se o aplicativo registrar dados de temperatura de centenas de sensores a cada segundo, talvez você não esteja preocupado se ler os dados que não incluem as leituras mais recentes. Você pode perder os requisitos de consistência para fornecer acesso mais rápido a esses dados.

Você pode definir o nível de write concern do seu conjunto de réplicas por meio do URI da connection string. Utilize uma write concern do majority para garantir que seus dados sejam gravados com sucesso no seu banco de dados e persistam. Esse é o padrão recomendado e suficiente para a maioria dos casos de uso.

Quando você usa uma write concern que exige confirmação, como majority, também pode especificar um limite de tempo máximo para que as gravações atinjam esse nível de confirmação:

  • O parâmetro de cadeia de conexão wtimeoutMS para todas as gravações, ou

  • A opção tempo-limite para uma única operação de gravação.

Se você utiliza ou não um limite de tempo e o valor que você utiliza depende do contexto do aplicativo.

Dica

Veja também:

Para obter mais informações sobre como definir níveis de write concern, consulte Opções de write concern.

Importante

Se você não especificar um limite de tempo para gravações e o nível de write concern for inatingível, a operação de gravação nunca será concluída.

Você pode definir o nível de read concern do seu conjunto de réplicas por meio do URI da connection string. A read concern ideal depende dos requisitos do aplicativo, mas o padrão é suficiente para a maioria dos casos de uso. Não é necessário nenhum parâmetro de connection string para utilizar as preocupações de read concern.

Especificar uma referência de leitura pode melhorar as garantias em torno dos dados que a aplicação recebe do reconhecimento de data center.

Dica

Veja também:

Para obter mais informações sobre como definir níveis de read concern, consulte Opções de read concern.

Observação

A combinação específica de write concern e read concern que seu aplicativo usa afeta as garantias de ordem de operação. Isso é chamado de consistência causal. Para obter mais informações sobre garantias de consistência causal, consulte Consistência causal e read and write concern.

Comandos inválidos, interrupções de rede e erros de rede que não são manipulados por gravações repetíveis retornam erros. Consulte a documentação daAPI do seu driver para obter detalhes sobre o erro.

Por exemplo, se um aplicativo tentar inserir um documento com um _id duplicado, o driver retornará um erro que inclui:

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

Sem o tratamento adequado de erros, um erro pode bloquear seu aplicativo de processar solicitações até que ele seja reiniciado.

Seu aplicativo deve lidar com erros sem travamentos ou efeitos colaterais. No exemplo anterior de um aplicativo inserindo um _id duplicado, este aplicativo pode lidar com erros como segue:

// 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
});

A operação de inserção neste exemplo gera um erro "duplicate key" na segunda vez em que é invocada porque o campo _id deve ser exclusivo. O aplicativo detecta o erro, o cliente é notificado e o aplicativo continua em execução. No entanto, a operação de inserção falha e cabe a você decidir se deseja mostrar uma mensagem ao usuário, repetir a operação ou fazer outra coisa.

Você deve sempre registrar erros. Estratégias comuns para erros de processamento adicionais incluem:

  • Devolva o erro ao cliente com uma mensagem de erro. Essa é uma boa estratégia quando você não consegue resolver o erro e precisa informar a um usuário que uma ação não pode ser concluída.

  • Escreva em um banco de dados de backup. Essa é uma boa estratégia quando você não consegue resolver o erro, mas não quer correr o risco de perder os dados da solicitação.

  • Tente novamente a operação além da tentativa única padrão. Essa é uma boa estratégia quando você pode resolver a causa de um erro programaticamente e, em seguida, tentar novamente.

Você deve selecionar as melhores estratégias para o contexto do seu aplicativo.

Exemplo

No exemplo de um erro de chave duplicada, você deve registrar o erro, mas não repetir a operação porque ela nunca terá êxito. Em vez disso, você pode gravar em um banco de dados de fallback e revisar o conteúdo desse banco de dados posteriormente para garantir que nenhuma informação seja perdida. O usuário não precisa fazer mais nada e os dados são gravados, então você pode optar por não enviar uma mensagem de erro para o cliente.

Retornar um erro pode ser um comportamento desejável quando uma operação nunca seria concluída e bloquearia seu aplicativo de executar novas operações. Você pode usar o método maxTimeMS para colocar um limite de tempo em operações individuais, retornando um erro para que seu aplicativo manipule se esse limite de tempo for excedido.

O limite de tempo que você coloca em cada operação depende do contexto desta operação.

Exemplo

Se sua aplicação ler e exibir informações simples do produto de uma coleção inventory , você poderá ter certeza razoável de que essas operações de leitura levam apenas um momento. Uma query de execução incomumente longa é um bom indicador de que há um problema de rede duradouro. Definir maxTimeMS nessa operação para 5000, ou 5 segundos, significa que seu aplicativo recebe comentários assim que você tiver certeza de que há um problema de rede.

O aplicativo de exemplo a seguir reúne as recomendações para a criação de aplicativos resilientes.

O aplicativo é uma API simples de registros de usuário que expõe dois endpoints em http://localhost:3000:

Método
Endpoint
Descrição
GET
/users
Obtém uma lista de nomes de usuário de uma coleção do users.
POST
/users
Exige um name no corpo da solicitação. Adiciona um novo usuário a uma coleção users.

Observação

O seguinte aplicativo de servidor usa NanoHTTPD e json que você precisa adicionar ao seu projeto como dependências antes de executá-lo.

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}

Observação

O aplicativo de servidor a seguir usa o Express, que você precisa adicionar ao seu projeto como uma dependência antes de executá-lo.

1const express = require('express');
2const bodyParser = require('body-parser');
3
4// Use the latest drivers 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});

Voltar

Eventos de auditoria