2015-04-05 55 views
4

我正在玩不同的異步HTTP服務器,看他們如何處理多個同時連接。要強制執行耗時的I/O操作,我使用PostgreSQL函數來模擬耗時的數據庫查詢。這裏是比如我做什麼用的Node.js:執行異步數據庫查詢的HTTP服務器的最小示例?

var http = require('http'); 
var pg = require('pg'); 
var conString = "postgres://al:[email protected]/al"; 
/* SQL query that takes a long time to complete */ 
var slowQuery = 'SELECT 42 as number, pg_sleep(0.300);'; 

var server = http.createServer(function(req, res) { 
    pg.connect(conString, function(err, client, done) { 
    client.query(slowQuery, [], function(err, result) { 
     done(); 
     res.writeHead(200, {'content-type': 'text/plain'}); 
     res.end("Result: " + result.rows[0].number); 
    }); 
    }); 
}) 

console.log("Serve http://127.0.0.1:3001/") 
server.listen(3001) 

所以這個,做一個SQL查詢到300毫秒,並返回一個響應一個非常簡單的請求處理程序。當我嘗試對它進行基準測試時,我得到以下結果:

$ ab -n 20 -c 10 http://127.0.0.1:3001/ 
Time taken for tests: 0.678 seconds 
Complete requests:  20 
Requests per second: 29.49 [#/sec] (mean) 
Time per request:  339.116 [ms] (mean) 

這清楚地表明請求是並行執行的。每個請求需要300ms才能完成,因爲我們有兩批10個請求並行執行,所以總共需要600ms。

現在我正試圖用Elixir做同樣的事情,因爲我聽說它的透明異步I/O。這裏是我的幼稚的做法:

defmodule Toto do 
    import Plug.Conn 

    def init(options) do 
    {:ok, pid} = Postgrex.Connection.start_link(
     username: "al", password: "al", database: "al") 
    options ++ [pid: pid] 
    end 

    def call(conn, opts) do 
    sql = "SELECT 42, pg_sleep(0.300);" 
    result = Postgrex.Connection.query!(opts[:pid], sql, []) 
    [{value, _}] = result.rows 
    conn 
    |> put_resp_content_type("text/plain") 
    |> send_resp(200, "Result: #{value}") 
    end 
end 

在情況下,可能相關的,這裏是我的上司:

defmodule Toto.Supervisor do 
    use Application 

    def start(type, args) do 
    import Supervisor.Spec, warn: false 

    children = [ 
     worker(Plug.Adapters.Cowboy, [Toto, []], function: :http), 
    ] 
    opts = [strategy: :one_for_one, name: Toto.Supervisor] 
    Supervisor.start_link(children, opts) 
    end 
end 

正如您所料,這不會給我預期的結果:

$ ab -n 20 -c 10 http://127.0.0.1:4000/ 
Time taken for tests: 6.056 seconds 
Requests per second: 3.30 [#/sec] (mean) 
Time per request:  3028.038 [ms] (mean) 

看起來沒有並行性,請求被一個接一個地處理。我究竟做錯了什麼?

回答

8

Elixir應該完全適用於此設置。不同的是,您的node.js代碼正在爲每個請求創建到數據庫的連接。然而,在你的Elixir代碼中,init會被調用一次(而不是每個請求!),所以你最終只需一個進程向所有請求發送查詢到Postgres,然後就成爲你的瓶頸。

最簡單的解決方案是將連接遷移到Postgres從initcall。但是,我建議您將也設置到數據庫連接池。您也可以玩pool configuration以獲得最佳效果。

5

UPDATE這只是測試代碼,如果你想做這樣的事情,請參閱@ AlexMarandon的Ecto pool答案。

我只是在玩移動連接設置爲何塞建議:

defmodule Toto do 
    import Plug.Conn 

    def init(options) do 
    options 
    end 

    def call(conn, opts) do 
    { :ok, pid } = Postgrex.Connection.start_link(username: "chris", password: "", database: "ecto_test") 
    sql = "SELECT 42, pg_sleep(0.300);" 
    result = Postgrex.Connection.query!(pid, sql, []) 
    [{value, _}] = result.rows 
    conn 
    |> put_resp_content_type("text/plain") 
    |> send_resp(200, "Result: #{value}") 
    end 
end 

有了結果:

% ab -n 20 -c 10 http://127.0.0.1:4000/ 
Time taken for tests: 0.832 seconds 
Requests per second: 24.05 [#/sec] (mean) 
Time per request:  415.818 [ms] (mean) 
+0

酷!請記住,建立連接池是最好的方式,因爲建立與數據庫的連接並不便宜。你也會有更好的結果! – 2015-04-06 08:49:49

+0

其實這段代碼創建連接,但從來沒有關閉它,所以它停止工作後一段時間:) – 2015-04-06 18:54:52

+0

@AlexMarandon - 是的,你應該真正使用Ecto池,因爲何塞說,所以我會upvote你的答案。這僅僅是爲了演示的目的,但很好的電話指出,以防有人過來後,沒有意識到這一點。 – chrismcg 2015-04-07 09:45:00

3

這裏是我想出了以下José's answer代碼:

defmodule Toto do 
    import Plug.Conn 

    def init(options) do 
    options 
    end 

    def call(conn, _opts) do 
    sql = "SELECT 42, pg_sleep(0.300);" 
    result = Ecto.Adapters.SQL.query(Repo, sql, []) 
    [{value, _}] = result.rows 
    conn 
    |> put_resp_content_type("text/plain") 
    |> send_resp(200, "Result: #{value}") 
    end 
end 

爲此,我們需要聲明一個回購模塊:

defmodule Repo do 
    use Ecto.Repo, otp_app: :toto 
end 

,並開始了監督員回購:

defmodule Toto.Supervisor do 
    use Application 

    def start(type, args) do 
    import Supervisor.Spec, warn: false 

    children = [ 
     worker(Plug.Adapters.Cowboy, [Toto, []], function: :http), 
     worker(Repo, []) 
    ] 
    opts = [strategy: :one_for_one, name: Toto.Supervisor] 
    Supervisor.start_link(children, opts) 
    end 
end 

何塞提到的,我通過調整配置一點得到了最好的性能:

config :toto, Repo, 
    adapter: Ecto.Adapters.Postgres, 
    database: "al", 
    username: "al", 
    password: "al", 
    size: 10, 
    lazy: false 

下面是結果我的基準測試(經過幾次運行後,游泳池有時間「預熱」),默認配置爲:

$ ab -n 20 -c 10 http://127.0.0.1:4000/ 
Time taken for tests: 0.874 seconds 
Requests per second: 22.89 [#/sec] (mean) 
Time per request:  436.890 [ms] (mean) 

這裏是size: 10lazy: false結果:

$ ab -n 20 -c 10 http://127.0.0.1:4000/ 
Time taken for tests: 0.619 seconds 
Requests per second: 32.30 [#/sec] (mean) 
Time per request:  309.564 [ms] (mean)