不能直接用$_session统计在线人数,因其依赖文件存储、gc机制不可控、多机不共享,且session_start()不等价于用户在线;redis set配合ttl实现“写入即续期”,scard可准确获取实时在线数。

为什么不能直接用 $_SESSION 统计在线人数
PHP 的 $_SESSION 默认存文件,生命周期依赖 session.gc_maxlifetime,但实际用户关闭浏览器后 session 文件不会立刻删除,且多机部署时 session 文件不共享。更关键的是,session_start() 本身不等于“用户在线”——可能只是偶然刷新、爬虫触发、或 AJAX 心跳未清理。
Redis 的 SET 集合天然适合去重 + 过期管理:每个用户用唯一标识(如 user:123 或 sess:abc456)作为成员,设置 TTL,到期自动剔除,SCARD 一次返回准确基数。
实操建议:
- 不要用 IP 做 key —— 内网 NAT、移动网络出口 IP 大量复用,会导致严重低估
- 优先用登录态中的
user_id;未登录用户可用session_id()+setcookie()确保客户端持久携带 - 每次用户有活跃行为(页面加载、API 请求)都执行一次
SETEX或EXPIRE延长 TTL,而非只在首次访问设值
如何用 Redis SET 实现带心跳的在线统计 核心逻辑是“写入即续期”,不是“上线写、下线删”。因为 HTTP 无连接状态,你无法可靠捕获“下线”事件,但可以高频确认“还活着”。
示例代码片段(PHP + predis):
// 每次请求都执行(放在公共入口或中间件)
$redis = new Predis\Client(['scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379]);
$userKey = 'online:user:' . ($userId ?: 'guest:' . session_id());
$redis->setex($userKey, 300, 1); // 5 分钟过期
// 统计总数(注意:这是实时近似值,非强一致)
$onlineCount = $redis->scard('online:set'); // ❌ 错!SET 集合名不能和成员混用
正确做法是把所有在线 key 归到一个集合里:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用
SADD online:users $userKey添加成员(不是 setex) - 同时用
EXPIRE $userKey 300单独设 TTL - 但这样需额外维护:当
$userKey过期,它不会自动从online:users中移除
更稳妥的方案是放弃集合嵌套,改用 SCAN 扫描前缀 + TTL 校验(适合中小流量),或直接用 KEYS online:user:*(仅开发环境,生产禁用)。
高并发下 SCARD 不准?那是 key 设计错了
常见错误是把所有用户塞进同一个 Redis SET,比如 SADD online:all user:1 user:2 user:3,然后靠 SCARD online:all 获取总数。这看似合理,但问题在于:
Redis 的 SET 成员是字符串,如果你用 user:1 作为 member,那没问题;但若误写成 SADD online:all "user:1"(加了引号),实际存的是带引号的字符串,导致重复添加不生效。
- 检查是否真正在添加:用
SMEMBERS online:all查看实际成员 - 避免用动态拼接导致空格/换行混入:
trim($uid)必须做 - 如果用
user_id,注意类型统一——123和"123"在 Redis 是两个不同 member - 别在循环里反复
SCARD,它 O(1),但频繁调用仍增加 Redis 负载;可考虑每 10s 缓存一次结果到本地变量或 APCu
线上必须加的兜底与监控项
Redis 故障时,SCARD 报错或返回 0,不能让整个网站挂掉或显示“0 人在线”。
实操建议:
- 封装统计函数,捕获
Predis\Connection\ConnectionException,降级返回缓存值或 -1(表示不可用) - 用
INFO keyspace定期检查db0:keys=xxx,expires=yyy,确保过期 key 数量接近总 key 数,否则说明续期逻辑失效 - 给每个
online:user:*key 设置相同 TTL 后,观察redis-cli --bigkeys是否出现异常大 key —— 可能是未登录用户疯狂刷出新 session_id - 如果用 Nginx + PHP-FPM,注意
session.cookie_httponly和session.cookie_samesite配置,否则跨域请求拿不到 session_id,导致 guest 用户重复计入
SET 看似简单,但在线统计真正难的不是命令怎么写,而是“什么才算在线”这个定义是否经得起压测、断网、多标签页、隐身模式的反复验证。多数线上事故,都出在续期时机太松或 key 命名太随意。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










