持续聚合
Flow 引擎按时间窗口维护总和、平均值、计数等 聚合结果,新数据到达时更新 sink 表。查询直接读 sink 表,不必扫描 source 表。
下面三个例子覆盖了常见的用法:
- 实时分析:把高频事件流降采样到较低分辨率。例如把毫秒级的日志事件汇总成每分钟的请求数、平均响应时间和错误率。
- 实时监控:对数据流做聚合,并把聚合出的行本身作为告警,例如窗口内最高温度超过阈值时才写出一行。
- 实时仪表盘:预先算好仪表盘要展示的计数,例如每分钟的请求数、平均响应时间和错误数。
实时分析示例
日志统计
这个例子是根据 source 表中的数据计算一系列统计数据,包括一分钟时间窗口内的总日志数、最小大小、最大大小、平均大小以及大小大于 550 的数据包数。
首先,创建一个 source 表 ngx_access_log 和一个 sink 表 ngx_statistics,如下所示:
CREATE TABLE `ngx_access_log` (
`client` STRING NULL,
`ua_platform` STRING NULL,
`referer` STRING NULL,
`method` STRING NULL,
`endpoint` STRING NULL,
`trace_id` STRING NULL FULLTEXT INDEX,
`protocol` STRING NULL,
`status` SMALLINT UNSIGNED NULL,
`size` DOUBLE NULL,
`agent` STRING NULL,
`access_time` TIMESTAMP(3) NOT NULL,
TIME INDEX (`access_time`)
)
WITH(
append_mode = 'true'
);
CREATE TABLE `ngx_statistics` (
`status` SMALLINT UNSIGNED NULL,
`total_logs` BIGINT NULL,
`min_size` DOUBLE NULL,
`max_size` DOUBLE NULL,
`avg_size` DOUBLE NULL,
`high_size_count` BIGINT NULL,
`time_window` TIMESTAMP time index,
`update_at` TIMESTAMP NULL,
PRIMARY KEY (`status`)
);
然后创建名为 ngx_aggregation 的 flow 任务,包括 count、min、max、avg size 列的聚合函数,以及 size 大于 550 的数据包数量。聚合是在 access_time 列的 1 分钟固定窗口中计算的,并且还按 status 列分组。某个窗口内 high_size_count 或 max_size 突然升高,就指向了需要进一步排查的那一分钟。
下方 SQL 语句中的 EXPIRE AFTER '6h' 参数确保 flow 计算仅使用过去 6 小时内的源数据。对于 sink 表中超过 6 小时的历史数据,本 flow 不会对其进行修改。有关EXPIRE AFTER的详细信息,请参阅管理 Flow
CREATE FLOW ngx_aggregation
SINK TO ngx_statistics
EXPIRE AFTER '6h'
COMMENT 'aggregate nginx access logs'
AS
SELECT
status,
count(client) AS total_logs,
min(size) as min_size,
max(size) as max_size,
avg(size) as avg_size,
sum(case when `size` > 550 then 1 else 0 end) as high_size_count,
date_bin('1 minutes'::INTERVAL, access_time) as time_window
FROM ngx_access_log
GROUP BY
status,
time_window;
要检查持续聚合是否正常工作,首先插入一些数据到源表 ngx_access_log 中。
INSERT INTO ngx_access_log
VALUES
('android', 'Android', 'referer', 'GET', '/api/v1', 'trace_id', 'HTTP', 200, 1000, 'agent', now() - INTERVAL '1' minute),
('ios', 'iOS', 'referer', 'GET', '/api/v1', 'trace_id', 'HTTP', 200, 500, 'agent', now() - INTERVAL '1' minute),
('android', 'Android', 'referer', 'GET', '/api/v1', 'trace_id', 'HTTP', 200, 600, 'agent', now()),
('ios', 'iOS', 'referer', 'GET', '/api/v1', 'trace_id', 'HTTP', 404, 700, 'agent', now());
然后,sink 表 ngx_statistics 将被增量更新并包含以下数据。
下面结果中的时间窗口和 update_at 时间戳仅用于示例,会随执行时间而变化。
SELECT * FROM ngx_statistics;
+--------+------------+----------+----------+----------+-----------------+---------------------+----------------------------+
| status | total_logs | min_size | max_size | avg_size | high_size_count | time_window | update_at |
+--------+------------+----------+----------+----------+-----------------+---------------------+----------------------------+
| 200 | 2 | 500 | 1000 | 750 | 1 | 2025-04-24 06:46:00 | 2025-04-24 06:47:06.680000 |
| 200 | 1 | 600 | 600 | 600 | 1 | 2025-04-24 06:47:00 | 2025-04-24 06:47:06.680000 |
| 404 | 1 | 700 | 700 | 700 | 1 | 2025-04-24 06:47:00 | 2025-04-24 06:47:06.680000 |
+--------+------------+----------+----------+----------+-----------------+---------------------+----------------------------+
3 rows in set (0.01 sec)
尝试向 ngx_access_log 表中插入更多数据:
INSERT INTO ngx_access_log
VALUES
('android', 'Android', 'referer', 'GET', '/api/v1', 'trace_id', 'HTTP', 200, 500, 'agent', now()),
('ios', 'iOS', 'referer', 'GET', '/api/v1', 'trace_id', 'HTTP', 404, 800, 'agent', now());
结果表 ngx_statistics 将被增量更新,注意 max_size、avg_size 和 high_size_count 是如何更新的:
SELECT * FROM ngx_statistics;