Ryan Bates在this episode中讨论推送通知时提到了Postgres的监听/通知功能,但我还没有找到任何关于如何在我的rails应用程序中实现监听/通知的提示.
下面是pg
适配器内部wait_for_notify函数的文档,但我不知道它的具体用途.
我们需要直接点击pg
适配器的connection
变量吗?
Ryan Bates在this episode中讨论推送通知时提到了Postgres的监听/通知功能,但我还没有找到任何关于如何在我的rails应用程序中实现监听/通知的提示.
下面是pg
适配器内部wait_for_notify函数的文档,但我不知道它的具体用途.
我们需要直接点击pg
适配器的connection
变量吗?
使用wait_for_notify
方法可以找到正确的位置,但由于ActiveRecord显然没有提供使用它的API,因此需要找到ActiveRecord用来与Postgre对话的底层PG::Connection对象(或者其中一个,如果您正在运行多线程设置).
一旦建立了连接,只需执行所需的LISTEN
条语句,然后将一个块(以及可选的超时时间)传递到wait_for_notify
.请注意,这将阻止当前线程,并独占Postgres连接,直到达到超时或出现NOTIFY
(因此,您不希望在web请求中这样做).当另一个进程在您正在收听的其中一个频道上发出NOTIFY
时,将使用三个参数调用该块——通知的频道、触发NOTIFY
的Postgres后端的pid,以及伴随NOTIFY
的有效负载(如果有).
我已经有一段时间没有使用ActiveRecord了,所以可能有一种更干净的方法来实现这一点,但在4.0.0中似乎可以正常工作.beta1:
# Be sure to check out a connection, so we stay thread-safe.
ActiveRecord::Base.connection_pool.with_connection do |connection|
# connection is the ActiveRecord::ConnectionAdapters::PostgreSQLAdapter object
conn = connection.instance_variable_get(:@connection)
# conn is the underlying PG::Connection object, and exposes #wait_for_notify
begin
conn.async_exec "LISTEN channel1"
conn.async_exec "LISTEN channel2"
# This will block until a NOTIFY is issued on one of these two channels.
conn.wait_for_notify do |channel, pid, payload|
puts "Received a NOTIFY on channel #{channel}"
puts "from PG backend #{pid}"
puts "saying #{payload}"
end
# Note that you'll need to call wait_for_notify again if you want to pick
# up further notifications. This time, bail out if we don't get a
# notification within half a second.
conn.wait_for_notify(0.5) do |channel, pid, payload|
puts "Received a second NOTIFY on channel #{channel}"
puts "from PG backend #{pid}"
puts "saying #{payload}"
end
ensure
# Don't want the connection to still be listening once we return
# it to the pool - could result in weird behavior for the next
# thread to check it out.
conn.async_exec "UNLISTEN *"
end
end
有关更一般用法的示例,请参见Sequel's implementation.
Edit to add:下面是对发生的事情的另一个描述.这可能不是幕后的确切实现,但它似乎足够好地描述了行为.
Postgres保留每个连接的通知列表.当你使用一个连接来执行LISTEN channel_name
时,你告诉Postgres该通道上的任何通知都应该被推送到该连接的列表中(多个连接可以监听同一个通道,因此一个通知可能会被推送到多个列表中).一个连接可以同时连接到多个通道,其中任何一个通道的通知都会被推送到同一个列表中.
wait_for_notify
所做的是从连接列表中弹出最早的通知,并将其信息传递给块——或者,如果列表为空,则Hibernate ,直到通知可用并对其执行相同操作(或者直到达到超时,在这种情况下,它只返回nil).由于wait_for_notify
只处理一个通知,如果你想处理多个通知,你必须反复调用它.
当您UNLISTEN channel_name
或UNLISTEN *
时,Postgres将停止将这些通知推送到您连接的列表中,但已经推送到该列表中的通知将保留在那里,并且在下次调用时等待通知仍将返回它们.这可能会导致一个问题,即在wait_for_notify
之后但在UNLISTEN
之前累积的通知仍然存在,并且在另一个线程判断该连接时仍然存在.在这种情况下,在UNLISTEN
之后,你可能想在短时间内拨打wait_for_notify
,直到它返回零.但是,除非你为了许多不同的目的大量使用LISTEN
和NOTIFY
,否则这可能不值得担心.
我在上面的Sequel实现中添加了一个更好的链接,我建议大家看看.这很简单.