Supabase 实时订阅

解读

在国内一线/二线大厂的 PHP 面试中,面试官问“Supabase 实时订阅”并不是想听你背诵 WebSocket 协议报文,而是考察三层能力:

  1. 你是否知道 Supabase 的“实时”能力本质上是 PostgreSQL 的逻辑复制槽(Logical Replication Slot)+ Realtime Server(Elixir 写的 Brod 消费组);
  2. 你是否能在 PHP 侧落地一条“可投产”的链路:鉴权、断线重连、私有行级策略、水平扩展、降级方案;
  3. 你是否清楚国内网络环境(HTTPS 443 统一出口、Nginx 反向代理、公司级网关对长连接的限制)下,如何把 Supabase 的 wss 流稳定嵌入现有 PHP 业务。

一句话:面试官想看“PHP 工程师如何把国外流行的 Supabase Realtime 安全、稳定、低成本地跑在中国的机房里”。

知识点

  1. Supabase 实时架构
    • PostgreSQL WAL → Logical Replication Slot → Realtime Server(Elixir) → Phoenix Channels → 客户端 wss
    • 行级安全(RLS)与 JWT 在 wss 握手阶段就生效,过滤结果集。
  2. PHP 生态里“没有官方 SDK”的现状
    • 官方只提供 js/python/dart/go,PHP 只能走“裸”wss 或自己封装。
  3. 国内机房常见限制
    • 防火墙默认 30s 无数据即断 TCP,必须心跳;
    • 公司级网关只放行 80/443,不能自定义 4000/5432;
    • 高并发场景下一个频道 1000+ 连接时,Realtime Server CPU 瓶颈明显。
  4. PHP 侧技术选型
    • 同步阻塞派:Ratchet/ReactPHP 做本地中转,把 wss 转内部 tcp 或 unix sock,再让 FPM Worker 读;
    • 异步常驻派:Swoole4/8 的 WebSocket\Client 或 OpenSwoole Coroutine\Http\Client 直接连 Supabase,收到消息后写 Redis Stream,供 FPM 消费;
    • 折中派:用 Nginx 的 stream 模块或 Envoy 做 wss→tcp 反向代理,PHP 只负责 REST 轮询,降级到 3s 长轮询。
  5. 鉴权与续租
    • Supabase 使用 anonKey + userJWT;JWT 过期后需 refresh,PHP 要在 Swoole Timer 里提前 30s 调用 /auth/v1/token?grant_type=refresh_token。
  6. 断线重连策略
    • 指数退避:1s→2s→4s→8s…上限 60s;
    • 配合 channel 级别的 callback 做幂等去重(用 Redis SET key=msg_id NX EX)。
  7. 私有数据合规
    • 若数据涉用户隐私,必须走“应用层中转”模式:客户端只订阅 topic=user:123,PHP 后端通过 RLS 保证 user_id=123 才能收到,避免前端直接连 Supabase 造成“脱库”风险。
  8. 压测与监控
    • 使用 k6 或阿里云 PTS 模拟 5k 长连接,观察 Realtime Server CPU、BEAM VM 内存、Postgres 复制槽 lag;
    • Prometheus + Grafana 监控 phpswoole_coroutine_websocket_connect_total、supabase_realtime_lag_bytes。

答案

“如果让我在生产环境用 PHP 接入 Supabase 实时订阅,我会分五步落地:

第一步,架构选型。因为 PHP-FPM 是无状态的,我不会让它直接维持长连接,而是用 Swoole 的常驻进程作为‘边车’。每台业务机起 4 个 Swoole 协程进程,负责与 Supabase 的 wss 保持心跳,收到消息后写入本地 Redis Stream(key=supabase:events:{$channel}),PHP-FPM 通过 XREAD 非阻塞拉取,实现‘异步解耦’。

第二步,鉴权。用户登录后,后端生成两枚 JWT:access(有效期 15min)与 refresh(有效期 7d)。Swoole 进程在 access 过期前 30s 自动调用 /auth/v1/token 续租,失败则触发告警并降级到轮询。

第三步,频道隔离与 RLS。对‘聊天室’场景,我建一张 chat_messages 表,RLS 策略为 using (room_id in (select room_id from room_members where user_id=auth.uid()));客户端订阅 room:123,Swoole 进程在 JOIN 消息里带上用户 JWT,这样 Supabase 只推该用户有权限的行,避免前端拿到全表。

第四步,断线重连与幂等。Swoole 协程内维护一个 isConnected 标志位,断线后按 1-2-4-8s 指数退避重连;消息体里自带 msg_id=uuidv4,PHP 消费端用 Redis SETNX 做幂等,确保重放不重复写库。

第五步,监控与回退。Prometheus 暴露指标 php_supabase_connected 0/1,lag>10s 就短信告警;若 Realtime Server 故障,客户端 5s 内收不到心跳自动回退到‘轮询模式’:走 REST /rest/v1/chat_messages?select=*&room_id=eq.123&order=created_at.desc&limit=30 每 3s 拉一次,保证可用性 99.9%。

这套方案已在我司电商客服系统上线,双 11 峰值 2.3k 长连接,CPU 占用 12%,消息延迟 p99 180ms,完全符合国内网络与合规要求。”

拓展思考

  1. 如果公司政策禁止任何境外流量,能否把 Supabase 的 Realtime Server 与 Postgres 全部迁到阿里云 RDS+ACK 自建?需要改哪些源码?
  2. 当频道数达到 10 万、单频道 QPS 1k 时,PostgreSQL 逻辑复制槽的 WAL 积压会爆炸,你是否考虑用 Kafka 做中间层,把“行级事件”转成 topic 分区,再让 PHP 消费?
  3. 在 Swoole 5 的 PHP8.2 协程环境下,如何结合 OpenSwoole\Coroutine\PostgreSQL 实现“本地读写主库,远程订阅只读副本”的混合云架构?
  4. 假如未来 Supabase 官方推出 PHP SDK,你觉得最需要封装哪三个 API,才能覆盖国内 80% 业务场景?