Webman配合ClickHouse实现高并发日志处理的关键在于复用客户端实例、批量异步写入、严格字段匹配、合理分区查询及分离日志通道。

直接用 smi2/phpclickhouse 配合 Webman 的常驻内存特性,就能撑住每秒数千条日志写入+实时聚合查询——关键不是换数据库,而是别让 PHP 每次请求都重连、重建连接池、重复初始化客户端。
Webman 中复用 ClickHouse 客户端实例
Webman 是常驻进程,\ClickHouseDB\Client 实例必须单例复用,不能每次请求 new 一个。否则会快速耗尽系统文件描述符(尤其是高并发下),还可能触发 ClickHouse 的连接数限制。
- 在
app/bootstrap.php或服务提供者中初始化并绑定到容器:$container->bind(\ClickHouseDB\Client::class, function () { return new \ClickHouseDB\Client([ 'host' => '127.0.0.1', 'port' => '8123', 'username' => 'default', 'password' => '', 'database' => 'logs', 'timeout' => 30, ]); }); - 控制器里通过依赖注入或
$this->app->get(\ClickHouseDB\Client::class)获取,避免new \ClickHouseDB\Client(...)出现在业务逻辑中 - 不手动调用
__destruct()或关闭连接——cURL 连接由底层自动复用,强行 close 反而破坏 keep-alive
日志写入要 batch + async,别用 insert() 逐条塞
ClickHouse 对批量写入极度友好,但对单行 INSERT 极其低效。Webman 虽然能扛并发,但若控制器里每条日志都调一次 insert(),吞吐立刻崩盘。
- 用
insertBatch()替代insert(),至少 100 行/批,上限建议 ≤ 5000 行(避免单次请求超时):$client->insertBatch('log_events', $rows, [ 'event_time' => 'DateTime', 'user_id' => 'UInt64', 'path' => 'String', ]); - 不要在 HTTP 请求主流程里等写入完成:把日志数据推到 Redis 队列或内存缓冲区,用 Webman 的定时器或独立 workerman 进程异步消费并批量刷入 ClickHouse
- 字段类型必须严格匹配表定义,比如 ClickHouse 的
DateTime字段要传时间戳整数(秒级),传字符串如'2026-05-20 22:00:00'会静默失败或转成 0
查日志时绕开 WHERE 子句陷阱
ClickHouse 不是 MySQL,WHERE 写法稍有不慎就会全表扫描,哪怕加了索引。尤其在 Webman 接口返回慢时,90% 是因为没用好分区键和排序键。
立即学习“PHP免费学习笔记(深入)”;
- 确保日志表按天/月分区(如
PARTITION BY toYYYYMMDD(event_time)),查询必须带上event_time >= '2026-05-01'才能命中分区,否则扫全部历史分区 - 避免
WHERE path LIKE '%/api/v2/%'—— ClickHouse 的LIKE无法利用索引,改用match(path, '/api/v2/.*')(需开启enable_early_constant_folding=1)或前置正则预处理字段 - 聚合查询慎用
SELECT COUNT(*) FROM log_events:它会触发全分区扫描;改用SELECT count() FROM system.parts WHERE database='logs' AND table='log_events'查近似行数,或建物化视图预计算
Webman 日志与 ClickHouse 日志要分开关联
Webman 自己的运行日志(runtime/logs/webman.log)和写入 ClickHouse 的业务日志是两回事。混淆二者会导致排查困难,甚至误删线上分析数据。
- Webman 的
config/log.php里只管框架错误、路由未匹配、异常堆栈等运维日志,**绝不**把 ClickHouse 查询 SQL 或参数 dump 进这里 - ClickHouse 客户端报错(如
cURL error 60、Code: 516. DB::Exception: Received from ...:8123. DB::Exception: default: Authentication failed)应单独捕获,记录到独立通道(如 Kafka 或 ELK),并触发告警 - ClickHouse 服务端日志级别设为
information即可,debug或trace会迅速打爆磁盘;可通过SET send_logs_level = 'warning'在客户端临时提升单条查询的日志输出等级
最易被忽略的是:Webman 进程重启时,内存里的日志缓冲区会丢数据。哪怕用了 Redis 队列,也要确认消费者进程是否随 Webman 一起启停,以及队列是否有持久化和重试机制——这比选什么驱动重要得多。



















