Webman需通过中间件+协程异步写入实现流量统计与计费,避免阻塞;用Redis分片存储日志并批量落库,动态加载计费规则并缓存,高并发下依赖Redis原子指令保证计数器一致性。

Webman 本身不内置流量统计或计费逻辑,但它的常驻内存 + 异步 I/O 特性,让实时采集、高频写入、毫秒级响应成为可能——关键在于你如何组织数据采集点、避免阻塞、控制写入节奏。
如何在请求链路中无感埋点统计
不能靠每个控制器里手动 $count++ 或写数据库,那样会拖慢所有接口。正确做法是把统计逻辑下沉到中间件,并用协程非阻塞方式提交。
- 在
app/middleware/AccessLogMiddleware.php中拦截请求,提取$request->ip()、$request->path()、$request->method()等基础字段 - 不要直接调用
Db::insert(),改用co::sleep(0)切出当前协程后,再用go(function () { Db::insert(...) })异步写入 - 对高频路径(如健康检查
/ping)做白名单过滤,避免日志爆炸 - 注意:Webman 的中间件执行顺序影响统计完整性——计费类中间件必须放在鉴权之后、业务控制器之前
为什么不能直接用 MySQL 存原始访问日志
单机 MySQL 在每秒写入超 500 条时就会出现连接池耗尽、主键冲突或延迟飙升,尤其当计费需要按秒聚合时,同步写入会反向拖垮 API 响应。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- 推荐方案:先写入 Redis 的
list或stream,由独立的消费进程(如php worker.php start -d)批量落库 - Redis key 建议按小时分片,例如
access:2026072915,避免单 key 过大 - 如果必须实时查,用
HyperLogLog统计 UV,SortedSet按时间戳存 PV,比 count(*) 快一个数量级 - 切记:Webman 的
Db类默认不支持事务跨协程,别在异步写入里套beginTransaction()
计费规则如何动态加载不重启服务
计费策略变更是高频操作,硬编码在 PHP 文件里每次都要 reload,违背 Webman 常驻内存的设计初衷。
- 把计费规则存在 MySQL 的
billing_rules表中,字段包括api_path、quota_per_minute、price_per_call、is_active - 用
Cache::remember('billing_rules', 60, function () { return Db::table('billing_rules')->where('is_active', 1)->get(); })缓存 60 秒,避免每次请求都查库 - 规则变更后,通过 Webman 的
reload信号或 Redis Pub/Sub 主动清缓存,无需 stop/start - 注意:Cache 键名别写死成
billing_rules,建议带版本号如billing_rules_v2,方便灰度切换
高并发下计数器原子性怎么保证
多个协程同时更新同一用户今日调用次数,$count = $count + 1 不是原子操作,容易漏计或重复计费。
- 必须用 Redis 的
INCR或INCRBY,例如redis()->incr("user:123:calls:20260729") - 配合
EXPIRE设置过期时间,避免 key 永久残留,例如redis()->expire("user:123:calls:20260729", 86400) - 若需更复杂逻辑(比如“每 10 次免费,之后 0.1 元/次”),用 Lua 脚本封装读-判-写三步,确保原子性
- 别依赖 PHP 的
static变量或全局数组,它们在多进程下不共享,且协程间也不安全
真正难的不是写代码,而是判断哪些数据必须强一致(如余额扣减)、哪些可以最终一致(如 PV 统计),以及在 Redis、MySQL、本地内存之间划清边界——Webman 给了你并发能力,但不会替你做取舍。


















