Trong bài đăng này, chúng tôi sẽ hướng dẫn bạn cách sử dụng tổng hợp kết nối MongoDB trên AWS Lambda bằng cách sử dụng cả trình điều khiển Node.js và Java.
AWS Lambda là gì?
AWS Lambda là một dịch vụ máy tính không máy chủ, hướng sự kiện được cung cấp bởi Amazon Web Services . Nó cho phép người dùng chạy mã mà không cần bất kỳ tác vụ quản trị nào, không giống như phiên bản EC2 trong đó người dùng chịu trách nhiệm cung cấp máy chủ, mở rộng quy mô, tính khả dụng cao, v.v. Thay vào đó, bạn chỉ phải tải lên mã và thiết lập trình kích hoạt sự kiện và AWS Lambda tự động xử lý mọi thứ khác.
AWS Lambda hỗ trợ nhiều thời gian chạy khác nhau, bao gồm Node.js , Python , Java và Bắt đầu . Nó có thể được kích hoạt trực tiếp bởi các dịch vụ AWS như S3 , DynamoDB , Kinesis , SNS , v.v. Trong ví dụ của chúng tôi, chúng tôi sử dụng cổng AWS API để kích hoạt các chức năng Lambda.
Nhóm kết nối là gì?
Mở và đóng một kết nối cơ sở dữ liệu là một hoạt động tốn kém vì nó liên quan đến cả thời gian và bộ nhớ của CPU. Nếu một ứng dụng cần mở kết nối cơ sở dữ liệu cho mọi hoạt động, thì điều đó sẽ có tác động nghiêm trọng đến hiệu suất.
Điều gì sẽ xảy ra nếu chúng ta có một loạt các kết nối cơ sở dữ liệu được giữ nguyên trong bộ nhớ cache? Bất cứ khi nào ứng dụng cần thực hiện thao tác với cơ sở dữ liệu, nó có thể mượn kết nối từ bộ đệm, thực hiện thao tác cần thiết và trả lại. Bằng cách sử dụng phương pháp này, chúng tôi có thể tiết kiệm thời gian cần thiết để thiết lập một kết nối mới mọi lúc và sử dụng lại các kết nối. Bộ nhớ cache này được gọi là nhóm kết nối .
Kích thước của nhóm kết nối có thể định cấu hình trong hầu hết các trình điều khiển MongoDB và kích thước nhóm mặc định khác nhau giữa các trình điều khiển. Ví dụ:nó là 5 trong trình điều khiển Node.js, trong khi đó là 100 trong trình điều khiển Java. Kích thước nhóm kết nối xác định số lượng yêu cầu song song tối đa mà trình điều khiển của bạn có thể xử lý tại một thời điểm nhất định. Nếu đạt đến giới hạn nhóm kết nối, bất kỳ yêu cầu mới nào sẽ được thực hiện để đợi cho đến khi các yêu cầu hiện có được hoàn thành. Do đó, kích thước nhóm cần phải được lựa chọn cẩn thận, xem xét tải ứng dụng và sự đồng thời cần đạt được.
Các hồ kết nối MongoDB trong AWS Lambda
Trong bài đăng này, chúng tôi sẽ chỉ cho bạn các ví dụ liên quan đến cả Node.js và trình điều khiển Java cho MongoDB. Đối với hướng dẫn này, chúng tôi sử dụng MongoDB được lưu trữ trên ScaleGrid bằng cách sử dụng các phiên bản AWS EC2. Chỉ mất chưa đầy 5 phút để thiết lập và bạn có thể tạo bản dùng thử 30 ngày miễn phí tại đây để bắt đầu.
Cách sử dụng Tổng hợp kết nối #MongoDB trên AWS Lambda Sử dụng Node.js và Lambda Drivers Nhấp để Tweet
Nhóm kết nối MongoDB của trình điều khiển Java
Đây là mã để bật nhóm kết nối MongoDB bằng trình điều khiển Java trong hàm xử lý 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");
}
}
Tổng hợp kết nối đạt được ở đây bằng cách khai báo sgMongoClient biến bên ngoài của hàm xử lý. Các biến được khai báo bên ngoài phương thức xử lý vẫn được khởi tạo qua các lệnh gọi, miễn là cùng một vùng chứa được sử dụng lại. Điều này đúng với bất kỳ ngôn ngữ lập trình nào khác được AWS Lambda hỗ trợ.
Nhóm kết nối MongoDB trình điều khiển Node.js
Đối với trình điều khiển Node.js, việc khai báo biến kết nối trong phạm vi toàn cục cũng sẽ thực hiện thủ thuật. Tuy nhiên, có một cài đặt đặc biệt mà không thể kết nối gộp chung. Tham số đó là callbackWaitsForEmptyEventLoop thuộc về đối tượng ngữ cảnh của Lambda. Đặt thuộc tính này thành false sẽ khiến AWS Lambda đóng băng quy trình và mọi dữ liệu trạng thái. Điều này được thực hiện ngay sau khi gọi lại được gọi, ngay cả khi có sự kiện trong vòng lặp sự kiện.
Đây là mã để bật nhóm kết nối MongoDB bằng trình điều khiển Node.js trong hàm xử lý 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 };
}
}
Phân tích và quan sát nhóm kết nối AWS Lambda
Để xác minh hiệu suất và sự tối ưu hóa của việc sử dụng các nhóm kết nối, chúng tôi đã chạy một số thử nghiệm cho cả hai hàm Lambda và Node.js Lambda. Sử dụng cổng AWS API làm trình kích hoạt, chúng tôi đã gọi các hàm trong một loạt 50 yêu cầu mỗi lần lặp và xác định thời gian phản hồi trung bình cho một yêu cầu trong mỗi lần lặp. Thử nghiệm này được lặp lại đối với các hàm Lambda mà không sử dụng nhóm kết nối ban đầu và sau đó với nhóm kết nối.
Các biểu đồ trên thể hiện thời gian phản hồi trung bình của một yêu cầu trong mỗi lần lặp. Bạn có thể thấy ở đây sự khác biệt về thời gian phản hồi khi nhóm kết nối được sử dụng để thực hiện các hoạt động cơ sở dữ liệu. Thời gian phản hồi khi sử dụng nhóm kết nối thấp hơn đáng kể do nhóm kết nối khởi tạo một lần và sử dụng lại kết nối thay vì mở và đóng kết nối cho mỗi hoạt động cơ sở dữ liệu.
Sự khác biệt đáng chú ý duy nhất giữa các hàm Lambda của Java và Node.js là thời gian bắt đầu nguội.
Thời gian bắt đầu nguội là gì?
Thời gian bắt đầu nguội đề cập đến thời gian mà hàm AWS Lambda thực hiện để khởi tạo. Khi hàm Lambda nhận được yêu cầu đầu tiên, nó sẽ khởi tạo vùng chứa và môi trường xử lý yêu cầu. Trong các biểu đồ trên, thời gian phản hồi của yêu cầu 1 bao gồm thời gian bắt đầu nguội, khác biệt đáng kể dựa trên ngôn ngữ lập trình được sử dụng cho hàm AWS Lambda.
Tôi có cần lo lắng về thời gian bắt đầu nguội không?
Nếu bạn đang sử dụng cổng AWS API làm trình kích hoạt cho hàm Lambda, thì bạn phải cân nhắc thời gian bắt đầu lạnh. Phản hồi cổng API sẽ xảy ra lỗi nếu chức năng tích hợp AWS Lambda không được khởi chạy trong phạm vi thời gian nhất định. Thời gian chờ tích hợp cổng API nằm trong khoảng từ 50 mili giây đến 29 giây.
Trong biểu đồ cho hàm Java AWS Lambda, bạn có thể thấy rằng yêu cầu đầu tiên mất hơn 29 giây, do đó, phản hồi cổng API đã xảy ra lỗi. Thời gian khởi động nguội đối với hàm AWS Lambda được viết bằng Java cao hơn so với các ngôn ngữ lập trình được hỗ trợ khác. Để giải quyết các vấn đề về thời gian bắt đầu nguội này, bạn có thể kích hoạt một yêu cầu khởi tạo trước khi thực hiện lệnh gọi. Một giải pháp thay thế khác là thử lại ở phía khách hàng. Bằng cách đó, nếu yêu cầu không thành công do thời gian bắt đầu lạnh, quá trình thử lại sẽ thành công.
Điều gì xảy ra với chức năng AWS Lambda khi không hoạt động?
Trong thử nghiệm của mình, chúng tôi cũng nhận thấy rằng các vùng chứa lưu trữ AWS Lambda đã bị dừng khi chúng không hoạt động trong một thời gian. Khoảng thời gian này thay đổi từ 7 đến 20 phút. Vì vậy, nếu các hàm Lambda của bạn không được sử dụng thường xuyên, thì bạn cần cân nhắc giữ chúng tồn tại bằng cách kích hoạt các yêu cầu về nhịp tim hoặc thêm các lần thử lại ở phía máy khách.
Điều gì sẽ xảy ra khi tôi gọi đồng thời các hàm Lambda?
Nếu các hàm Lambda được gọi đồng thời, thì Lambda sẽ sử dụng nhiều vùng chứa để phục vụ yêu cầu. Theo mặc định, AWS Lambda cung cấp 1000 yêu cầu đồng thời chưa được lưu trữ và có thể định cấu hình cho một hàm Lambda nhất định.
Đây là lúc bạn cần phải cẩn thận về kích thước nhóm kết nối vì các yêu cầu đồng thời có thể mở quá nhiều kết nối. Vì vậy, bạn phải giữ cho kích thước nhóm kết nối tối ưu cho chức năng của bạn. Tuy nhiên, khi các vùng chứa bị dừng, các kết nối sẽ được giải phóng dựa trên thời gian chờ từ máy chủ MongoDB.
Kết luận tổng hợp kết nối AWS Lambda
Các hàm Lambda là không trạng thái và không đồng bộ, và bằng cách sử dụng nhóm kết nối cơ sở dữ liệu, bạn sẽ có thể thêm trạng thái vào nó. Tuy nhiên, điều này sẽ chỉ hữu ích khi các thùng chứa được tái sử dụng, cho phép bạn tiết kiệm rất nhiều thời gian. Nhóm kết nối sử dụng AWS EC2 dễ quản lý hơn vì một phiên bản duy nhất có thể theo dõi trạng thái của nhóm kết nối mà không gặp bất kỳ sự cố nào. Do đó, sử dụng AWS EC2 làm giảm đáng kể nguy cơ hết kết nối cơ sở dữ liệu. AWS Lambda được thiết kế để hoạt động tốt hơn khi có thể truy cập API và không phải kết nối với công cụ cơ sở dữ liệu.