iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 25
1

各位好這個系列到目前為止我們用 Rust + Actix + Diesel 的架構已經做了下面的事情,

  • 透過 cli 呼叫 python 的深度學習程式把中文翻譯成英文
  • 撰寫 restful 的 api 給翻譯程式使用
  • 完成 Diesel 的 tutorial
  • 改寫 Diesel 的範例為 resful api

那麼今天就要來用試試看使用 r2d2 改寫 DB 的 connection 進一步優化我們的 web service。

添加依賴

筆者研究了一下發現原來 Diesel 本身就有整合 r2d2 所以我們在添加依賴的時候在 feature 把他加進來即可,

diesel = { version = "1.0.0", features = ["postgres", "r2d2"] }

修改 establish_connection

首先 r2d2 官方的 example 很簡單如下,

use std::thread;

extern crate r2d2;
extern crate r2d2_foodb;

fn main() {
    // 新增主要的 pool
    let manager = r2d2_foodb::FooConnectionManager::new("localhost:1234");
    let pool = r2d2::Pool::builder()
        // max size 很重要尤其是如果你的 cpu 線程夠多的時候很容易就會超出 postgresql 的最大連線數
        // 計算方式為 workers * max_size
        .max_size(15)
        .build(manager)
        .unwrap();

    // 這邊只是示例用實際的程式碼只需要 pool 即可
    for _ in 0..20 {
        let pool = pool.clone();
        thread::spawn(move || {
            let conn = pool.get().unwrap();
            // use the connection
            // it will be returned to the pool when it falls out of scope.
        })
    }
}

這邊要注意的就是 max_size ,筆者我踩到的這個雷是因為 actix 預設就會執行跟你 CPU 線程數一樣多的 thread,而這個數值可以在啟動 server 時看到,如下圖。

https://ithelp.ithome.com.tw/upload/images/20191011/20119807MaVgR9l089.png

而 r2d2 的 pool 又會在單的線程上開啟多個連線因此當 workers 越多越容易超過 postgresql 預設的最大連線數(100),其算法為 workers * max_size。

若是超過的話就會顯示像這樣的錯誤,

https://ithelp.ithome.com.tw/upload/images/20191011/20119807CNgfelUZE5.png

那麼我們繼續來修改原本的 establish_connection 吧,

src/lib.rs

use diesel::r2d2::{ ConnectionManager, Pool };

pub type PgPool = Pool<ConnectionManager<PgConnection>>;

pub fn establish_connection() -> PgPool {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");
    let manager = ConnectionManager::<PgConnection>::new(database_url);
    Pool::builder().max_size(8).build(manager).expect("Failed to create pool.")
}

這裏我們定義了 PgPool 的 type,另外就是改寫了 establish_connection 也就是跟上面的範例一樣並且在最後把 Pool 回傳。

修改 main

那麼接著我們就來調整 main 這支程式,首先我們先把剛剛的 establish_connection 加在 App Data 裡面這樣我們就可以讓 api 取得 pool。

src/main.rs

fn main() {
    let mut server = HttpServer::new(
        || App::new()
            .data(establish_connection())
}

然後我們就可以把 api 改寫成這樣,

#[post("/post")]
fn create_post_handler(pool: web::Data<PgPool>, params: web::Json<NewPost>) -> Result<HttpResponse> {
    let result = create_post(&pool.get().unwrap(), params.0);

    Ok(HttpResponse::Ok().json(result))
}

這樣就大功告成了,是不是很簡單呢?

完整專案程式看這邊

最後一樣有問題歡迎發問

/images/emoticon/emoticon07.gif

參考網址

practical-rust-web-development-connection-pool


上一篇
[Day 24] Rust Actix PART5
下一篇
[Day 26] Rust Packages and Crates (實作 Actix 後的說明以及補充 PART1)
系列文
WebAssembly + Rust 的前端應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言