系统架构
evstats 由一个二进制加上 ClickHouse 和 Redis 组成。这一页讲清组件分工、一条事件和一次查询各自怎么走、请求落在哪里,以及从单机到多节点的几种部署形态。
组件
| 组件 | 职责 | 数量 |
|---|---|---|
evstats | 采集、入库、统计、Agent、OpenAPI,管理界面的静态资源也打在二进制里 | 一个或多台,前面放负载均衡,不需要会话粘滞 |
ClickHouse s0 | EVS_CLICKHOUSE(或 -clickhouse)指向的库:账号、角色、项目、配置、看板、操作日志等系统数据,以及落在 s0 上的项目的事件 | 一个 |
| ClickHouse 分片 | 在「系统 → 分片」里追加的 s1、s2……只放事件与聚合 | 零个或多个 |
| Redis | 采集队列(Redis Stream)、登录会话、限流、验证码、查询结果缓存、配置变更通知 | 一个,所有进程共用 |
| SDK | Web、Go、Java 三端,把事件批量发到 POST /sa | 按需 |
没有第二个数据库,没有消息中间件,也没有单独的前端服务。进程只认三个启动设置:推荐环境变量 EVS_CLICKHOUSE、EVS_REDIS、EVS_PORT,同名启动参数写了就覆盖,都有默认值。监听对所有网卡开放。管理员、AI、IP 库在第一次打开网页时填写。
一条事件怎么走
- SDK 生成事件 ID(
messageId),攒成一批,带着写入密钥POST /sa。 - 任意一台
evstats收到后,用内存里的项目表核对写入密钥、采集开关、请求大小、域名名单、爬虫和排除 IP,规范化字段。 - 合法的事件写进 Redis 的采集队列,写入成功才回复 SDK 成功。被过滤的事件按原因计数,接入检查里能看到。
- 所有进程在同一个消费组里取消息,解析 IP 和 User-Agent,按项目所在的分片凑批,插入 ClickHouse 的
evs_event,插入成功才确认(ACK)。 - ClickHouse 的物化视图随插入维护日聚合、维度聚合、首次出现、点击格子和触点表。
- 被 ClickHouse 拒绝的单条事件转进失败事件表,不会堵住队列。
某台进程在确认之前崩溃,它没确认的消息空闲一段时间后由其它进程接着写,已经回复成功的事件不会丢。代价是重试时可能出现少量重复行。
一次查询怎么走
统计只有一个实现:内核里的模块注册表。一个模块(例如 funnel)同时是管理界面的页面、/api/v1 的路径、Agent 的工具、看板卡片的类型,参数名和返回形状在几处完全相同。
- 请求带着项目、日期和参数进来,内核先校验权限和参数,再读出项目所在的分片。
- 没有属性筛选时,PV、UV、排行读预先聚合好的表;有筛选或需要明细时查
evs_event。每条 SQL 都带项目和日期范围,只扫需要的分区。 - 结果按「模块 + 参数」存进 Redis 缓存,管理界面、Agent、OpenAPI 命中同一条缓存,所以三处的数字对得上。
口径(UV 怎么去重、会话怎么切、跳出怎么算)写在 指标口径。
请求落在哪
| 路径 | 谁调用 | 鉴权 |
|---|---|---|
POST /sa、GET /sa.gif | SDK、像素 | 项目的写入密钥 evs_wk_xxx |
GET /sdk/config、POST /replay | Web SDK | 写入密钥 |
GET /sdk/heatmap | 叠加在网页上的热力图 | 一次性令牌 |
/dash/** | 管理界面(看数、Agent、看板、设置) | 登录后的 HttpOnly Cookie |
/api/v1/** | 业务系统、外部 Agent | 用户的接口密钥 evs_xxx,Authorization: Bearer |
GET /openapi.json | 文档、代码生成 | 无 |
GET /health、GET /metrics | 负载均衡、监控 | 无;/metrics 只对内网开放 |
| 其余路径 | 浏览器 | 管理界面的静态资源 |
写入密钥只能写、不能读,出现在网页源码里是正常的;接口密钥能读数据,属于人,权限跟着这个人在后台能看的项目走。两把钥匙不能互换。
数据存在哪
| 表 | 内容 | 保留 |
|---|---|---|
evs_event | 全部事件明细,按项目、日期、事件排序,按天分区 | 项目的保留天数,默认 365 天 |
evs_agg_* | 日聚合、维度聚合、首次出现 | 按系统配置 |
evs_click、evs_attr_* | 热力图格子、首次与末次触点 | 按系统配置 |
evs_replay | 会话回放的分块 | replay.retain_days |
evs_user、evs_project、evs_config 等 | 系统数据,只在 s0 | 长期 |
表结构写在程序里,进程启动时对照调和:缺表建表、缺列加列、视图定义变了就重建,改了什么记在「关于」页。升级不需要人去执行迁移脚本,见 升级。
部署形态
| 形态 | 机器 | 适合 |
|---|---|---|
| 单机 | 一台机器上用 Compose 跑 ClickHouse、Redis、evstats | 体验、测试环境、小流量站点 |
| 生产环境 | ClickHouse、Redis、evstats 各自一台服务器,evstats 前面放 Nginx 终止 HTTPS | 正式上线的推荐形态 |
| 多节点与分片 | 多台 evstats 共用一个 Redis;ClickHouse 追加分片,新项目落到项目数少的分片 | 流量或项目数上来之后 |
三种形态用的是同一个程序、同一组环境变量,从单机换到生产环境、再到多节点,不需要换架构。步骤见 部署总览、生产环境部署、多节点与分片。
安全边界
- 访客 IP 用离线的 ip2region 库在本机解析,不发给任何第三方;程序不向作者回传任何数据。
- 登录会话在 Redis,Cookie 是
HttpOnly,全集群即时失效;接口密钥在服务端只存 SHA-256。写入密钥校验用 SHA-256,原文单独存一份,只有管理员输入登录密码后才能看到,不进缓存、日志和列表接口。 - Agent 调用的是你配置的模型;模型只拿到查询结果,拿不到会话回放的正文,也改不了项目。
- 自定义 SQL 只给管理员,只能读当前项目的事件表与聚合表。
完整的安全说明见 安全与隐私。
某个组件出问题时
| 组件 | 影响 |
|---|---|
一台 evstats | 负载均衡把请求转给其它进程;它没确认的事件由其它进程接着写 |
| Redis | 登录和采集暂停,SDK 把事件留在本地队列重试;已经写进 ClickHouse 的数据不受影响 |
| 某个分片 | 只有落在它上面的项目查询失败;采集照常进队列,恢复后自动写入 |
s0 | 系统数据不可用,管理界面和查询不可用;恢复后照常 |