sql >> Database >  >> NoSQL >> MongoDB

MongoDB Connection Pooling gebruiken op AWS Lambda

In dit bericht laten we u zien hoe u MongoDB-verbindingspooling op AWS Lambda kunt gebruiken met zowel Node.js- als Java-stuurprogramma's.

Wat is AWS Lambda?

AWS Lambda is een gebeurtenisgestuurde, serverloze computerservice die wordt aangeboden door Amazon Web Services . Hiermee kan een gebruiker code uitvoeren zonder de administratieve taken, in tegenstelling tot EC2-instanties waarbij een gebruiker verantwoordelijk is voor het inrichten van servers, schalen, hoge beschikbaarheid, enz. In plaats daarvan hoeft u alleen de code te uploaden en de gebeurtenistrigger in te stellen, en AWS Lambda zorgt automatisch voor al het andere.

AWS Lambda ondersteunt verschillende runtimes, waaronder Node.js , Python , Java , en Ga . Het kan direct worden geactiveerd door AWS-services zoals S3 , DynamoDB , Kinesis , SNS , enz. In ons voorbeeld gebruiken we de AWS API-gateway om de Lambda-functies te activeren.

Wat is een verbindingspool?

Het openen en sluiten van een databaseverbinding is een dure operatie omdat het zowel CPU-tijd als geheugen vereist. Als een applicatie voor elke bewerking een databaseverbinding moet openen, heeft dat een grote impact op de prestaties.

Wat als we een heleboel databaseverbindingen hebben die in een cache in leven worden gehouden? Wanneer een toepassing een databasebewerking moet uitvoeren, kan deze een verbinding uit de cache lenen, de vereiste bewerking uitvoeren en deze teruggeven. Door deze aanpak te gebruiken, kunnen we de tijd besparen die nodig is om elke keer een nieuwe verbinding tot stand te brengen en de verbindingen opnieuw te gebruiken. Deze cache staat bekend als de verbindingspool .

De grootte van de verbindingspool is configureerbaar in de meeste MongoDB-stuurprogramma's en de standaardgrootte van de pool varieert van stuurprogramma tot stuurprogramma. Het is bijvoorbeeld 5 in het Node.js-stuurprogramma, terwijl het 100 is in het Java-stuurprogramma. De grootte van de verbindingspool bepaalt het maximale aantal parallelle verzoeken dat uw stuurprogramma op een bepaald moment kan verwerken. Als de limiet van de verbindingspool is bereikt, worden nieuwe aanvragen gedaan om te wachten tot de bestaande zijn voltooid. Daarom moet de poolgrootte zorgvuldig worden gekozen, rekening houdend met de toepassingsbelasting en gelijktijdigheid die moet worden bereikt.

MongoDB-verbindingspools in AWS Lambda

In dit bericht laten we u voorbeelden zien van zowel Node.js als Java-stuurprogramma voor MongoDB. Voor deze zelfstudie gebruiken we MongoDB gehost op ScaleGrid met AWS EC2-instanties. Het duurt minder dan 5 minuten om in te stellen en u kunt een gratis proefperiode van 30 dagen maken hier om aan de slag te gaan.
Hoe #MongoDB Connection Pooling te gebruiken op AWS Lambda met Node.js en Lambda DriversClick To Tweet

Java Driver MongoDB-verbindingspool

Hier is de code om de MongoDB-verbindingspool in te schakelen met behulp van het Java-stuurprogramma in de AWS Lambda-handlerfunctie:


public class LambdaFunctionHandler
		implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

	private MongoClient sgMongoClient;
	private String sgMongoClusterURI;
	private String sgMongoDbName;

	@Override
	public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
		APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
		response.setStatusCode(200);

		try {
			context.getLogger().log("Input: " + new Gson().toJson(input));
			init(context);
			String body = getLastAlert(input, context);
			context.getLogger().log("Result body: " + body);
			response.setBody(body);
		} catch (Exception e) {
			response.setBody(e.getLocalizedMessage());
			response.setStatusCode(500);
		}

		return response;
	}

	private MongoDatabase getDbConnection(String dbName, Context context) {
		if (sgMongoClient == null) {
			context.getLogger().log("Initializing new connection");
			MongoClientOptions.Builder destDboptions = MongoClientOptions.builder();
			destDboptions.socketKeepAlive(true);
			sgMongoClient = new MongoClient(new MongoClientURI(sgMongoClusterURI, destDboptions));
			return sgMongoClient.getDatabase(dbName);
		}
		context.getLogger().log("Reusing existing connection");
		return sgMongoClient.getDatabase(dbName);
	}

	private String getLastAlert(APIGatewayProxyRequestEvent input, Context context) {
		String userId = input.getPathParameters().get("userId");
		MongoDatabase db = getDbConnection(sgMongoDbName, context);
		MongoCollection coll = db.getCollection("useralerts");
		Bson query = new Document("userId", Integer.parseInt(userId));
		Object result = coll.find(query).sort(Sorts.descending("$natural")).limit(1).first();
		context.getLogger().log("Result: " + result);
		return new Gson().toJson(result);
	}

	private void init(Context context) {
		sgMongoClusterURI = System.getenv("SCALEGRID_MONGO_CLUSTER_URI");
		sgMongoDbName = System.getenv("SCALEGRID_MONGO_DB_NAME");
	}

}

