前阵子写的日志分析工具NginxPulse,自开源以来,已过去 2 周时间,目前 GitHub 已收获 1.5k 的 star 。收到了不少用户的反馈建议,花了点时间将这些问题都处理了下。
本文就跟大家分享下新版本都解决了哪些问题,优化了哪些内容,欢迎各位感兴趣的开发者阅读本文。
有不少用户反馈说日志文件很大的时候( 10G+),解析速度非常慢,需要解析好几个小时,解析完成之后数据看板的查询也比较慢(接口响应在 5 秒左右)。
于是,我重写了日志解析策略(解析阶段不做 IP 归属地查询,仅入库其他数据,将日志中 IP 记录起来),日志解析完毕后,将记录的 IP 做去重处理,随后去做归属地的查询处理(优先本地的 ip2region 库,远程的 API 调用查询做兜底),最后将解析到的归属地回填至对应的数据库表中,这样一套下来就可以大大提升日志的解析速度。
数据库的数据量大了之后,SQLite 的表现就有点差强人意了,请教了一些后端朋友,他们给了我一些方案,结合我自身的实际场景后,最后选定了 PostgreSQL 作为新的数据库选型。
这套方案落地后,用户群的好兄弟说:他原先需要解析 1 个小时的日志,新版只需要 10 多分钟。

有一部分用户反馈说他非专业人士,这些晦涩的配置对他来说使用门槛太高了,希望能有一个 UI 配置页面,他只需要点一点、敲敲键盘,就能完成这些配置。
我将整个配置流程做成了 4 步,同时也准备一个[演示视频](NginxPulse 支持 UI 配置化了) - https://www.bilibili.com/video/BV1hqzyBVEU9:

因为配置过于庞大,仓库主页浏览 README.md 比较费劲,希望能整理一份 wiki 文档发上去。
花了点时间,简化了下 README ,整理了一份: https://github.com/likaia/nginxpulse/wiki

有部分用户反馈说希望增加更多的筛选条件以及导出 Excel 功能,现在它来了:

概况页面的日期筛选之前放在趋势分析卡片的上方,但是他的切换影响的维度还包含了指标,于是我就调整了下它的位置,新版如下图所示:

