sql >> Databáze >  >> NoSQL >> MongoDB

Jak používat MongoDB Connection Pooling na AWS Lambda

V tomto příspěvku vám ukážeme, jak používat sdružování připojení MongoDB na AWS Lambda pomocí ovladačů Node.js i Java.

Co je AWS Lambda?

AWS Lambda je událostmi řízená, bezserverová výpočetní služba poskytovaná Amazon Web Services . Umožňuje uživateli spouštět kód bez jakýchkoli administrativních úkolů, na rozdíl od instancí EC2 kde je uživatel zodpovědný za poskytování serverů, škálování, vysokou dostupnost atd. Místo toho stačí pouze nahrát kód a nastavit spouštění události a AWS Lambda se automaticky postará o vše ostatní.

AWS Lambda podporuje různá běhová prostředí, včetně Node.js , Python , Java a Přejít . Může být přímo spouštěn službami AWS, jako je S3 , DynamoDB , Kineze , SNS , atd. V našem příkladu používáme bránu AWS API ke spouštění funkcí Lambda.

Co je to fond připojení?

Otevírání a zavírání databázového připojení je nákladná operace, protože vyžaduje čas CPU i paměť. Pokud aplikace potřebuje pro každou operaci otevřít připojení k databázi, bude to mít vážný dopad na výkon.

Co když máme spoustu databázových připojení, která jsou udržována při životě v mezipaměti? Kdykoli aplikace potřebuje provést databázovou operaci, může si vypůjčit spojení z mezipaměti, provést požadovanou operaci a vrátit ji zpět. Použitím tohoto přístupu můžeme ušetřit čas potřebný k navázání nového připojení pokaždé a znovu použít připojení. Tato mezipaměť je známá jako fond připojení .

Velikost fondu připojení je konfigurovatelná ve většině ovladačů MongoDB a výchozí velikost fondu se liší ovladač od ovladače. Například v ovladači Node.js je to 5, zatímco v ovladači Java je to 100. Velikost fondu připojení určuje maximální počet paralelních požadavků, které může váš ovladač v daném čase zpracovat. Je-li dosaženo limitu fondu připojení, budou provedeny nové požadavky, aby počkaly na dokončení stávajících. Proto je třeba velikost fondu volit pečlivě s ohledem na zatížení aplikací a souběžnost, kterých má být dosaženo.

Součásti připojení MongoDB v AWS Lambda

V tomto příspěvku vám ukážeme příklady zahrnující ovladač Node.js i Java pro MongoDB. Pro tento tutoriál používáme MongoDB hostovaný na ScaleGrid pomocí instancí AWS EC2. Nastavení trvá méně než 5 minut a můžete si vytvořit bezplatnou 30denní zkušební verzi zde, abyste mohli začít.
Jak používat #MongoDB sdružování připojení na AWS Lambda pomocí ovladačů Node.js a Lambda Kliknutím na Tweet

Java Driver MongoDB Connection Pool

Zde je kód pro povolení fondu připojení MongoDB pomocí ovladače Java ve funkci obslužné rutiny AWS Lambda:


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

}

Sdružování připojení je zde dosaženo deklarací sgMongoClient proměnná mimo funkci handleru. Proměnné deklarované mimo metodu obslužné rutiny zůstávají inicializovány napříč voláními, dokud je znovu použit stejný kontejner. To platí pro jakýkoli jiný programovací jazyk podporovaný AWS Lambda.

Pool připojení MongoDB ovladače Node.js

U ovladače Node.js bude stačit deklarace proměnné připojení v globálním rozsahu. Existuje však speciální nastavení, bez kterého není sdružování připojení možné. Tento parametr je callbackWaitsForEmptyEventLoop který patří k objektu kontextu Lambda. Nastavení této vlastnosti na false způsobí, že AWS Lambda zmrazí proces a veškerá data o stavu. To se provádí brzy po zavolání zpětného volání, i když jsou ve smyčce událostí události.

Zde je kód pro aktivaci fondu připojení MongoDB pomocí ovladače Node.js ve funkci obslužné rutiny AWS Lambda:


'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 };
	}
}

Analýza a pozorování fondu připojení lambda AWS