De verbindingspooling wordt hier bereikt door een sgMongoClient . te declareren variabele buiten de handlerfunctie. De variabelen die buiten de handlermethode worden gedeclareerd, blijven geïnitialiseerd voor alle aanroepen, zolang dezelfde container opnieuw wordt gebruikt. Dit geldt voor elke andere programmeertaal die wordt ondersteund door AWS Lambda.

Node.js-stuurprogramma MongoDB-verbindingspool

Voor het Node.js-stuurprogramma is het ook voldoende om de verbindingsvariabele in globaal bereik te declareren. Er is echter een speciale instelling zonder welke de pooling van de verbinding niet mogelijk is. Die parameter is callbackWaitsForEmptyEventLoop die bij het contextobject van Lambda hoort. Als u deze eigenschap instelt op false, bevriest AWS Lambda het proces en alle statusgegevens. Dit wordt gedaan kort nadat de callback is aangeroepen, zelfs als er gebeurtenissen in de gebeurtenislus zijn.

Hier is de code om de MongoDB-verbindingspool in te schakelen met behulp van het Node.js-stuurprogramma in de AWS Lambda-handlerfunctie:


'use strict'

var MongoClient = require('mongodb').MongoClient;

let mongoDbConnectionPool = null;
let scalegridMongoURI = null;
let scalegridMongoDbName = null;

exports.handler = (event, context, callback) => {

    console.log('Received event:', JSON.stringify(event));
    console.log('remaining time =', context.getRemainingTimeInMillis());
    console.log('functionName =', context.functionName);
    console.log('AWSrequestID =', context.awsRequestId);
    console.log('logGroupName =', context.logGroupName);
    console.log('logStreamName =', context.logStreamName);
    console.log('clientContext =', context.clientContext);
   
    // This freezes node event loop when callback is invoked
    context.callbackWaitsForEmptyEventLoop = false;

    var mongoURIFromEnv = process.env['SCALEGRID_MONGO_CLUSTER_URI'];
    var mongoDbNameFromEnv = process.env['SCALEGRID_MONGO_DB_NAME'];
    if(!scalegridMongoURI) {
	if(mongoURIFromEnv){
		scalegridMongoURI = mongoURIFromEnv;
	} else {
		var errMsg = 'Scalegrid MongoDB cluster URI is not specified.';
		console.log(errMsg);
		var errResponse = prepareResponse(null, errMsg);
		return callback(errResponse);
	}			
    }

    if(!scalegridMongoDbName) {
	if(mongoDbNameFromEnv) {
                scalegridMongoDbName = mongoDbNameFromEnv;
	} else {
		var errMsg = 'Scalegrid MongoDB name not specified.';
		console.log(errMsg);
		var errResponse = prepareResponse(null, errMsg);
		return callback(errResponse);
	}
    }

    handleEvent(event, context, callback);
};


function getMongoDbConnection(uri) {

    if (mongoDbConnectionPool && mongoDbConnectionPool.isConnected(scalegridMongoDbName)) {
        console.log('Reusing the connection from pool');
        return Promise.resolve(mongoDbConnectionPool.db(scalegridMongoDbName));
    }

    console.log('Init the new connection pool');
    return MongoClient.connect(uri, { poolSize: 10 })
        .then(dbConnPool => { 
                            mongoDbConnectionPool = dbConnPool; 
                            return mongoDbConnectionPool.db(scalegridMongoDbName); 
                          });
}

function handleEvent(event, context, callback) {
    getMongoDbConnection(scalegridMongoURI)
        .then(dbConn => {
			console.log('retrieving userId from event.pathParameters');
			var userId = event.pathParameters.userId;
			getAlertForUser(dbConn, userId, context);
		})
        .then(response => {
            console.log('getAlertForUser response: ', response);
            callback(null, response);
        })
        .catch(err => {
            console.log('=> an error occurred: ', err);
            callback(prepareResponse(null, err));
        });
}

function getAlertForUser(dbConn, userId, context) {

    return dbConn.collection('useralerts').find({'userId': userId}).sort({$natural:1}).limit(1)
        .toArray()
        .then(docs => { return prepareResponse(docs, null);})
        .catch(err => { return prepareResponse(null, err); });
}

function prepareResponse(result, err) {
	if(err) {
		return { statusCode:500, body: err };
	} else {
		return { statusCode:200, body: result };
	}
}

AWS Lambda-verbindingspoolanalyse en -observaties

