性能考虑因素
Overview
在本指南中,您可以了解如何优化 Rust 驱动程序的性能。 要连接到 MongoDB,您必须创建一个 Client
实例。 Client
实例会自动处理连接的大多数方面,例如发现服务器拓扑结构、监控连接以及维护内部连接池。本指南介绍了配置和使用Client
实例的最佳实践。
本指南包括以下部分:
客户端生命周期
我们建议您在会话和操作中重复使用客户端。 您可以使用同一Client
实例来执行多个任务,因为Client
类型可以安全地由多个线程并发使用。 为每个请求创建新的Client
实例会导致性能下降。
以下代码创建一个接受指向现有Client
实例的指针的方法,该实例允许您使用同一客户端执行多个请求:
// ... Create a client earlier in your code async fn make_request(client: &Client) -> Result<(), Box<dyn Error>> { // Use the client to perform operations Ok(()) }
连接池
对于 MongoDB 拓扑结构中的每个服务器,每个 Client
实例都有一个内置连接池。连接池按需打开套接字以支持应用程序中对 MongoDB 的并发请求。
Client
的默认配置适用于大多数应用程序。 以下代码展示了如何使用默认连接设置创建客户端:
let client = Client::with_uri_str("<connection string>").await?;
或者,您可以调整连接池以最好地满足应用程序的需求并优化性能。 有关如何自定义连接设置的更多信息,请参阅本指南的以下小节:
配置最大池大小
每个连接池的最大大小由max_pool_size
选项设置,默认为10
。 如果正在使用的服务器连接数达到max_pool_size
值,则对该服务器的下一个请求将等待,直到出现可用连接。
除了支持应用程序请求所需的套接字之外,每个Client
实例还为 MongoDB 拓扑结构中的每个服务器再打开两个套接字,用于监控服务器的状态。 例如,连接到三节点副本集的客户端会打开六个监控套接字。 如果应用程序使用max_pool_size
的默认设置,并且仅查询主(默认)节点,则连接池中的总连接数最多为 16。如果应用程序使用读取偏好来查询从节点,则这些连接池会增长,总连接数可为 36。
要在一个进程中支持大量并发 MongoDB 请求,可以增加max_pool_size
选项的值。 以下代码演示了如何在实例化Client
时为max_pool_size
指定值:
let mut client_options = ClientOptions::parse("<connection string>").await?; client_options.max_pool_size = Some(20); let client = Client::with_options(client_options)?;
配置并发连接选项
连接池的速率受到限制。 max_connecting
选项决定连接池在任何时候可以并行创建的连接数量。 例如,如果max_connecting
的值为默认值2
,则仅当出现以下情况之一时,尝试同时签出连接的第三个请求才会成功:
连接池已完成连接创建,并且池中的连接数小于或等于
max_pool_size
的值。现有连接会重新检入池中。
由于对连接创建的速率限制,驱动程序重用现有连接的能力得到提高。
您可以使用min_pool_size
选项设置每个服务器的最小并发连接数,默认为0
。 驱动程序使用这个数量的套接字初始化连接池。 如果套接字已关闭,并且正在使用和空闲的套接字总数低于最小值,则连接池将打开更多套接字,直到达到最小值。
以下代码在实例化Client
时设置max_connecting
和min_pool_size
选项:
let mut client_options = ClientOptions::parse("<connection string>").await?; client_options.max_connecting = Some(3); client_options.min_pool_size = Some(1); let client = Client::with_options(client_options)?;
配置最大空闲时间
您可以通过设置 max_idle_time
选项来设置一个连接在连接池中保持空闲状态的最大时间量。一旦连接持续处于空闲状态的时间达到 max_idle_time
中指定的时间,连接池就会删除并替换该连接。此选项默认为 0
,即无限制。
在应用程序中的任何点调用Client::shutdown()
方法时,驱动程序都会关闭所有空闲套接字,并关闭所有正在使用的套接字,因为它们会返回到池中。调用Client::shutdown()
仅关闭非活动套接字,因此您无法使用此方法中断或终止任何正在进行的操作。 仅当进程完成时,驱动程序才会关闭这些套接字。
实例化Client
时,以下代码将max_idle_time
选项的值设置为90
秒:
let mut client_options = ClientOptions::parse("<connection string>").await?; client_options.max_idle_time = Some(Duration::new(90, 0)); let client = Client::with_options(client_options)?;
并行度
如果可以运行并行数据操作,则可以通过运行异步并发任务来优化性能。 以下代码使用tokio::task
模块中的spawn()
方法创建单独的并发任务来执行插入操作:
let client = Client::with_uri_str("<connection string>").await?; let data = doc! { "title": "1984", "author": "George Orwell" }; for i in 0..5 { let client_ref = client.clone(); let data_ref = data.clone(); task::spawn(async move { let collection = client_ref .database("items") .collection::<Document>(&format!("coll{}", i)); collection.insert_one(data_ref).await }); }
运行时
Client
实例绑定到您在其中创建它的tokio
或async-std
运行时的实例。 如果使用Client
实例在不同的运行时上执行操作,则可能会遇到意外行为或故障。
如果使用tokio
或async_std
包中的test
助手宏来测试您的应用程序,您可能会意外在与预期不同的运行时运行时运行操作。 这是因为这些辅助宏会为每个测试创建一个新的运行时。 但是,您可以使用以下策略之一来避免此问题:
将运行时附加到
Client
实例,而不使用test
辅助宏。为每个
async
测试创建一个新的Client
实例。
此示例遵循第一个策略,并创建一个仅用于测试的全局运行时。 在以下代码中, test_list_dbs()
方法使用手动连接到此运行时的客户端来列出部署中的数据库:
use tokio::runtime::Runtime; use once_cell::sync::Lazy; static CLIENT_RUNTIME: Lazy<(Client, Runtime)> = Lazy::new(|| { let rt = Runtime::new().unwrap(); let client = rt.block_on(async { Client::with_uri_str("<connection string>").await.unwrap() }); (client, rt) }); fn test_list_dbs() -> Result<(), Box<dyn Error>> { let (client, rt) = &*CLIENT_RUNTIME; rt.block_on(async { client.list_database_names().await })?; Ok(()) }
在实施第二种策略时,以下代码为使用tokio::test
的每次测试运行创建一个新的实例Client
,确保运行时之间不会出现意外的交互:
async fn test_list_dbs() -> Result<(), Box<dyn Error>> { let client = Client::with_uri_str("<connection string>").await?; client.list_database_names().await?; Ok(()) }
更多信息
要学习;了解有关连接到MongoDB的更多信息,请参阅连接指南。
要了解有关 Rust 驱动程序可用运行时的更多信息,请参阅异步和同步 API 指南。
API 文档
spawn() 在
tokio::task
模块中