MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Как да използвате MongoDB Connection Pooling на AWS Lambda

В тази публикация ще ви покажем как да използвате обединяване на връзки MongoDB в AWS Lambda, като използвате драйвери както на Node.js, така и на Java.

Какво е AWS Lambda?

AWS Lambda е управлявана от събития компютърна услуга без сървър, предоставяна от Amazon Web Services . Позволява на потребителя да изпълнява код без никакви административни задачи, за разлика от екземплярите EC2 където потребител е отговорен за осигуряване на сървъри, мащабиране, висока наличност и т.н. Вместо това само трябва да качите кода и да настроите задействането на събитието, а AWS Lambda автоматично се грижи за всичко останало.

AWS Lambda поддържа различни времена за изпълнение, включително Node.js , Python , Java , и Напред . Може да се задейства директно от услуги на AWS като S3 , DynamoDB ,Кинезис , SNS , и т.н. В нашия пример използваме шлюза на AWS API, за да задействаме ламбда функциите.

Какво е пул за връзки?

Отварянето и затварянето на връзка с база данни е скъпа операция, тъй като включва както процесорно време, така и памет. Ако дадено приложение трябва да отвори връзка с база данни за всяка операция, това ще има сериозно въздействие върху производителността.

Ами ако имаме куп връзки към базата данни, които се поддържат живи в кеша? Всеки път, когато дадено приложение трябва да изпълни операция с база данни, то може да заеме връзка от кеша, да извърши необходимата операция и да я върне. Използвайки този подход, можем да спестим времето, необходимо за установяване на нова връзка всеки път, и да използваме повторно връзките. Този кеш е известен като пул за връзки .

Размерът на пула за връзки може да се конфигурира в повечето драйвери на MongoDB и размерът на пула по подразбиране варира от драйвер до драйвер. Например, това е 5 в драйвера на Node.js, докато в драйвера на Java е 100. Размерът на пула за връзки определя максималния брой паралелни заявки, които вашият драйвер може да обработва в даден момент. Ако се достигне ограничението на пула за връзки, всички нови заявки ще бъдат направени, за да изчакат, докато съществуващите бъдат завършени. Следователно размерът на пула трябва да бъде избран внимателно, като се има предвид натоварването на приложението и едновременността, която трябва да се постигне.

Пулове за връзки MongoDB в AWS Lambda

В тази публикация ще ви покажем примери, включващи Node.js и Java драйвер за MongoDB. За този урок използваме MongoDB, хостван в ScaleGrid, използвайки AWS EC2 екземпляри. Настройването отнема по-малко от 5 минути и можете да създадете безплатен 30-дневен пробен период тук, за да започнете.
Как да използвате #MongoDB Connection Pooling в AWS Lambda с помощта на Node.js и Lambda Drivers Щракнете, за да Tweet

Java Driver MongoDB Connection Pool

Ето кода за активиране на пула за връзки MongoDB с помощта на драйвера на Java във функцията за манипулиране на 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");
	}

}

Обединяването на връзки се постига тук чрез деклариране на sgMongoClient променлива извън функцията манипулатор. Променливите, декларирани извън метода на манипулатора, остават инициализирани в извикванията, докато същият контейнер се използва повторно. Това важи за всеки друг език за програмиране, поддържан от AWS Lambda.

Node.js Driver MongoDB Connection Pool

За драйвера Node.js декларирането на променливата на връзката в глобалния обхват също ще свърши работа. Има обаче специална настройка, без която обединяването на връзки не е възможно. Този параметър е callbackWaitsForEmptyEventLoop който принадлежи на контекстния обект на Lambda. Задаването на това свойство на false ще накара AWS Lambda да замрази процеса и всички данни за състоянието. Това се прави скоро след извикване на обратното извикване, дори ако има събития в цикъла на събитията.

Ето кода за активиране на пула за връзки MongoDB с драйвера Node.js във функцията за манипулиране на 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 };
	}
}

Анализ и наблюдения на пула за ламбда връзки на AWS

