9

我正在使用Goliath(由 eventmachine 提供支持)和 postgres gem pg,目前我正在以阻塞方式使用 pg gem:(conn.exec('SELECT * FROM products')例如)我想知道是否有更好的方法来连接到 postgres数据库?

4

4 回答 4

16

pg库为 PostgreSQL 的异步 API 提供全面支持。我在目录中添加了一个如何使用它的示例samples/

#!/usr/bin/env ruby

require 'pg'

# This is a example of how to use the asynchronous API to query the
# server without blocking other threads. It's intentionally low-level;
# if you hooked up the PGconn#socket to some kind of reactor, you
# could make this much nicer.

TIMEOUT = 5.0 # seconds to wait for an async operation to complete
CONN_OPTS = {
    :host     => 'localhost',
    :dbname   => 'test',
    :user     => 'jrandom',
    :password => 'banks!stealUR$',
}

# Print 'x' continuously to demonstrate that other threads aren't
# blocked while waiting for the connection, for the query to be sent,
# for results, etc. You might want to sleep inside the loop or 
# comment this out entirely for cleaner output.
progress_thread = Thread.new { loop { print 'x' } }

# Output progress messages
def output_progress( msg )
    puts "\n>>> #{msg}\n"
end

# Start the connection
output_progress "Starting connection..."
conn = PGconn.connect_start( CONN_OPTS ) or 
    abort "Unable to create a new connection!"
abort "Connection failed: %s" % [ conn.error_message ] if
    conn.status == PGconn::CONNECTION_BAD

# Now grab a reference to the underlying socket so we know when the
# connection is established
socket = IO.for_fd( conn.socket )

# Track the progress of the connection, waiting for the socket to 
# become readable/writable before polling it
poll_status = PGconn::PGRES_POLLING_WRITING
until poll_status == PGconn::PGRES_POLLING_OK ||
      poll_status == PGconn::PGRES_POLLING_FAILED

    # If the socket needs to read, wait 'til it becomes readable to
    # poll again
    case poll_status
    when PGconn::PGRES_POLLING_READING
        output_progress "  waiting for socket to become readable"
        select( [socket], nil, nil, TIMEOUT ) or
            raise "Asynchronous connection timed out!"

    # ...and the same for when the socket needs to write
    when PGconn::PGRES_POLLING_WRITING
        output_progress "  waiting for socket to become writable"
        select( nil, [socket], nil, TIMEOUT ) or
            raise "Asynchronous connection timed out!"
    end

    # Output a status message about the progress
    case conn.status
    when PGconn::CONNECTION_STARTED
        output_progress "  waiting for connection to be made."
    when PGconn::CONNECTION_MADE
        output_progress "  connection OK; waiting to send."
    when PGconn::CONNECTION_AWAITING_RESPONSE
        output_progress "  waiting for a response from the server."
    when PGconn::CONNECTION_AUTH_OK
        output_progress "  received authentication; waiting for " +
                        "backend start-up to finish."
    when PGconn::CONNECTION_SSL_STARTUP
        output_progress "  negotiating SSL encryption."
    when PGconn::CONNECTION_SETENV
        output_progress "  negotiating environment-driven " +
                        "parameter settings."
    end

    # Check to see if it's finished or failed yet
    poll_status = conn.connect_poll
end

abort "Connect failed: %s" % [ conn.error_message ] unless 
    conn.status == PGconn::CONNECTION_OK

output_progress "Sending query"
conn.send_query( "SELECT * FROM pg_stat_activity" )

# Fetch results until there aren't any more
loop do
    output_progress "  waiting for a response"

    # Buffer any incoming data on the socket until a full result 
    # is ready. 
    conn.consume_input
    while conn.is_busy
        select( [socket], nil, nil, TIMEOUT ) or
            raise "Timeout waiting for query response."
        conn.consume_input
    end

    # Fetch the next result. If there isn't one, the query is 
    # finished
    result = conn.get_result or break

    puts "\n\nQuery result:\n%p\n" % [ result.values ]
end

output_progress "Done."
conn.finish

if defined?( progress_thread )
    progress_thread.kill
    progress_thread.join
end

我建议您阅读有关PQconnectStart函数的文档和 PostgreSQL 手册的异步命令处理部分,然后将其与上面的示例进行比较。

我以前没有使用过 EventMachine,但是如果它允许您注册一个套接字并在它变得可读/可写时进行回调,我认为将数据库调用集成到其中会相当容易。

我一直打算使用Ilya Grigorik 关于使用 Fibers 清理事件代码以使异步 API 更易于使用的文章中的想法,但这还有一段路要走。如果您有兴趣/有动力自己做,我确实有一张票可以跟踪它。

于 2011-05-19T00:18:18.697 回答
3

是的,您可以从 goliath 以非阻塞方式访问 postgres。我有同样的需求,并将这个概念证明放在一起:https ://github.com/levicook/goliath-postgres-spike

于 2012-03-15T21:38:52.240 回答
1

我(不再)对 Pg 不太熟悉,但我还没有听说任何流行的数据库都可以异步连接。因此,您仍然需要在查询期间保持与数据库的连接。因此,您仍然需要阻止堆栈中的某些位置。

根据您的应用程序,您可能已经在以最好的方式进行操作。

但是当你处理某种轮询应用程序时(同一个客户端在短时间内发送大量请求)并且得到响应更重要,即使它是空的,那么你可以编写一个 rubyFiber​​ 或 Flull 吹过的线程或长期存在并代理对数据库的查询并缓存结果的进程。

例如:来自客户端 A 的请求。Goliath 应用程序使用一些唯一 ID 处理对 DB 进程的查询,并以'no data yet'响应查询。DB 进程完成查询并使用 ID 将结果保存到缓存中。当下一个请求来自同一个客户端时,Goliath 看到它已经有查询结果等待,从缓存中删除结果并响应客户端。同时,它使用 DB 进程安排下一个查询,以便它更快地准备好。如果下一个请求在最后一个请求完成之前进入,则不会安排新的查询(不增加查询)。

通过这种方式,您的响应快速且无阻塞,同时仍能尽快从数据库中提供新数据。当然,它们可能与实际数据有点不同步,但同样,根据应用程序,这可能不是问题。

于 2011-05-05T09:17:40.743 回答
1

这个想法是使用数据库的异步适配器(Postgresql)和事件 Web 服务器(Goliath)来获得性能。Mike Perham去年为 Rails 2.3编写了一个PG activerecord 适配器。也许你可以使用它。

作为另一个例子,Ilya Grigorik 发布了这个异步 Rails 堆栈的演示。在这种情况下,事件服务器是 Thin,数据库是 Mysql。安装演示并尝试使用和不使用 EM 感知驱动程序的基准测试。差异是戏剧性的。

于 2011-05-05T15:34:28.987 回答