Ryan Bates 在本集中讨论推送通知时提到了 Postgres 的 LISTEN/NOTIFY 功能,但我无法找到任何关于如何在我的 rails 应用程序中实现 LISTEN/NOTIFY 的提示。
这是适配器内部wait_for_notify函数的文档pg
,但我无法弄清楚它到底是做什么/设计的。
我们是否需要直接利用适配器的connection
变量?pg
Ryan Bates 在本集中讨论推送通知时提到了 Postgres 的 LISTEN/NOTIFY 功能,但我无法找到任何关于如何在我的 rails 应用程序中实现 LISTEN/NOTIFY 的提示。
这是适配器内部wait_for_notify函数的文档pg
,但我无法弄清楚它到底是做什么/设计的。
我们是否需要直接利用适配器的connection
变量?pg
您正在使用该wait_for_notify
方法寻找正确的位置,但是由于 ActiveRecord 显然没有提供使用它的 API,因此您需要获取底层 PG::Connection 对象(或其中一个,如果您是ActiveRecord 用来与 Postgres 对话的多线程设置。
建立连接后,只需执行所需的任何LISTEN
语句,然后将一个块(和一个可选的超时时间)传递给wait_for_notify
. 请注意,这将阻塞当前线程并独占 Postgres 连接,直到达到超时或NOTIFY
发生 a (例如,您不希望在 Web 请求中执行此操作)。当另一个进程在您正在收听的一个频道上发出 aNOTIFY
时,将使用三个参数调用该块 - 通知的频道、触发 的 Postgres 后端的 pidNOTIFY
以及伴随的有效负载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 的实现。
编辑添加:这是对正在发生的事情的另一种描述。这可能不是幕后的确切实现,但它似乎很好地描述了行为。
Postgres 为每个连接保留一个通知列表。当您使用连接执行LISTEN channel_name
时,您是在告诉 Postgres 该通道上的任何通知都应推送到此连接的列表(多个连接可以侦听同一个通道,因此单个通知最终可能会被推送到多个列表)。一个连接可以同时连接LISTEN
到多个频道,并且任何一个频道的通知都会被推送到同一个列表中。
什么wait_for_notify
是从连接列表中弹出最旧的通知并将其信息传递给块 - 或者,如果列表为空,则休眠直到通知变为可用并对其执行相同操作(或直到达到超时,其中如果它只返回 nil)。由于wait_for_notify
只处理一个通知,如果你想处理多个通知,你将不得不重复调用它。
当 youUNLISTEN channel_name
或UNLISTEN *
时,Postgres 将停止将这些通知推送到您的连接列表,但已经推送到该列表的通知将保留在那里,并且 wait_for_notify 在下次调用时仍会返回它们。wait_for_notify
这可能会导致一个问题,即在之后但之前累积的通知UNLISTEN
仍然存在,并且在另一个线程检查该连接时仍然存在。在这种情况下,UNLISTEN
您可能希望以wait_for_notify
短暂的超时调用,直到它返回 nil。但是,除非您大量使用LISTEN
并NOTIFY
用于许多不同的目的,否则可能不值得担心。
我在上面添加了一个更好的 Sequel 实现链接,我建议您查看它。这很简单。