至此,文章就分享完毕了。
我是神奇的程序员,一位前端开发工程师。
如果你对我感兴趣,请移步我的个人网站,进一步了解。
1
yjhatfdu2 9 小时 29 分钟前 为什么不用 duckdb 呢?和 sqlite 一样是进程内数据库,但是是列存分析型数据库性能超强,存储效率也会比 pg 和 sqlite 高很多,存储体积小,也基本支持 postgresql 的语法,估计时间可以进一步缩短好几倍。
|
2
dog82 9 小时 21 分钟前
为啥把日志写到 DB 呢?
|
3
yjhatfdu2 9 小时 5 分钟前 @dog82 估计是便于分析聚合吧,不过存 pg 也确实是效率有点低了,我做的话可能会考虑存 parquet 文件用 duckdb 分析,这样文件可以存文件系统也可以存 s3 ,比较灵活,体积也非常小,10G 文件做到分钟级解析我也有信心
|
4
MagicCoder OP @yjhatfdu2 好高级的词汇😂,我研究下 duckdb 之前没听过
|
5
concernedz 8 小时 46 分钟前
日志不是用 mongodb 么
|
6
yjhatfdu2 6 小时 55 分钟前
试了下,使用 golang 并行解析 11G 的 nginx accesslog ,同样适用 ip2region 解析地理位置,解析 useragent 字段,8 个线程,写入 parquet 文件,在我 m1max 老机器上可以在 40 秒左右完成。然后使用 duckdb 直接查询,7000 多万条数据,根据状态码 group by count 聚合大概 0.11 秒,还是非常适合这个场景的,整个 dashboard 尤其是只分析时间段的,应该秒级全出。
select count(*),status from 'parquet_out/*.parquet' group by status; ┌──────────────┬────────┐ │ count_star() │ status │ │ int64 │ int32 │ ├──────────────┼────────┤ │ 16455120 │ 200 │ │ 420 │ 413 │ │ 58349130 │ 404 │ │ 261330 │ 400 │ │ 8310 │ 500 │ │ 60 │ 408 │ │ 3540 │ 501 │ │ 3537120 │ 405 │ │ 90 │ 403 │ │ 7230 │ 206 │ │ 15630 │ 304 │ │ 4980 │ 499 │ ├──────────────┴────────┤ │ 12 rows 2 columns │ └───────────────────────┘ Run Time (s): real 0.112 user 0.937740 sys 0.047321 |
7
yjhatfdu2 6 小时 46 分钟前
然后使用 create table access_log as select * from 'parquet_out/*.parquet'; 创建 duckdb 的表并导入 parquet 的所有数据,耗时 20 秒,之后查询可以再快一倍,最终 duckdb 存储 7000 多万条记录占用硬盘 1.9G ,原始 access.log 一共 11G
|
8
MagicCoder OP @yjhatfdu2 卧槽 这性能可以
|
9
yjhatfdu2 6 小时 34 分钟前
我看了下,你这里用了大量的维度表、预聚合、分表来提高性能,其实用 duckdb 性能足够,单机 10 亿条都用不着干这些事。可以极大的简化后端,减少存储占用和提高解析和写入速度
|
10
MagicCoder OP @yjhatfdu2 对的,我后端做了大量的优化工作提升性能,感觉 duckdb 确实可行😂
|
11
MagicCoder OP @yjhatfdu2 之前朋友还给我推荐过 clickhouse ,我就是看他太耗费资源了,我这个场景还用不到
|
12
yjhatfdu2 6 小时 13 分钟前
@MagicCoder clickhouse 性能上也是可以的,但是部署维护复杂,稍微老点的版本对于 update/delete 效率非常低,资源消耗也是很高。duckdb 适合直接平替 sqlite ,单机模式下很适合。当然你这里如果需要初始化也做的非常快,还是需要做一些工程优化的,如果是 duckdb 直接使用 golang 的驱动,使用 appender 接口进行写入,这个场景也就 20-30w 行一秒,我是尝试了直接使用 go-parquet ,每个线程独立直接写入 parquet 文件,最后再用 duckdb 执行 sql 直接导入,后续再使用 go 的 database/sql 接口写入增量数据,这样才能做到初始化的极速、后续处理的方便。
|
13
MagicCoder OP @yjhatfdu2 懂了,非常感谢,我研究下你说的这个方案
|
14
XyIsMy 5 小时 21 分钟前
@MagicCoder duckdb ,可以直接在 postgresql 上面装一个扩展,开启扩展,会自动转换成 duckdb ,也很方便
|
15
MagicCoder OP @XyIsMy 哦豁,这么高级,那就方便很多了
|
16
goodryb 4 小时 34 分钟前
OP 这下又有活可以干了,等你下个稳定版出来了我试试
|
17
Hermitist 3 小时 46 分钟前
我也在等 OP 的更新, 冒昧问下, 用你的项目在一个暂时免费, 但以后商业化的软件有什么问题吗?
|
18
MagicCoder OP @Hermitist 没事,随便用
|
19
MagicCoder OP @goodryb 行 别忘了🤣
|
20
yjhatfdu2 2 小时 37 分钟前
@MagicCoder duckdb 的 pg_duckdb 插件并不是很适合。首先,你还是需要 pg ,pg 需要单独部署,不能集成到应用里面,作为轻量化的,必然增加了部署的复杂度。第二,pg 需要部署 pg_duckdb 插件需要自行编译或者使用特定的发行版,还是有一定复杂度的。第三,pg_duckdb 主要是方便使用 pg 访问、查询外部 parquet 等文件,或者联合 pg 本地表进行分析查询。但是还是需要走 pg 的协议、parser 等,也没法直接写入 duckdb 自己的库,对于现在这个场景降低了性能,提高了复杂度。
|
21
MagicCoder OP @yjhatfdu2 好吧🤣 那看来还是得完整的用 duckdb
|