За да проверим производителността и оптимизирането на използването на пулове за връзки, проведохме няколко теста както за Java, така и за функциите Lambda на Node.js. Използвайки шлюза на AWS API като задействане, ние извикахме функциите в поредица от 50 заявки на итерация и определихме средното време за отговор за заявка във всяка итерация. Този тест беше повторен за Lambda функции, без първоначално да се използва пулът за връзки, а по-късно с пула за връзки.

Графиките по-горе представят средното време за отговор на заявка във всяка итерация. Тук можете да видите разликата във времето за реакция, когато пул за връзки се използва за извършване на операции с база данни. Времето за реакция при използване на пул за връзки е значително по-ниско поради факта, че пулът за връзки се инициализира веднъж и използва повторно връзката, вместо да отваря и затваря връзката за всяка операция с база данни.

Единствената забележителна разлика между функциите Lambda на Java и Node.js е времето за студен старт.

Какво е студен старт?

Времето за студен старт се отнася до времето, необходимо на функцията AWS Lambda за инициализация. Когато Lambda функцията получи първата си заявка, тя ще инициализира контейнера и необходимата среда на процеса. В горните графики времето за отговор на заявка 1 включва времето за студен старт, което значително се различава в зависимост от езика за програмиране, използван за функцията AWS Lambda.

Трябва ли да се притеснявам за студен старт?

Ако използвате шлюза на AWS API като задействане за функцията Lambda, тогава трябва да вземете предвид времето за студен старт. Отговорът на шлюза на API ще се появи грешка, ако функцията за интегриране на AWS Lambda не е инициализирана в дадения времеви диапазон. Времето за изчакване за интегриране на API шлюз варира от 50 милисекунди до 29 секунди.

В графиката за функцията Java AWS Lambda можете да видите, че първата заявка е отнела повече от 29 секунди, следователно отговорът на шлюза на API е дал грешка. Времето за студен старт за AWS Lambda функция, написана с Java, е по-висока в сравнение с други поддържани езици за програмиране. За да разрешите тези проблеми с времето за студен старт, можете да задействате заявка за инициализация преди действителното извикване. Другата алтернатива е да имате повторен опит от страна на клиента. По този начин, ако заявката не успее поради студен старт, повторният опит ще бъде успешен.

Какво се случва с AWS Lambda функция по време на неактивност?

При нашето тестване ние също така забелязахме, че AWS Lambda хостинг контейнерите бяха спрени, когато бяха неактивни за известно време. Този интервал варира от 7 до 20 минути. Така че, ако вашите Lambda функции не се използват често, тогава трябва да помислите да ги поддържате живи, като задействате заявки за сърдечен ритъм или добавяте повторни опити от страна на клиента.

Какво се случва, когато извикам ламбда функции едновременно?

Ако Lambda функциите се извикват едновременно, тогава Lambda ще използва много контейнери, за да обслужи заявката. По подразбиране AWS Lambda осигурява нерезервиран едновременност на 1000 заявки и може да се конфигурира за дадена Lambda функция.

Тук трябва да внимавате за размера на пула от връзки, тъй като едновременните заявки могат да отворят твърде много връзки. Така че трябва да поддържате размера на пула за връзки оптимален за вашата функция. Въпреки това, след като контейнерите бъдат спрени, връзките ще бъдат освободени въз основа на изчакване от сървъра MongoDB.

AWS Lambda Connection Pooling Заключение

Ламбда функциите са без състояние и асинхронни и като използвате пула за връзки към базата данни, ще можете да добавите състояние към него. Това обаче ще помогне само когато контейнерите се използват повторно, позволявайки ви да спестите много време. Пулирането на връзки с помощта на AWS EC2 е по-лесно за управление, тъй като един екземпляр може да проследява състоянието на своя пул за връзки без никакъв проблем. По този начин използването на AWS EC2 значително намалява риска от изчерпване на връзките към базата данни. AWS Lambda е проектиран да работи по-добре, когато може просто да удари API и не трябва да се свързва с движок за база данни.


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Как да защитите MongoDB с потребителско име и парола

  2. MongoDB група по стойности в поле на масив

  3. ECONNREFUSED грешка при свързване към mongodb от node.js

  4. Настройване на singleton връзка с node.js и mongo

  5. Mongoose.js създава множество връзки към MongoDB от едно извикване на connect().