当前位置:嗨网首页>书籍在线阅读

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软件包与数据库交互。接下来我们将对它进行探讨。