workerman 必须通过 http 接口配合 clickhouse-php 客户端连接 clickhouse,禁用 pdo 直连;需用容器单例或协程连接池管理 client 实例,避免 onworkerstart 中 new 实例导致连接数爆炸;日志写入必须用 insertbatch() 批量提交,控制批次 1000–5000 行,并严格匹配字段类型;分区键须含时间字段(如 toyyyymmdd(event_time)),排序键高频过滤字段置左;异步插入需显式关闭 wait_for_async_insert 以保障低延迟。

Workerman 默认连不上 ClickHouse,因为它不内置 ClickHouse 驱动,PDO 直连 MySQL 兼容端口(9004)会因协议模拟不全而失败,比如报 PDOException: SQLSTATE[HY000]: General error: 1002 Unknown setting 'names'。必须走 HTTP 接口,用官方维护的 clickhouse-php 客户端封装连接池,否则写入吞吐和稳定性都不可控。
为什么不能 new Client() 写在 onWorkerStart 里
Workerman 的 onWorkerStart 是进程级回调,每个 worker 进程启动时执行一次。如果在这里 new \ClickHouseDB\Client(),会导致:多个进程各自持有一个长连接,HTTP keep-alive 无法复用;连接数随 worker 数线性增长,ClickHouse 服务端很快 hit max_connections;协程未启用时,cURL 同步阻塞,高并发下请求排队严重。
正确做法是用依赖注入容器绑定单例,或用 co\Channel(需开启协程)管理连接池:
- 在
config/container.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 实例 - 若需更高并发控制(如限流、自动重试),再包一层带
co\Channel的连接池封装,维持 5–10 个复用连接
insertBatch() 是唯一能扛住海量日志的写入方式
ClickHouse 对单行 insert() 极其低效:每条语句生成一个 Part,小 Part 多了会拖垮 Merge 和查询性能;HTTP 请求开销占比过高,吞吐卡在几百 QPS 级别。
必须用 insertBatch() 批量写入,且要控制批次大小和触发时机:
- 每批建议 1000–5000 行,低于 100 行收益不明显,高于 5000 行易超时(尤其含 JSON/Array 字段时)
- 字段类型必须严格匹配表定义:如
DateTime字段传整数时间戳(秒级),传字符串如'2026-05-20 22:00:00'会静默转成 0 - 不要在主请求流程等写入完成:把日志先推到 Redis List 或内存
static $buffer = [],用定时器(Timer::add(1.0, [...]))每秒刷一次批
示例:
$client->insertBatch('log_events', $rows, [
'event_time' => 'DateTime',
'user_id' => 'UInt64',
'path' => 'String',
]);
分区键与排序键没对齐,实时查询就变慢查询
Workerman 常用于实时报表接口(如 /api/realtime/uv),响应必须在 500ms 内。但若建表时分区/排序键设计不当,WHERE 条件无法裁剪数据,就会扫全表。
关键点:
- 分区键必须含时间字段,且粒度匹配查询范围:用
PARTITION BY toYYYYMMDD(event_time),才能让WHERE event_time >= now() - INTERVAL 1 HOUR跳过历史分区 - 排序键要把高频过滤字段放最左:如查
app_id = 'web'最多,就写ORDER BY (app_id, event_time);若写成(event_time, app_id),即使加了app_id条件,仍要扫描整个当天分区 - 写入数据的时间字段必须是 UTC,Webman 请求头里的
X-Timezone不影响 ClickHouse 解析,它只认传入值本身
最容易被忽略的是:ClickHouse 的异步插入(async_insert=1)虽能提吞吐,但默认 wait_for_async_insert=1 会让客户端等数据落盘才返回——这和 Workerman 的高性能定位冲突。生产环境若确认可接受极短时间(秒级)内查不到最新数据,应在 JDBC 连接串或 INSERT 语句里显式关掉等待,否则批量写入延迟会翻倍。











