博客的访问统计是基于51la实现,其只会统计“真人”的访问记录会过滤掉绝大部分的全站流量,因此如果想要分析全站的流量是否存在异常则需要更底层的实现。博客的基础架构是 nginx + hexo 的静态博客页面,因此全站流量分析的思路是:
采集 nginx 的访问日志
同步到分析性数据库
搭建实时报表
结合博主的技术栈,设计出如下架构

二、框架解析
本节解释为什么要选择 vector + clickhouse + metabase
2.1 vector
vector 是一个轻量级、超快用于构建可观测数据管道的工具,这里用作日志采集的 ETL工具。相较于 flume 或 filebeat + logstash 有以下优点
使用 rust 编写,无 gc 运行效率极高且部署简单无需额外的环境(flume 或 ELK 都需要 jre)
vector 支持 44 种 source、15 种 transform 和 59 种 sink
同时要考虑实际情况,博主公网服务器的配置仅为 2c4g,vector 是一个性价比极高的选择
2.2 clickhouse
市面上流量的 AP 数据库大概就是:clickhouse、doris(starrocks) 或 DuckDB。这个实验有如下特点:
实时
单机
从单机的架构来看,clickhouse 仅需自身服务不像 doris 至少还需要 fe+be。同时 clickhouse 也是博主最熟悉的 AP 数据库
duckdb 似乎也是不错的选择,但是作为内嵌的数据库对外提供服务的能力博主还需要去研究一下
2.3 metabase
BI 方面最开始选择的是 superset,但 superset 匿名访问 dashboard 的配置过于繁琐,且部署文档欠缺。因此选择 metabase
三、框架搭建
3.1 vector
一行命令直接安装
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev | bash |
部署在 nginx 服务器中
如果是 root 用户执行安装命令,vector 的 home 路径为:/root/.vector;为了后续方便执行可以添加到环境变量
VECTOR_HOME=/root/.vector |
vector 默认给出了一个演示案例,执行
vector -c $VECTOR_HOME/config/vector.yaml |

3.2 clickhouse
使用 docker 进行部署
services: |
建议部署在分析服务器,如果公网服务器配置足够也可与 nginx 在一台
3.3 metabase
使用 docker 进行部署
services: |
配置 clickhouse 连接

