我正在使用Goliath(由eventmachine提供支持)和postgres gem pg
,目前我正以阻塞的方式使用pg gem:conn.exec('SELECT * FROM products')
(例如)我想知道是否有更好的方法连接到postgres数据库?
答案 0 :(得分:15)
pg
库为PostgreSQL的异步API提供全面支持。我添加了an example如何将其用于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
我建议您阅读PostgreSQL手册的PQconnectStart函数和Asynchronous Command Processing部分的文档,然后将其与上面的示例进行比较。
之前我没有使用过EventMachine,但是如果它允许你注册套接字和回调它变得可读/可写,我认为将数据库调用集成到它中相当容易。
我一直想使用Ilya Grigorik's article on using Fibers to clean up evented code中的想法来使异步API更容易使用,但这还有很长的路要走。如果您对自己感兴趣/有动力,我会a ticket open跟踪它。
答案 1 :(得分:3)
是的,你可以从goliath以非阻塞的方式访问postgres。我有同样的需要,并将这个概念证明放在一起:https://github.com/levicook/goliath-postgres-spike
答案 2 :(得分:1)
我(不再)非常熟悉Pg,但我没有听说过任何流行的数据库都可以异步连接。因此,您仍需要在查询期间保持与数据库的连接。因此,您仍然需要阻止堆栈中的某些位置。
根据您的应用程序,您可能已经采用了最佳方式。
但是当你处理某种轮询应用程序时(同一个客户端在短时间内发送大量请求)并且将响应输出更为重要,即使它是空的,那么你可以编写一个ruby { {1}}或flull烧结线程或长期存在的进程,代理查询数据库并缓存结果。
例如:来自客户端A的请求.Goliath应用程序使用一些唯一ID处理对数据库进程的查询,并使用'no data yet'响应查询。数据库进程完成查询并将结果保存到具有ID的缓存中。当下一个请求来自同一个客户端时,Goliath发现它已经有等待的查询结果,从缓存中删除结果并响应客户端。同时,它使用数据库进程安排下一个查询,以便更快地准备好。如果下一个请求在最后一个请求完成之前进入,则不会安排新查询(不会将查询相乘)。
这样,您的响应快速且无阻塞,同时仍然可以从DB ASAP提供新数据。当然,它们可能与实际数据有点不同步,但同样,根据应用程序的不同,这可能不是问题。
答案 3 :(得分:1)
我们的想法是使用与数据库(Postgresql)的异步适配器和一个公平的Web服务器(Goliath)来获得性能。 Mike Perham去年为Rails 2.3写了PG activerecord adaptor。也许你可以使用它。
另一个例子是,Ilya Grigorik发布了this demo异步Rails堆栈。在这种情况下,事件服务器是Thin,数据库是Mysql。安装演示并尝试使用和不使用EM感知驱动程序的基准测试。差别很大。