2015-04-05 17 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)