本文目录导读:

- 方案一:基于数据库的简单记录(适合小型项目)
- 方案二:基于 Redis 的 Bitmap(推荐,高性能,适合中大型项目)
- 方案三:基于消息队列 + 异步写入(高并发项目)
- 方案四:更详细的统计(PV/UV + 行为分析)
- 最佳实践建议
- 总结:你的项目该选哪种?
在PHP项目中实现用户活跃统计,通常需要结合数据库记录、缓存(如Redis) 以及定时任务来处理。
核心思路是:记录用户的每一次有效行为(如页面访问、API调用),并根据时间窗口进行聚合分析。
以下是几种从简单到复杂的实现方案:
基于数据库的简单记录(适合小型项目)
原理:每次用户请求时,更新或插入一条记录到数据库表中。
-
数据库表设计:
CREATE TABLE `user_active_log` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL COMMENT '用户ID', `active_date` DATE NOT NULL COMMENT '活跃日期', `ip_address` VARCHAR(45) DEFAULT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_date` (`user_id`, `active_date`) -- 防止同一天重复记录 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
PHP 代码(核心逻辑):
<?php // 在用户访问首页或触发关键操作时调用 function recordUserActivity($userId) { $db = getDBConnection(); // 你的数据库连接 $today = date('Y-m-d'); // 使用 INSERT ON DUPLICATE KEY UPDATE 来保证一天只记录一次活跃 $sql = "INSERT INTO user_active_log (user_id, active_date, created_at) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE created_at = NOW()"; // 更新时间表示再次活跃 $stmt = $db->prepare($sql); $stmt->execute([$userId, $today]); } ?> -
统计方法:
- 日活跃数(DAU):
SELECT COUNT(DISTINCT user_id) FROM user_active_log WHERE active_date = CURDATE(); - 月活跃数(MAU):
SELECT COUNT(DISTINCT user_id) FROM user_active_log WHERE active_date BETWEEN '2023-10-01' AND '2023-10-31'; - 某用户连续X天活跃:通过
GROUP BY user_id HAVING COUNT(*) >= X实现。
- 日活跃数(DAU):
适用场景:日均 UV 小于 1 万,表数据量不大的项目。
基于 Redis 的 Bitmap(推荐,高性能,适合中大型项目)
原理:利用 Redis 的 SETBIT 命令,以用户ID作为偏移量,以日期作为Key。
- 优点是:极其节省内存(1个用户1天只占1比特),统计速度极快(
BITCOUNT命令)。 - 缺点是:只能统计“是否活跃”,不能记录次数或时间戳。
-
PHP 代码(使用 Predis 或 PhpRedis):
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); function recordActive($userId) { global $redis; $key = 'active:user:' . date('Ymd'); // active:user:20231027 // 设置第 userId 位为 1 $redis->setBit($key, $userId, 1); // 建议设置过期时间,比如35天,防止key堆积 $redis->expire($key, 3600 * 24 * 35); } // 用户登录或访问时调用 recordActive(10086); ?> -
统计方法:
- DAU:
$redis->bitCount('active:user:20231027'); - 过去7天活跃用户数:使用
BITOP OR将7天的bitmap合并,BITCOUNT。$past7Days = []; for ($i = 0; $i < 7; $i++) { $key = 'active:user:' . date('Ymd', strtotime("-$i days")); $past7Days[] = $key; } $redis->bitOp('OR', 'active:weekly', ...$past7Days); $weeklyActive = $redis->bitCount('active:weekly'); $redis->expire('active:weekly', 60); // 临时key,1分钟后过期
- DAU:
-
获取具体活跃用户ID:通过
BITFIELD命令或逐字节解析。
基于消息队列 + 异步写入(高并发项目)
原理:用户请求 -> 写入消息队列(Redis List / RabbitMQ / Kafka) -> 消费者进程批量写入数据库或 Redis。
-
生产者(用户请求时):
<?php // 将活跃事件推入队列 $event = json_encode(['user_id' => $userId, 'time' => time(), 'action' => 'page_view']); $redis->lPush('queue:user_active', $event); ?> -
消费者(使用 PHP CLI 脚本或 Worker):
<?php // consumer.php while ($event = $redis->brPop('queue:user_active', 5)) { $data = json_decode($event[1], true); // 方法A:写入Redis Bitmap $redis->setBit('active:user:' . date('Ymd', $data['time']), $data['user_id'], 1); // 方法B:批量写入MySQL(每100条执行一次) // ... } ?>
更详细的统计(PV/UV + 行为分析)
如果不仅是统计“登录/访问”,还要统计“点击”、“分享”、“评论”等具体行为,需要记录更详细的数据。
-
数据库表:
CREATE TABLE `user_action_log` ( `id` BIGINT AUTO_INCREMENT, `user_id` INT NOT NULL, `action` VARCHAR(50) NOT NULL COMMENT '行为类型:like, share, comment, post', `target_id` INT DEFAULT NULL COMMENT '操作对象ID', `ip` VARCHAR(45), `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX `idx_user_action` (`user_id`, `action`, `created_at`), INDEX `idx_date` (`created_at`) ) ENGINE=InnoDB;
-
统计:
- 用户在某段时间内发帖次数:
SELECT COUNT(*) FROM user_action_log WHERE user_id=1 AND action='post' AND created_at BETWEEN ... - 网站热点事件统计:
SELECT action, COUNT(*) as cnt FROM user_action_log GROUP BY action ORDER BY cnt DESC
- 用户在某段时间内发帖次数:
最佳实践建议
-
数据分层:
- 实时层:用 Redis Bitmap 存今日/昨日 DAU。
- 离线层:用
crontab每天凌晨将 Redis 数据同步到 MySQL(归档,保留全量历史)。 - 分析层:如果需要复杂分析(漏斗、留存),可以将 MySQL 数据导入OLAP 数据库(如 ClickHouse)。
-
避免重复写入:
- 在高并发下,避免每个请求都直接写数据库,可以使用 Redis Bitmap 或 Golang/PHP 内存表数组 定时冲刷。
-
跨天处理:
- 凌晨 00:00:00 的请求属于前一天的活跃,还是今天的?建议用统一时区的服务端时间(如 UTC+8)记录
Ymd,避免前端时间戳误差。
- 凌晨 00:00:00 的请求属于前一天的活跃,还是今天的?建议用统一时区的服务端时间(如 UTC+8)记录
-
用户留存计算:
- 需要知道:第 N 日留存 = (第1天登录的用户中,在第 N 天登录了的人数)/ 第1天登录的总人数。
- 可以使用 Redis 的 Set 集合来做交集运算。
你的项目该选哪种?
| 项目类型 | 推荐方案 | 原因 |
|---|---|---|
| 个人博客 / 小型企业站 | 方案一(MySQL) | 实现简单,不需要额外组件。 |
| 日活5万以下的App/Web | 方案二(Redis Bitmap) | 性能好,内存消耗极低,统计极快。 |
| 日活百万级 / 需要行为分析 | 方案三+方案四 | 异步解耦,队列保证系统稳定性,MySQL+ClickHouse做报表。 |
最常用且建议优先尝试:方案二(Redis Bitmap),它能让你的系统轻松承载高并发,且代码量很少。