最佳实践
使用以下最佳实践正确管理Amazon Web Services Lambda 和Atlas之间的连接:
在 AWS Lambda 处理程序函数 外部定义 MongoDB 服务器的客户端。
请勿在每次调用函数时都定义新的客户端对象。这样做会导致驱动程序在每次函数调用时创建一个新的数据库连接。这可能会产生高昂的成本,并可能导致应用程序超出数据库连接限制。为获得最佳性能,请遵循以下准则:
创建一次客户端对象。
存储该对象,以便函数可以跨函数调用重复使用客户端。
连接示例会重复使用现有数据库连接来加快与数据库的通信,并将与数据库的连接数保持在与应用程序流量相当的合理水平。
如果您有一个连接到具有许多分片的分片集群的Lambda函数,则可能会遇到性能问题。示例,对于有 10 个分片的集群,驱动程序默认连接到所有 30 个
mongos实例。您可以在连接字符串中使用srvMaxHosts选项来设立驱动程序连接到的主机的最大数量。要提高驱动程序性能,设立srvMaxHosts=3。示例:mongodb+srv://<db_username>:<db_password>@<clusterName>.mongodb.net/?retryWrites=true&w=majority&srvMaxHosts=3
如果您有一个连接到具有许多分片的分片集群的 Lambda 函数,可能会遇到性能问题。例如,对于具有 10 个分片的分片集群,默认情况下,驱动程序会连接到所有 30 个
mongos实例。您可以使用连接字符串中的srvMaxHosts选项来设置驱动程序连接到的主机的最大数量。要提高驱动程序性能,请设置srvMaxHosts=3。例如:mongodb+srv://<db_username>:<db_password>@<clusterName>.mongodb.net/?retryWrites=true&w=majority&srvMaxHosts=3 要了解更多信息,请参阅《指定连接选项》指南。
如果您的处理程序将 回调 作为最后一个参数,请将 AWS Lambda Context 对象上的
callbackWaitsForEmptyEventLoop属性设置为 false。context.callbackWaitsForEmptyEventLoop = false; 这允许Lambda函数将其结果返回给调用者,而无需关闭MongoDB 数据库连接。设置此属性不适用于异步处理程序。
如果您有一个连接到具有许多分片的分片集群的 Lambda 函数,可能会遇到性能问题。例如,对于具有 10 个分片的分片集群,默认情况下,驱动程序会连接到所有 30 个
mongos实例。您可以使用连接字符串中的srvMaxHosts选项来设置驱动程序连接到的主机的最大数量。要提高驱动程序性能,请设置srvMaxHosts=3。例如:mongodb+srv://<db_username>:<db_password>@<clusterName>.mongodb.net/?retryWrites=true&w=majority&srvMaxHosts=3 要了解详细信息,请参阅连接选项。
如果您有一个连接到具有许多分片的分片集群的 Lambda 函数,可能会遇到性能问题。例如,对于具有 10 个分片的分片集群,默认情况下,驱动程序会连接到所有 30 个
mongos实例。您可以使用连接字符串中的srvMaxHosts选项来设置驱动程序连接到的主机的最大数量。要提高驱动程序性能,请设置srvMaxHosts=3。例如:mongodb+srv://<db_username>:<db_password>@<clusterName>.mongodb.net/?retryWrites=true&w=majority&srvMaxHosts=3 要了解详细信息,请参阅连接选项。
要将无服务器PHP应用程序部署到 AWS Lambda,请使用Bref框架。要安装必备组件并配置 Bref,请按照 Bref 文档中的设置教程进行操作。
接下来,您可以创建项目目录,安装MongoDB和 Bref 依赖项,并通过运行以下命令来初始化无服务器配置:
mkdir bref-mongodb-app && cd bref-mongodb-app composer init composer require bref/bref bref/extra-php-extensions mongodb/mongodb vendor/bin/bref init 最后,将MongoDB PHP扩展添加到您的项目中。
bref/extra-php-extensionsComposer包提供了一个无服务器框架插件,该插件将MongoDB PHP扩展添加为Lambda层。更新serverless.yml文件以包含以下突出显示的行:plugins: - ./vendor/bref/bref - ./vendor/bref/extra-php-extensions functions: api: handler: index.php runtime: php-85-fpm layers: - ${bref-extra:mongodb2-php-85}
如果您有一个连接到具有许多分片的分片集群的 Lambda 函数,可能会遇到性能问题。例如,对于具有 10 个分片的分片集群,默认情况下,驱动程序会连接到所有 30 个
mongos实例。您可以使用连接字符串中的srvMaxHosts选项来设置驱动程序连接到的主机的最大数量。要提高驱动程序性能,请设置srvMaxHosts=3。例如:mongodb+srv://<db_username>:<db_password>@<clusterName>.mongodb.net/?retryWrites=true&w=majority&srvMaxHosts=3 要了解更多信息,请参阅连接到 MongoDB 的工具。
如果您有一个连接到具有许多分片的分片集群的 Lambda 函数,可能会遇到性能问题。例如,对于具有 10 个分片的分片集群,默认情况下,驱动程序会连接到所有 30 个
mongos实例。您可以使用连接字符串中的srvMaxHosts选项来设置驱动程序连接到的主机的最大数量。要提高驱动程序性能,请设置srvMaxHosts=3。例如:mongodb+srv://<db_username>:<db_password>@<clusterName>.mongodb.net/?retryWrites=true&w=majority&srvMaxHosts=3 要了解更多信息,请参阅 URI 选项。
如果您有一个连接到具有许多分片的分片集群的 Lambda 函数,可能会遇到性能问题。例如,对于具有 10 个分片的分片集群,默认情况下,驱动程序会连接到所有 30 个
mongos实例。您可以使用连接字符串中的srvMaxHosts选项来设置驱动程序连接到的主机的最大数量。要提高驱动程序性能,请设置srvMaxHosts=3。例如:mongodb+srv://<db_username>:<db_password>@<clusterName>.mongodb.net/?retryWrites=true&w=majority&srvMaxHosts=3
限制对 Atlas 集群的网络访问。
在 Atlas 集群和 AWS Lambda 函数之间使用网络对等连接或私有端点并通过私有网络连接到 Atlas 集群,以便仅允许使用 IP 访问列表中的私有 IP 地址。
如果您不使用专用网络,请考虑使用具有已映射弹性 IP 地址的 NAT 网关连接到 Atlas 集群。否则,您必须允许所有 IP 地址(0.0.0.0/0)访问您的服务集群。
警告
将
0.0.0.0/0添加到您的 IP 访问列表中,这允许从公共互联网中的任何位置访问集群。在允许从任何位置进行访问时,请确保对所有数据库用户使用强式档案。
注意
如果您将 0.0.0.0/0 CIDR 添加到项目访问列表,Atlas 会向所有直接被授予项目角色或通过团队成员间接被授予项目角色(如果团队被授予项目角色)的所有用户发送警报电子邮件。
将 maxIdleTimeMS 设置为
60000可在空闲时间达到 1 分钟后自动关闭连接。调整maxIdleTimeMS有助于减少无服务器函数发生超时错误的概率。
设置统一 AWS 访问权限,并尽可能使用 AWS IAM 身份验证。
您可以使用Amazon Web Services IAM 角色连接到Atlas集群,而不是在Lambda中硬编码您的凭证。任何访问您的Amazon Web Services Lambda环境的人都可以查看硬编码凭证,这可能会带来安全风险。 借助 Amazon Web Services IAM身份验证, Atlas可通过 假定的 IAM角色 访问 Amazon Web Services Lambda ,因此您无需在连接字符串中提供凭证。
Atlas支持对运行MongoDB版本7.0或更高版本的集群进行Amazon Web Services IAM身份验证。如果您的集群符合这些要求,则强烈建议对Lambda连接使用Amazon Web Services IAM身份验证。
分配给 Lambda 函数的内存量默认为 128 MB。您可将分配给 Lambda 函数的内存量配置为 128 MB 到 10,240 MB 之间。请务必分配足够的内存。增大内存以增加可用虚拟 CPU 的内存量并提高 MongoDB 驱动程序性能。要了解详情,请参阅内存和计算能力。
设置 AWS_STS_REGIONAL_ENDPOINTS 和 AWS_REGION 环境变量。
连接示例
void lambda_handler () { bson_error_t error; mongoc_init(); // Parse URI char *uri_string = "your-mongodb-atlas-connection-string"; mongoc_uri_t *uri = mongoc_uri_new_with_error (uri_string, &error); if (!uri) { fprintf (stderr, "Failed to parse URI: %s\n", error.message); return; } // Create client mongoc_client_t *client = mongoc_client_new_from_uri_with_error(uri, &error); if (!client) { fprintf(stderr, "Failed to create client: %s\n", error.message); return; } // Perform client operations here // Cleanup mongoc_client_destroy(client); mongoc_uri_destroy (uri); mongoc_cleanup(); }
using namespace aws::lambda_runtime; class ExampleAwsHandler { private: mongocxx::client mongo_client; client CreateMongoClient() { mongocxx::uri uri("mongodb://<hostname>:<port>/?authMechanism=MONGODB-AWS"); mongocxx::options::server_api server_api_options(mongocxx::options::server_api::version::k_version_1); mongocxx::options::client client_options; client_options.server_api_opts(server_api_options); return client(uri, client_options); } public: ExampleAwsHandler() : mongo_client(CreateMongoClient()) { } std::string HandleRequest() { try { using bsoncxx::builder::basic::kvp; using bsoncxx::builder::basic::make_document; auto db = mongo_client["my_database"]; auto command = make_document(kvp("hello", 1)); auto result = db.run_command(command.view()); return bsoncxx::to_json(result); } catch (const mongocxx::exception &e) { std::cerr << "MongoDB Exception: " << e.what() << std::endl; return "{}"; } } }; static invocation_response my_handler(invocation_request const &) { ExampleAwsHandler handler; std::string response = handler.HandleRequest(); return invocation_response::success(response, "application/json"); } int main() { Aws::SDKOptions options; Aws::InitAPI(options); { mongocxx::instance instance{}; run_handler(my_handler); } Aws::ShutdownAPI(options); return 0; }
AWS IAM 身份验证
string username = Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID"); string password = Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY"); string awsSessionToken = Environment.GetEnvironmentVariable("AWS_SESSION_TOKEN"); var awsCredentials = new MongoCredential("MONGODB-AWS", new MongoExternalIdentity(username), new PasswordEvidence(password)) .WithMechanismProperty("AWS_SESSION_TOKEN", awsSessionToken); var mongoUrl = MongoUrl.Create($"<MONGODB_URI>"); var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl); mongoClientSettings.Credential = awsCredentials; mongoClientSettings.ServerApi = new ServerApi(ServerApiVersion.V1, strict: true); return new MongoClient(mongoClientSettings);
其他身份验证
private static MongoClient MongoClient { get; set; } private static MongoClient CreateMongoClient() { var mongoClientSettings = MongoClientSettings.FromConnectionString($"<MONGODB_URI>"); mongoClientSettings.ServerApi = new ServerApi(ServerApiVersion.V1, strict: true); return new MongoClient(mongoClientSettings); } static ShareMongoClientLambdaHandler() { MongoClient = CreateMongoClient(); } public string HandleRequest(ILambdaContext context) { var database = MongoClient.GetDatabase("db"); var collection = database.GetCollection<BsonDocument>("coll"); var result = collection.Find(FilterDefinition<BsonDocument>.Empty).First(); return result.ToString(); }
import ( "context" "os" runtime "github.com/aws/aws-lambda-go/lambda" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) var client, err = mongo.Connect(options.Client().ApplyURI(os.Getenv("MONGODB_URI"))) func HandleRequest(ctx context.Context) error { if err != nil { return err } return client.Ping(context.TODO(), nil) }
import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import org.bson.Document; public class ExampleAwsLambdaHandler implements RequestHandler<String, String> { private final MongoClient client; public ExampleAwsLambdaHandler() { client = MongoClients.create(System.getenv("MONGODB_URI")); } public String handleRequest(final String input, final Context context) { return client.getDatabase("admin").runCommand(new Document("ping", 1)).toJson(); } }
import com.mongodb.kotlin.client.coroutine.MongoClient import com.mongodb.kotlin.client.coroutine.MongoDatabase import org.bson.Document import kotlinx.coroutines.runBlocking import com.amazonaws.services.lambda.runtime.Context import com.amazonaws.services.lambda.runtime.RequestHandler class ExampleAwsLambdaHandler : RequestHandler<String, String> { private val client: MongoClient = MongoClient.create(System.getenv("MONGODB_URI")) override fun handleRequest(input: String, context: Context): String = runBlocking { val database: MongoDatabase = client.getDatabase("admin") val command = Document("ping", 1) val result = database.runCommand(command) result.toJson() } }
import com.mongodb.kotlin.client.MongoClient import com.mongodb.kotlin.client.MongoDatabase import org.bson.Document import com.amazonaws.services.lambda.runtime.Context import com.amazonaws.services.lambda.runtime.RequestHandler class ExampleAwsLambdaHandler : RequestHandler<String, String> { private val client: MongoClient = MongoClient.create(System.getenv("MONGODB_URI")) override fun handleRequest(input: String, context: Context): String { val database: MongoDatabase = client.getDatabase("admin") val command = Document("ping", 1) val result = database.runCommand(command) return result.toJson() } }
AWS IAM 身份验证
const { MongoClient } = require('mongodb'); // Get the URI for the cluster then set AWS_ACCESS_KEY_ID as the username in the // URI and AWS_SECRET_ACCESS_KEY as the password, then set the appropriate auth // options. Note that MongoClient now auto-connects so no need to store the connect() // promise anywhere and reference it. const client = new MongoClient(process.env.MONGODB_URI, { auth: { username: process.env.AWS_ACCESS_KEY_ID, password: process.env.AWS_SECRET_ACCESS_KEY }, authSource: '$external', authMechanism: 'MONGODB-AWS' }); module.exports.handler = async function () { const databases = await client.db('admin').command({ listDatabases: 1 }); return { statusCode: 200, databases: databases }; };
其他身份验证
const { MongoClient } = require('mongodb'); // MongoClient now auto-connects so no need to store the connect() // promise anywhere and reference it. const client = new MongoClient(process.env.MONGODB_URI); module.exports.handler = async function () { const databases = await client.db('admin').command({ listDatabases: 1 }); return { statusCode: 200, databases: databases }; };
设置Amazon Web Services档案
配置 AWS IAM身份验证后,从Atlas用户界面复制连接字符串。从连接字符串中删除 <AWS access key>:<AWS secret key> 部分,因为驱动程序会从环境变量中读取这些凭证。在项目的 serverless.yml文件中,将 MONGODB_URI 环境变量设立为此修改后的连接字符串,如以下代码所示:
provider: environment: MONGODB_URI: "<connection string without credentials>"
定义Lambda函数
将 index.php文件的内容替换为以下代码:
require 'vendor/autoload.php'; use MongoDB\Client; // Create the MongoDB client outside the handler function $client = new Client(getenv('MONGODB_URI')); function handler($event, $context) { global $client; $database = $client->getDatabase('admin'); $result = $database->command(['ping' => 1]); return [ 'statusCode' => 200, 'body' => json_encode($result) ]; }
部署应用程序
要部署应用程序,运行以下命令:
serverless deploy
import os from pymongo import MongoClient client = MongoClient(host=os.environ["MONGODB_URI"]) def lambda_handler(event, context): return client.db.command("ping")
AWS IAM 身份验证
# Require the driver library. require "mongo" # Create a Mongo::Client instance using AWS IAM authentication. # CRITICAL: You must create the client instance outside the handler # so that the client can be reused across function invocations. client = Mongo::Client.new([ENV.fetch("MONGODB_HOST")], auth_mech: :aws, user: ENV.fetch("AWS_ACCESS_KEY_ID"), password: ENV.fetch("AWS_SECRET_ACCESS_KEY"), auth_mech_properties: { aws_session_token: ENV.fetch("AWS_SESSION_TOKEN"), }, database: ENV.fetch("MONGODB_DATABASE")) def lambda_handler(event:, context:) # Use the client to return the name of the configured database. client.database.name end
其他身份验证
# Require the driver library. require "mongo" # Create a Mongo::Client instance. # CRITICAL: You must create the client instance outside the handler # so that the client can be reused across function invocations. client = Mongo::Client.new(ENV.fetch("MONGODB_URI")) def lambda_handler(event:, context:) # Use the client to return the name of the configured database. client.database.name end
AWS IAM 身份验证
use lambda_runtime::{service_fn, LambdaEvent}; use mongodb::{ bson::doc, options::{AuthMechanism, ClientOptions, Credential}, Client, }; use serde_json::Value; use tokio::sync::OnceCell; // Initialize a global static MongoDB Client with AWS authentication. The following environment // variables should also be set: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and, optionally, // AWS_SESSION_TOKEN. static MONGODB_CLIENT: OnceCell<Client> = OnceCell::const_new(); async fn get_mongodb_client() -> &'static Client { MONGODB_CLIENT .get_or_init(|| async { let uri = std::env::var("MONGODB_URI") .expect("MONGODB_URI must be set to the URI of the MongoDB deployment"); let mut options = ClientOptions::parse(&uri) .await .expect("Failed to parse options from URI"); let credential = Credential::builder() .mechanism(AuthMechanism::MongoDbAws) .build(); options.credential = Some(credential); Client::with_options(options).expect("Failed to create MongoDB Client") }) .await } // Runs a ping operation on the "db" database and returns the response. async fn handler(_: LambdaEvent<Value>) -> Result<Value, lambda_runtime::Error> { let client = get_mongodb_client().await; let response = client .database("db") .run_command(doc! { "ping": 1 }) .await?; let json = serde_json::to_value(response)?; Ok(json) } async fn main() -> Result<(), lambda_runtime::Error> { let service = service_fn(handler); lambda_runtime::run(service).await?; Ok(()) }
其他身份验证
use lambda_runtime::{service_fn, LambdaEvent}; use mongodb::{bson::doc, Client}; use serde_json::Value; use tokio::sync::OnceCell; // Initialize a global static MongoDB Client. static MONGODB_CLIENT: OnceCell<Client> = OnceCell::const_new(); async fn get_mongodb_client() -> &'static Client { MONGODB_CLIENT .get_or_init(|| async { let uri = std::env::var("MONGODB_URI") .expect("MONGODB_URI must be set to the URI of the MongoDB deployment"); Client::with_uri_str(uri) .await .expect("Failed to create MongoDB Client") }) .await } // Runs a ping operation on the "db" database and returns the response. async fn handler(_: LambdaEvent<Value>) -> Result<Value, lambda_runtime::Error> { let client = get_mongodb_client().await; let response = client .database("db") .run_command(doc! { "ping": 1 }) .await?; let json = serde_json::to_value(response)?; Ok(json) } async fn main() -> Result<(), lambda_runtime::Error> { let service = service_fn(handler); lambda_runtime::run(service).await?; Ok(()) }
import org.mongodb.scala.{MongoClient, Document} import scala.concurrent.Await import scala.concurrent.duration.Duration import com.amazonaws.services.lambda.runtime.Context import com.amazonaws.services.lambda.runtime.RequestHandler class ExampleAwsLambdaHandler extends RequestHandler[String, String] { private val client: MongoClient = MongoClient(System.getenv("MONGODB_URI")) override def handleRequest(input: String, context: Context): String = { val database = client.getDatabase("admin") val command = Document("ping" -> 1) val futureResult = database.runCommand(command).toFuture() val result = Await.result(futureResult, Duration(30, "second")) result.toJson() } }