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

Posted

技术标签:

【中文标题】如何获取通过特定频道订阅的模型的 ID【英文标题】:How to get IDs of models subscribed to via a specific channel 【发布时间】:2019-04-23 01:01:52 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

您没有得到所有订阅客户的列表。你应该做不到。如果您需要这些信息,您可能遇到了设计缺陷。

为什么?

发布/订阅范式旨在将这些细节抽象出来,允许以不同节点管理自己的订阅列表的方式进行水平扩展。

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

例子?

例如,使用iodine的pub/sub引擎时(详见the Ruby iodine WebSocket / HTTP server):

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

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

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

假设您在 Heroku 上运行两个碘“dynos”,每个有 16 个工人,那么:

Redis 每个通道最多可以看到两个客户端。

两个主进程中的每一个都可以看到每个通道最多 16 个客户端。

每个进程只能看到连接到该特定进程的客户端。

如您所见,您所要求的信息在任何地方均不可用。 pub/sub 实现分布在不同的机器上。每个进程/机器只管理 pub/sub 客户端列表的一小部分。

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

解决这个问题有三种可能的方法:

    客户端解决方案;

    服务器端解决方案;和

    一种惰性(失效)方法。

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

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

lazy-invalidation 方法是简单地使所有令牌无效。连接的客户端将保持连接状态(直到他们关闭浏览器、关闭他们的机器或退出他们的应用程序)。建立新连接时,客户端必须重新进行身份验证。

注意(如 cmets 中所述添加):

解决“雷鸣般的羊群”场景的唯一解决方案是惰性/无效解决方案。

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

实施

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

上,服务器端订阅只需将block 传递给client.subscribe method。这将创建一个特定于客户端的订阅,其中包含在服务器上运行的事件(而不是发送到客户端的消息)。

惰性失效方法可能会损害用户体验,具体取决于设计,因为他们可能必须重新输入凭据。

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

【讨论】:

@FavouriteOnwuemene - 我更新了我的答案。我希望这会有所帮助。 考虑到 Redis 拥有所有订阅者信息,难道不应该直接从 Redis 获取这些信息吗? @FavouriteOnwuemene - Redis 不(或不应该)保存所有订阅者信息。如果是这样,则发布/订阅系统过于集中而无法正确扩展。在我的示例中,Redis 拥有两个订阅者,而不是所有客户端……但是,您提出了一个有趣的观点。 Redis 可能知道所有 channels 仍然仅限于单个实例(未集群 - 否则可能会很棘手)。在您的应用程序的这个阶段,频道名称可能会用于使用PUBSUB CHANNELS command 提取可能的订阅者信息(我不建议这样做)。 谢谢,@Myst。我之前尝试过客户端实现的想法,但决定反对它,因为它可能导致服务器负载峰值。想象一下,有 1,000 个活跃用户/客户,他们都收到“重新验证”消息。也就是说,几乎同时向服务器发送了 1,000 个请求。除非可以对客户端进行编程以批量重新进行身份验证。 @FavouriteOnwuemene - 你是对的,我会更新我的答案。解决“雷声”场景的唯一解决方案是惰性/无效解决方案。尽管 WebSocket 服务器应该能够处理比 HTTP 服务器更多的并发流量,但将这些重新身份验证分散出去可能对网络更加友好。【参考方案2】:

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

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

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] 

【讨论】:

以上是关于如何获取通过特定频道订阅的模型的 ID的主要内容,如果未能解决你的问题,请参考以下文章

如何从订阅列表中获取仅订阅英语频道的用户

如何使用 Telethon 获取电报私人频道 ID

YouTube API:如何通过频道名称获取频道 ID?

如何通过自动生成的频道#name 获取 youtube 频道 ID?

如何获取 YouTube 频道的订阅者数量到 Google 表格

如何使用 YouTube Analytics API 获取任何 Youtube 频道的订阅用户列表及其详细信息?