点击数据库-添加数据库,填写clickhouse 连接信息即可。如果和博主一样 metabbase(下称 mb) 与 clickhouse(下称 ck) 均使用 docker 部署且使用博主的 compose 文件,可以直接填写服务器的 ip(非 127.0.0.1),如果熟悉 docker 的同学可以将两个 compose 文件合并成一个这样 mb 与 ck 就在一个网段上可以直接填写 service_name 作为 ip。
为了让 mb 可以即使获取 ck 的表结构可以打开
Scan all databases
为简单的探索重新运行查询
选择同步和扫描的时间
建议部署在分析服务器,如果公网服务器配置足够也可与 nginx 在一台
3.4 其它细节
博主的 clickhou、metabase 部署在自建的服务器中,借助公网 IP 进行穿透。使用 docker 部署的资源占用如下
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
cc0bf19b1d95 metabase 1.17% 1.105GiB / 15.12GiB 7.31% 25.4MB / 522MB 0B / 1.94GB 149
e3c8555282bb clickhouse-clickhouse-1 38.35% 544.5MiB / 15.12GiB 3.52% 48.4MB / 64.3MB 0B / 144GB 408 四、实验搭建
4.1 日志采集
nginx 默认的访问日志使用空格分割,为了方便后续的解析这里修改为 json 格式。修改默认的配置文件nginx.conf并在http模块中添加名为access_json日志格式
log_format access_json '{"@timestamp":"$time_iso8601",' |
并修改access_log日志应用上面配置的格式
access_log /var/log/nginx/access.log access_json; |
校验配置文件无误后重新加载
nginx -t |
观察 nginx 的日志是否符合要求
tail -F /var/log/nginx/access.log |
下面使用 vector 将日志采集到 clickhouse 中,在 clickhouse 创建nginx_access_logs用于接收原始日志
create table nginx_access_logs |
这里的message是 vector 默认映射到 clickhouse 的字段,初接触 vector 同学不建议修改这个字段
下面编写 vector 配置文件实时捕获 nginx 的访问日志并写入nginx_access_logs中
sources: |
启动日志采集服务
nohup vector -c nginx_access_log_to_clickhouse.yaml >> vector.log & |
vector 的 file source 默认支持断点续传将 offset 实时写入本地文件中,路径为$data_dir/<source_name>其中data_dir是 vector 的全局变量也可以在环境变量配置,默认/var/lib/vector/
会写入下面数据
JSON
{"version":"1","checkpoints":[{"fingerprint":{"first_lines_checksum":11589993358280241074},"position":26119026,"modified":"2025-06-08T05:57:56.154979458Z"}]其中position即为当前读取的文件偏移量。
4.2 解析日志
不出意外 clickhouse 的nginx_access_logs表已经接收到数据了,根据 nginx 的日志格式创建access_logs表用于接收解析后的结构数据
create table access_logs |
创建物化视图自动解析数据
SQL
CREATE MATERIALIZED VIEW mv_access_logs TO access_logs AS
SELECT
timestamp,
host,
client_ip,
size,
response_time,
upstream_time,
if(upstream_host = '-', NULL, upstream_host) AS upstream_host,
http_host,
uri,
domain,
if(remote_user = '-', NULL, remote_user) AS remote_user,
request,
method,
if(xff = '-', NULL, xff) AS xff,
if(referer = '-', NULL, referer) AS referer,
if(tcp_xff = '-', NULL, tcp_xff) AS tcp_xff,
http_user_agent,
status
FROM
(
SELECT
parseDateTime64BestEffortOrNull(JSONExtractString(message, '@timestamp')) AS timestamp,
JSONExtractString(message, 'host') AS host,
toIPv4OrNull(JSONExtractString(message, 'clientip')) AS client_ip,
JSONExtractUInt(message, 'size') AS size,
toFloat64OrNull(JSONExtractString(message, 'responsetime')) AS response_time,
toFloat64OrNull(JSONExtractString(message, 'upstreamtime')) AS upstream_time,
JSONExtractString(message, 'upstreamhost') AS upstream_host,
JSONExtractString(message, 'http_host') AS http_host,
JSONExtractString(message, 'uri') AS uri,
JSONExtractString(message, 'domain') AS domain,
JSONExtractString(message, 'remote_user') AS remote_user,
JSONExtractString(message, 'request') AS request,
splitByString(' ', JSONExtractString(message, 'request'))[1] AS method,
JSONExtractString(message, 'xff') AS xff,
JSONExtractString(message, 'referer') AS referer,
JSONExtractString(message, 'tcp_xff') AS tcp_xff,
JSONExtractString(message, 'http_user_agent') AS http_user_agent,
toUInt16OrNull(JSONExtractString(message, 'status')) AS status
FROM nginx_access_logs
) AS t
WHERE timestamp IS NOT NULL;至此便打通了实时的日志采集链路。
4.3 搭建报表
评估一下每秒的日志条数
select avg(cnt) |
数据量在 5 条/s,access_logs按天分区是可以满足今日的实时数据分析,无需额外基于access_logs进一步构建物化视图。因此直接在 metabase 中搭建报表,下面演示分时段 pv 统计的搭建过程
点击创建问题,按照下图选择即可

数据:报表的数据源,对应 sql 的 from
过滤:选择今日,对应 sql 的 where
汇总:选择行数,对应 sql 的聚合字段这里是 count()
通过:选择时间字段,因为是分时段统计同时选择时间单位为分钟,对应 sql 的 group by
排序:选择时间字段,为了让可视化可以连续显示,对应 sql 的 order
点击可视化后 mb 会自动判断最适合的展示图表,也可以对样式进行微调

后续所有的指标都类似,当然如果指标过于复杂无法通过低代码进行拖拉拽可以新建 sql 实现可视化。当指标足够多时创建面板将指标拖入即可生成一个流量分析的实时面板,面板可配置自动刷新以及生成分享链接

五、后续规划
数据更多有价值的指标
ip 映射地理位置
标签: metabase clickhouse 流量分析 sql