05-r2d2连接池
14.4 r2d2连接池
每次发生新事务时,打开和关闭数据库连接会很快成为“瓶颈”。通常,打开数据库连接是一项开销昂贵的操作。这主要是因为双方创建套接字连接时所需的相关TCP握手。如果数据库托管在远程服务器上,那么开销甚至会更高。如果我们可以为发送到数据库的后续请求复用连接,那么可能会大大减少延迟。减少此开销的有效方法是使用数据库连接池。当进程需要更新连接时,将从连接池中为其提取现有连接。当进程完成数据库所需的操作时,此连接句柄将返回连接池以供后续使用。
Rust中有r2d2软件包,它利用特征提供了维护各种数据库连接池的通用方法。它包含处理多种后端的子软件包,并支持PostgreSQL、Redis、MySQL、MongoDB、SQLite,以及一些其他已知的数据库系统。r2d2的体系结构由两部分组成:通用部分和兼容各后端部分。后端代码通过实现r2d2的ManageConnection特征,并为特定后端添加连接管理器来附加到通用部分。该特征如下所示:
pub trait ManageConnection: Send + Sync + 'static {
type Connection: Send + 'static;
type Error: Error + 'static;
fn connect(&self) -> Result<Self::Connection, Self::Error>;
fn is_valid(&self, conn: &mut Self::Connection) -> Result<(),
Self::Error>;
fn has_broken(&self, conn: &mut Self::Connection) -> bool;
}
通过特征定义可知,我们需要指定一个Connection类型,它必须是Send和'static,以及一个Error类型。我们还定义了3种方法:connect、is_valid和has_broken。Connect方法返回来自底层软件包的Connection类型,例如它可以是postgres后端的postgres::Connection类型。Error类型是一个枚举,它指定在连接阶段或检查连接有效性期间可能发生的所有Error场景。
出于演示目的,我们将首先查看如何将连接池连接到PostgreSQL,从而了解如何使用r2d2软件包。我们将对14.3节的代码进行修改以使用连接池,并将从8个线程中执行SQL查询。
这是使用r2d2-postgres后端软件包采用连接池和线程实现的完整代码:
// r2d2_demo/src/main.rs
use std::thread;
use r2d2_postgres::{TlsMode, PostgresConnectionManager};
use std::time::Duration;
const DROP_TABLE: &str = "DROP TABLE IF EXISTS books";
const CREATE_TABLE: &str = "CREATE TABLE IF NOT EXISTS books
(id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
author VARCHAR NOT NULL,
year SERIAL)";
#[derive(Debug)]
struct Book {
id: i32,
title: String,
author: String,
year: i32
}
fn main() {
let manager =
PostgresConnectionManager::new("postgres://postgres:postgres@localhost:5432", TlsMode::None).unwrap();
let pool = r2d2::Pool::new(manager).unwrap();
let conn = pool.get().unwrap();
let _ = conn.execute(DROP_TABLE, &[]).unwrap();
let _ = conn.execute(CREATE_TABLE, &[]).unwrap();
thread::spawn(move || {
let book = Book {
id: 3,
title: "A programmers introduction to mathematics".to_string(),
author: "Dr. Jeremy Kun".to_string(),
year: 2018
};
conn.execute("INSERT INTO books (id, title, author, year) VALUES
($1, $2, $3, $4)",
&[&book.id, &book.title, &book.author,
&book.year]).unwrap();
});
thread::sleep(Duration::from_millis(100));
for _ in 0..8 {
let conn = pool.get().unwrap();
thread::spawn(move || {
for row in &conn.query("SELECT id, title, author, year FROM
books", &[]).unwrap() {
let book = Book {
id: row.get(0),
title: row.get(1),
author: row.get(2),
year: row.get(3)
};
println!("{:?}", book);
}
});
}
}
它比上一个示例中的代码更简单,只是我们生成了8个线程对数据库执行SELECT查询。连接池的大小被配置为8,这意味着SELECT查询线程可以对后续的请求复用连接并发地执行8个查询。
到目前为止,我们主要使用原始SQL查询与Rust中的数据库进行交互。但是,一种更方便的强类型方法是,通过名为diesel的ROM软件包与数据库交互。接下来我们将对它进行探讨。