Abychom ověřili výkon a optimalizaci používání poolů připojení, provedli jsme několik testů pro funkce Java i Node.js Lambda. Pomocí brány AWS API jako spouštěče jsme funkce vyvolali v sérii 50 požadavků na iteraci a určili průměrnou dobu odezvy na požadavek v každé iteraci. Tento test byl opakován pro funkce Lambda bez použití fondu připojení zpočátku a později s fondem připojení.

Výše uvedené grafy představují průměrnou dobu odezvy požadavku v každé iteraci. Zde můžete vidět rozdíl v době odezvy, když se k provádění databázových operací používá fond připojení. Doba odezvy při použití fondu připojení je výrazně kratší kvůli skutečnosti, že fond připojení se inicializuje jednou a znovu použije připojení namísto otevírání a zavírání připojení pro každou operaci databáze.

Jediný významný rozdíl mezi funkcemi Java a Node.js Lambda je čas studeného startu.

Co je čas studeného startu?

Čas studeného startu se vztahuje k době, kterou potřebuje funkce AWS Lambda k inicializaci. Když funkce Lambda obdrží svůj první požadavek, inicializuje kontejner a požadované procesní prostředí. Ve výše uvedených grafech doba odezvy požadavku 1 zahrnuje dobu studeného startu, která se výrazně liší v závislosti na programovacím jazyce použitém pro funkci AWS Lambda.

Musím si dělat starosti s časem studeného startu?

Pokud používáte bránu AWS API jako spouštěč pro funkci Lambda, musíte vzít v úvahu čas studeného startu. Pokud funkce integrace AWS Lambda není v daném časovém rozsahu inicializována, odezva brány API se zobrazí chybově. Časový limit integrace brány API se pohybuje od 50 milisekund do 29 sekund.

V grafu pro funkci Java AWS Lambda můžete vidět, že první požadavek trval déle než 29 sekund, a proto došlo k chybě brány API. Doba studeného startu pro funkci AWS Lambda napsanou pomocí Javy je ve srovnání s jinými podporovanými programovacími jazyky delší. Chcete-li vyřešit tyto problémy s dobou studeného startu, můžete před skutečným vyvoláním spustit žádost o inicializaci. Druhou alternativou je opakování pokusu na straně klienta. Pokud tedy požadavek selže kvůli času studeného startu, pokus bude úspěšný.

Co se stane s funkcí AWS Lambda během nečinnosti?

Při našem testování jsme také pozorovali, že hostitelské kontejnery AWS Lambda byly zastaveny, když byly nějakou dobu neaktivní. Tento interval se pohyboval od 7 do 20 minut. Pokud tedy vaše funkce Lambda nepoužíváte často, musíte zvážit jejich udržení při životě buď spouštěním požadavků na srdeční tep nebo přidáváním opakování na straně klienta.

Co se stane, když současně vyvolám funkce lambda?

Pokud jsou funkce Lambda vyvolány souběžně, pak Lambda použije mnoho kontejnerů pro obsluhu požadavku. Ve výchozím nastavení poskytuje AWS Lambda nevyhrazenou souběžnost 1000 požadavků a je konfigurovatelná pro danou funkci Lambda.

Zde je třeba dávat pozor na velikost fondu připojení, protože souběžné požadavky mohou otevřít příliš mnoho připojení. Takže musíte udržovat velikost fondu připojení optimální pro vaši funkci. Jakmile se však kontejnery zastaví, připojení se uvolní na základě časového limitu ze serveru MongoDB.

Závěr AWS Lambda Connection Pooling

Funkce lambda jsou bezstavové a asynchronní a pomocí fondu připojení k databázi do něj budete moci přidat stav. To však pomůže pouze při opětovném použití kontejnerů, což vám umožní ušetřit spoustu času. Sdružování připojení pomocí AWS EC2 se snáze spravuje, protože jedna instance může bez problémů sledovat stav svého fondu připojení. Použití AWS EC2 tedy výrazně snižuje riziko vyčerpání databázových připojení. AWS Lambda je navržena tak, aby fungovala lépe, když stačí narazit na API a nemusí se připojovat k databázovému enginu.


  1. Udržování otevřeného připojení Redis pomocí BookSleeve

  2. Jak můžete odstranit všechny dokumenty ze sbírky pomocí Mongoose?

  3. Kódování HDFS Erasure v Big Data Hadoop

  4. Jak spustit soubor js v mongo pomocí jarních dat