ThinkPHP行为机制不适合请求统计,因其缺乏并发安全、数据聚合和持久化能力;应改用中间件配合Redis原子操作及请求过滤实现可靠统计。

ThinkPHP 的行为(Behavior)机制本身不直接适合做请求统计——它没有内置的并发安全、数据聚合或持久化能力,强行用 run 方法写文件或直连数据库极易导致计数丢失、重复写入或性能崩塌。
为什么不能在行为里直接写统计逻辑
行为是每次请求都会触发的钩子,但 ThinkPHP 的行为类默认无状态、无生命周期管理,且多个请求可能同时进入同一行为实例:
-
App::beforeStart或app_init行为中执行file_put_contents('count.txt', $n+1)会导致竞态,实际统计值远低于真实请求数 - 若在行为中执行
Db::name('log')->insert(),高并发下可能因事务/连接池耗尽而报错SQLSTATE[HY000] [2002] Connection refused - 行为无法区分 CLI 请求(如命令行任务)、静态资源请求(
/static/css/app.css)和有效业务接口,会把所有入口都计入
正确做法:用中间件 + 原子操作替代行为
ThinkPHP 5.1+ 支持中间件,比行为更可控;统计必须下沉到存储层保障原子性:
- 使用 Redis 的
INCR命令做计数,天然线程安全:$redis->incr('req:total'),再按需每小时用GET+DEL落库 - 中间件中通过
$request->url()和$request->isAjax()过滤掉静态资源、图片、favicon.ico 和非 GET/POST 接口 - 避免在中间件里调用模型(如
LogModel::create()),改用原生Db::execute()批量写入,或投递到消息队列异步处理
// app/middleware/RequestStat.php
<?php
namespace app\middleware;
use think\facade\Cache;
use think\facade\Db;
use think\facade\Request;
use think\facade\Redis;
class RequestStat
{
public function handle($request, \Closure $next)
{
$url = $request->url(true);
// 过滤明显非业务请求
if (preg_match('/\.(js|css|png|jpg|gif|ico|woff|ttf)$/i', $url) || $url === '/favicon.ico') {
return $next($request);
}
// Redis 原子计数
Redis::incr('req:total');
Redis::incr('req:'.date('YmdHi')); // 按分钟维度
return $next($request);
}
}
如何查数据?别在控制器里实时 COUNT(*)
统计查询不能依赖 SELECT COUNT(*) FROM log_table —— 数据量一过十万,页面就卡死。必须预聚合:
立即学习“PHP免费学习笔记(深入)”;
- 每天凌晨用命令行调度器跑一次汇总:
php think stat:aggregate,把昨日每分钟数据GROUP BY hour, minute合并进stat_daily表 - 前端图表只查
stat_daily或缓存好的Cache::get('dashboard:7d'),而不是现场算 - 如果必须看实时总数,从 Redis 读:
Redis::get('req:total'),而非数据库
真正难的不是“怎么写一行统计代码”,而是怎么让这行代码在日均百万请求下不拖垮数据库、不丢数据、不把运维半夜叫醒。Redis 计数 + 定时归档 + 查询走缓存,才是生产环境能活下来的组合。



















