如何获取通过特定渠道订阅的模型的ID

时间:2019-04-23 01:01:52

标签: ruby-on-rails websocket actioncable

如何获取当前通过特定ActionCable频道订阅的所有ActiveRecord模型的列表?

2 个答案:

答案 0 :(得分:1)

很抱歉给您一个您不想要的答案,但是...:

您没有得到所有已订阅客户的列表。您不应该能够。如果您需要此信息,则可能遇到设计缺陷。

为什么?

发布/订阅范式旨在将这些详细信息抽象化,从而允许水平缩放,让不同节点管理自己的订阅列表。

当然,当您在单个进程上运行单个应用程序时,您可能能够提取此信息-但是当您扩展规模时,使用更多的进程/机器,此信息将被分发,并且将不再可用更多。

示例?

例如,在使用碘的发布/订阅引擎时(有关详细信息,请参见the Ruby iodine WebSocket / HTTP server

  • 每个进程都管理自己的客户列表。

  • 每个进程都是主/根进程中的“客户端”。

  • 每个主/根进程都是Redis服务器中的客户端(假设使用Redis)。

假设您在Heroku上运行了两个碘“ dynos”,每个都有16个工人,然后:

  • Redis每个频道最多看到两个客户端。

  • 两个主进程中的每个进程每个通道最多看到16个客户端。

  • 每个进程仅查看连接到该特定进程的客户端。

如您所见,您所要求的信息在任何地方都不可用。发布/订阅实现分布在不同的计算机上。每个进程/机器仅管理发布/订阅客户端列表的一小部分。

编辑(1)-回答更新的问题

有三种方法可以解决此问题:

  1. 客户端解决方案;

  2. 服务器端解决方案;和

  3. 一种惰性(无效)方法。

作为客户端解决方案,客户端可以注册到全局“服务器通知通道”。当出现“重新认证”消息时,客户端应重新认证,并在其唯一连接上启动唯一令牌生成。

服务器端解决方案需要服务器端连接来侦听全局“服务器通知通道”。然后,连接对象将重新计算身份验证令牌,并将唯一的消息发送给客户端。

惰性无效方法是简单地使所有令牌无效。连接的客户端将保持连接状态(直到关闭浏览器,关闭计算机或退出其应用程序)。建立新连接时,客户端将必须重新认证。

注意(已添加为注释中的讨论内容):

唯一可以解决“雷电”情况的解决方案是惰性/无效解决方案。

任何其他解决方案都会导致网络流量和CPU消耗激增,因为所有连接的客户端都将在相似的时间处理事件。

实施

使用 ActionCable ,客户端解决方案可能更易于实现。它的设计和文档都非常“推送”。他们通常采用客户端处理方法。

上,服务器端订阅仅需要将block传递到client.subscribe method。这样会创建一个特定于客户端的预订,并带有在服务器上运行的事件(而不是发送给客户端的消息)。

根据设计的不同,惰性失效方法可能会损害用户体验,因为他们可能不得不重新输入凭据。

另一方面,惰性失效可能是最安全的,这增加了安全性并减轻了服务器负担。

答案 1 :(得分:0)

警告:请参阅@Myst答案和相关注释。当扩展到单个服务器实例之外时,不建议使用以下答案。

开发和测试环境所需的补丁程序

module ActionCable
  module SubscriptionAdapter
    class SubscriberMap
      def get_subscribers
        @subscribers
      end
    end
   end
end

获取所订购型号ID的代码

pubsub = ActionCable.server.pubsub

if Rails.env.production?
  channel_with_prefix = pubsub.send(:channel_with_prefix, ApplicationMetaChannel.channel_name)
  channels = pubsub.send(:redis_connection).pubsub('channels', "#{channel_with_prefix}:*")
  subscriptions = channels.map do |channel|
    Base64.decode64(channel.match(/^#{Regexp.escape(channel_with_prefix)}:(.*)$/)[1])
  end
else #DEVELOPMENT or Test Environment: Requires patching ActionCable::SubscriptionAdapter::SubscriberMap
  subscribers = pubsub.send(:subscriber_map).get_subscribers.keys

  subscriptions = []
  subscribers.each do |sid|
    next unless sid.split(':').size === 2
    channel_name, encoded_gid = sid.split(':')
    if channel_name === ApplicationMetaChannel.channel_name
      subscriptions << Base64.decode64(encoded_gid)
    end
  end

end

# the GID URI looks like that: gid://<app-name>/<ActiveRecordName>/<id>
gid_uri_pattern = /^gid:\/\/.*\/#{Regexp.escape(SomeModel.name)}\/(\d+)$/
some_model_ids = subscriptions.map do |subscription|
  subscription.match(gid_uri_pattern)
  # compacting because 'subscriptions' include all subscriptions made from ApplicationMetaChannel,
  # not just subscriptions to SomeModel records
end.compact.map { |match| match[1] }