Om de prestaties en optimalisatie van het gebruik van verbindingspools te verifiëren, hebben we enkele tests uitgevoerd voor zowel Java- als Node.js Lambda-functies. Met behulp van de AWS API-gateway als trigger hebben we de functies in een reeks van 50 verzoeken per iteratie aangeroepen en de gemiddelde reactietijd voor een verzoek in elke iteratie bepaald. Deze test werd herhaald voor Lambda-functies zonder eerst de verbindingspool te gebruiken en later met de verbindingspool.

De bovenstaande grafieken vertegenwoordigen de gemiddelde reactietijd van een verzoek in elke iteratie. Hier ziet u het verschil in reactietijd wanneer een verbindingspool wordt gebruikt voor het uitvoeren van databasebewerkingen. De responstijd bij gebruik van een verbindingspool is aanzienlijk lager omdat de verbindingspool eenmaal wordt geïnitialiseerd en de verbinding opnieuw gebruikt in plaats van de verbinding voor elke databasebewerking te openen en te sluiten.

Het enige opvallende verschil tussen Java en Node.js Lambda-functies is de koude starttijd.

Wat is koude starttijd?

Koude starttijd verwijst naar de tijd die de AWS Lambda-functie nodig heeft voor initialisatie. Wanneer de Lambda-functie zijn eerste aanvraag ontvangt, initialiseert deze de container en de vereiste procesomgeving. In de bovenstaande grafieken omvat de responstijd van verzoek 1 de koude starttijd, die aanzienlijk verschilt op basis van de programmeertaal die wordt gebruikt voor de AWS Lambda-functie.

Moet ik me zorgen maken over de koude starttijd?

Als u de AWS API-gateway gebruikt als trigger voor de Lambda-functie, moet u rekening houden met de koude starttijd. De API-gatewayreactie geeft een foutmelding als de AWS Lambda-integratiefunctie niet binnen het opgegeven tijdsbestek is geïnitialiseerd. De time-out voor integratie van de API-gateway varieert van 50 milliseconden tot 29 seconden.

In de grafiek voor de Java AWS Lambda-functie kunt u zien dat het eerste verzoek meer dan 29 seconden duurde, waardoor de reactie van de API-gateway een fout gaf. Koude starttijd voor AWS Lambda-functie geschreven met Java is hoger in vergelijking met andere ondersteunde programmeertalen. Om deze problemen met de koude starttijd op te lossen, kunt u een initialisatieverzoek starten vóór de daadwerkelijke aanroep. Het andere alternatief is om het opnieuw te proberen aan de clientzijde. Op die manier zal de nieuwe poging slagen als het verzoek mislukt vanwege de koude starttijd.

Wat gebeurt er met de AWS Lambda-functie tijdens inactiviteit?

Tijdens onze tests hebben we ook vastgesteld dat AWS Lambda-hostingcontainers werden gestopt toen ze een tijdje inactief waren. Dit interval varieerde van 7 tot 20 minuten. Dus als je Lambda-functies niet vaak worden gebruikt, moet je overwegen ze in leven te houden door hartslagverzoeken af ​​te vuren of nieuwe pogingen toe te voegen aan de clientzijde.

Wat gebeurt er als ik Lambda-functies gelijktijdig aanroep?

Als Lambda-functies gelijktijdig worden aangeroepen, zal Lambda veel containers gebruiken om het verzoek uit te voeren. AWS Lambda biedt standaard een onbeperkte gelijktijdigheid van 1000 verzoeken en is configureerbaar voor een bepaalde Lambda-functie.

Dit is waar u voorzichtig moet zijn met de grootte van de verbindingspool, aangezien gelijktijdige verzoeken te veel verbindingen kunnen openen. U moet de grootte van de verbindingspool dus optimaal houden voor uw functie. Zodra de containers echter zijn gestopt, worden verbindingen vrijgegeven op basis van een time-out van de MongoDB-server.

AWS Lambda Connection Pooling Conclusie

Lambda-functies zijn staatloos en asynchroon, en door de databaseverbindingspool te gebruiken, kunt u er een status aan toevoegen. Dit helpt echter alleen als de containers opnieuw worden gebruikt, waardoor u veel tijd bespaart. Verbindingspooling met AWS EC2 is gemakkelijker te beheren omdat een enkele instantie de status van de verbindingspool probleemloos kan volgen. Het gebruik van AWS EC2 vermindert dus aanzienlijk het risico dat de databaseverbindingen opraken. AWS Lambda is ontworpen om beter te werken wanneer het gewoon een API kan raken en geen verbinding hoeft te maken met een database-engine.


  1. Redis/Jedis - Verwijderen op patroon?

  2. MongoDB $dateFromParts

  3. ServiceStack.Redis:kan geen verbinding maken:sPort:50071

  4. Ubuntu 16.04 systemd redis-problemen met ulimit