You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Phoenix Presence中以常数时间统计在线用户数量?

问题描述

我正在使用Phoenix搭配LiveView,在某视图中编写了如下代码:

def handle_info(%{event: "presence_diff", payload: _payload}, socket) do
  # (Payload handling omitted... I just want the count)

  users_count = map_size(Presence.list("some_topic"))

  {:noreply, assign(socket, :users_count, users_count)}
end

根据map_size的文档说明,该操作是常数时间,这部分没问题,但我对Presence.list的返回值(实际返回Map而非List)存在疑问:每次调用Presence.list时,这个Map是重新创建的,还是只是某种引用?

我担心如果有10000个用户,每次有人加入/离开时,都要为每个用户创建包含10000个元素的Map,这会造成资源浪费。另外Presence模块似乎没有直接获取计数的函数,我查阅了大量源码但未得到明确结论。

注:我知道presence map中每个key对应一个metas数组,但我只关心不同key的数量。


解答

Presence.list的返回值是新创建的Map

Phoenix.Presence的list/1函数每次调用时,都会基于底层存储(比如本地ETS表或Redis)的当前数据生成一个全新的Map实例,不是返回已有Map的引用。这是因为Presence.list需要把底层存储的原始数据(带元信息的条目)转换为用户预期的结构:以用户ID为键、对应元数据数组为值的Map。

如果有10000个在线用户,每次调用Presence.list确实会生成包含10000个键的新Map,频繁调用(比如每次用户进出时)会带来不必要的内存分配和GC开销,你的担忧是合理的。

更高效的用户计数方案

既然你只需要唯一用户的数量,完全没必要每次调用Presence.list。可以利用presence_diff事件的payload来增量更新计数:

presence_diff的payload包含:joins和:leaves两个Map,分别对应新加入和离开的用户。你可以直接用map_size拿到新增/减少的用户数,然后更新当前计数:

def handle_info(%{event: "presence_diff", payload: %{joins: joins, leaves: leaves}}, socket) do
  count_delta = map_size(joins) - map_size(leaves)
  new_users_count = socket.assigns.users_count + count_delta

  {:noreply, assign(socket, :users_count, new_users_count)}
end

这种方式完全避开了获取整个在线用户列表,每次操作都是常数时间,哪怕用户量很大也不会有性能问题。

为什么Presence没有直接的计数API

Phoenix.Presence的核心设计是提供灵活的在线状态管理,而计数只是众多衍生需求之一。不同场景下的计数逻辑差异很大(比如是否区分用户元数据、是否按分组统计等),官方没有提供单一的计数函数,而是让开发者基于presence_diff事件或底层存储实现自定义逻辑。

如果需要全局准确的跨节点计数:

  • 本地ETS存储:可以用:ets.select_count直接统计ETS表中的唯一用户数(Phoenix.Presence.Local的ETS表按用户ID分组)
  • Redis存储:可以用Redis的HLEN命令(如果Presence用Hash结构存储)获取键的数量

但对大多数实时更新场景来说,用presence_diff增量更新计数是最优解。


内容的提问来源于stack exchange,提问作者user4757175

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 16:03:08