基于日志的 JuiceFS 可观测最佳实践
资料来源:火山引擎-开发者社区
业务背景
JuiceFS 是一款面向云原生设计的高性能分布式文件系统,在 Apache 2.0 开源协议下发布。提供完备的 POSIX 兼容性,可将几乎所有对象存储接入本地作为海量本地磁盘使用,亦可同时在跨平台、跨地区的不同主机上挂载读写。每个 JuiceFS 客户端都有访问日志,其中详细记录了文件系统上的所有操作,如操作类型、用户 ID、用户组 ID、文件 inode 及其花费的时间。
访问日志可以有多种用途,如性能分析、审计、故障诊断。但日志分布在多个客户端和服务器,收集和分析都比较困难,难以建立完整的监控和告警机制。
火山引擎日志服务(TLS)是一站式的可观测数据管理平台,提供日志/链路等观测数据的采集、存储、查询、分析、可视化和告警等功能。日志服务 TLS 可对 JuiceFS 访问日志分析,提供以下帮助:
| 日志服务 TLS 功能 | 说明 | | --- | --- | | 解决日志分散问题 | 统一采集和管理分布在各个客户端和服务器的 JuiceFS 访问日志。 | | 解决日志结构不统一问题 | 采集时进行解析处理,将不同格式的日志统一成便于分析的日志结构。 | | 支持复杂查询分析 | 利用 TLS 的 SQL 分析能力,实现对日志的深度挖掘。 | | 提供即开即用的分析大盘 | 针对 JuiceFS 日志分析常见场景预设分析模板,提供即开即用的分析大盘。 | | 提供实时监控告警能力 | 配置监控规则,实时监控日志,及时发现并响应异常。 |
本文从实操的角度,以如何将 JuiceFS 的访问日志采集到日志服务 TLS 内,并且可以从哪些维度进行分析为例进行介绍。
日志分析实战
整体的日志分析流程为日志采集、日志分析大盘和实时监控告警。流程图如下:

日志采集
JuiceFS 的访问日志格式如下:
其中每一列的含义为:
2021.01.15 08:26:11.003330
:当前操作的时间。[uid:0,gid:0,pid:4403]
:当前操作的用户 ID、用户组 ID、进程 ID。write
:操作类型。(17669,8666,4993160)
:当前操作类型的输入参数,如示例中的 write 操作的输入参数分别为写入文件的 inode、写入数据的大小、写入文件的偏移,不同操作类型的参数不同。OK
:当前操作是否成功,如果不成功会输出具体的失败信息。<0.000010>
:当前操作花费的时间(以秒为单位)。
上述日志是保存在 JuiceFS 客户端内,但输出的各个日志字段在同一行,无法直接进行统计,需要进行提取解析。日志服务 TLS 自研的日志采集器 Logcollector 提供多种采集模式和丰富的日志处理插件,可以将上述日志解析提取出来。
日志采集 &日志解析
LogCollector 是日志服务 TLS 自研的日志采集器,服务于火山引擎客户以及字节跳动内部业务。特点是兼容性好、性能高,可适配多家云厂商和 idc 环境,单 core 性能最高可达 100MB/s。同等性能要求下,对资源的消耗更少,可节省更多资源成本。
在服务器中安装 LogCollector 之后,通过控制台即可下发采集规则、快速接入日志服务。在目标服务器中安装 LogCollector 之后(参考安装 LogCollector(宿主机):https://www.volcengine.com/docs/6470/72007),根据不同的性能要求,可设置不同的初始参数来调整 CPU 阈值和内存阈值,可参考设置 LogCollector 启动参数(https://www.volcengine.com/docs/6470/1219476)。
通过在日志服务控制台中依次创建机器组、日志项目、日志主题及采集规则等资源,即可上报服务器的日志数据到日志服务。可参考文档 TLS 快速入门(https://www.volcengine.com/docs/6470/71978)。

值得注意的是,由于 JuiceFS 客户端的访问日志详细记录了文件系统的所有操作,且每个文件系统操作对应的访问日志结构有所差异,所以需要使用 LogCollector 条件处理器插件进行链式结构化处理,详细配置如下所示:
上述 LogCollector 条件处理器插件的整体流程是:
1.通用解析。
针对 JuiceFS 客户端访问日志的原始内容字段 \_\_content\_
\
,使用正则表达式提取基础字段。
时间戳(time),格式如
2024.06.30 12:34:56.789
用户 ID(uid)
组 ID(gid)
进程 ID(pid)
操作类型(op),如 write、read、open 等
操作结果(result),后续根据操作类型进一步详细解析
2.操作结果(result)解析。
针对不同操作类型(op)的操作结果(result)的详细解析。根据 op
字段的值,选择对应的正则表达式对 result
字段的值进行更细粒度的解析,提取具体的操作参数和状态。
write 操作
匹配模式:
(\d+),(\d+),(\d+),(\d+)\): (\w+) <(.*)>
提取字段:inode、length、offset、filehandle、status、delay
read 操作
匹配模式:
(\d+),(\d+),(\d+)\): (\w+) \((\d+)\) <(.*)>
提取字段:inode、length、offset、status、unknown、delay
unlink 操作(文件删除)
匹配模式:
(\d+),(.*)\): (\w+) <(.*)>
提取字段:inode、filename、status、delay
create 操作(文件创建)
匹配模式:
(\d+),(.*),(.*)\): (\w+) \((\d+),(.*)\) \[fh:(\d+)\] <(.*)>
提取字段:parent_inode、filename、mode、status、inode、others、filehandle、delay
open 操作
匹配模式:
(.*): (\w+) \[fh:(\d+)\] <(.*)>
提取字段:inode、status、filehandle、delay
lookup 操作(查找)
匹配模式:
(\w+),(\w+)\): (\w+) \((\d+),(.*)\) <(.*)>
提取字段:parent_inode、filename、status、inode、mode、delay
flush 操作
匹配模式:
(\w+),(\w+)\): (\w+) <(.*)>
提取字段:inode、filehandle、status、delay
TLS 索引字段设计
选择日志项目 > 日志主题 > 索引配置,即可配置具体的索引类型及其详细字段。详情可参考配置索引(https://www.volcengine.com/docs/6470/112665)。

其中,键值索引中各字段的解释如下:
| 字段 key | 字段 type | 字段说明 | | --- | --- | --- | | uid | long | 用户 ID(User ID),执行该操作的用户身份标识。 | | gid | long | 组 ID(Group ID),执行该操作的用户所属的用户组标识。 | | pid | long | 进程 ID(Process ID),执行该操作的进程编号,便于追踪具体进程。 | | op | text | 操作类型(Operation),日志中记录的文件系统操作。如 write、read、open、create、unlink 等。 | | delay | double | 操作延迟,通常表示此次操作耗时,可能是毫秒或微秒级别。 | | inode | long | 文件的索引节点号(inode),唯一标识文件系统中的一个文件。 | | length | long | 写入的数据长度(字节数),表示此次写操作写了多少字节。 | | offset | long | 写入偏移量,表示从文件的哪个位置开始写入数据。 | | filename | text | 查找的文件名。 | | filehandle | long | 文件句柄,系统内部用于标识打开的文件实例。 | | status | text | 操作状态,例如 OK 表示写操作成功,其他值可能表示错误类型。 | | mode | text | 文件权限和类型信息。 | | parent_inode | long | 查找操作的父目录索引节点号。 | | time | text | 日志时间。 | | others | text | 其他信息,可能包含额外的元数据或标志。 | | unknown | text | 未知字段,日志中可能是某种内部状态码或额外信息,需结合具体日志格式确认。 |
日志分析大盘
在 JuiceFS 的使用场景中,有许多关键指标需要重点关注,例如读、写、删除等操作的次数与数据量统计、IO 大小与执行耗时的分布,以及顺序读占比、覆盖写情况等。
针对核心的分析场景,TLS 已将其提炼为仪表盘模板,可基于模板一键快速创建出分析大盘。

同时,日志服务 TLS 还支持自定义仪表盘,下面选取了其中几个核心分析场景,介绍如何通过火山引擎日志服务 TLS 的查询分析和可视化能力实现对日志的深入分析。
仪表盘过滤器的使用
在实际日志分析中,经常需要对特定文件或路径进行筛选。TLS 仪表盘提供了过滤器功能,用户可在界面上创建过滤器,并配置过滤条件。
例如,访问日志存储时,不同任务的日志存在不同的文件路径下,便可以基于字段 \_\_path\_
\
创建名为 task
的过滤器,取值来源于实际日志的 SQL 运行结果。

配置好后,用户即可在界面上通过勾选需要分析的日志文件,实现针对性分析。

原始 SQL(分析所有写操作)示例:
应用过滤器后,系统自动向 SQL 中添加了
\_\_path\_
\
相关过滤条件:
通过设置过滤器,可以快速筛选出关注的日志范围并生成统计结果。
从上述示例 SQL 中可以获取到 \_\_path\_
\
为 extract.log、bev_cpu.log 的 write 操作对应的总次数、写入数据的平均大小、平均执行时间、最大执行时间、最小执行时间,对应如下表格:

写 IO 大小分布
写操作的 IO 大小分布能反映业务负载特征。
写 IO 大小分布示例 SQL:
通过 CASE WHEN
把写操作长度分为不同区间,并统计每个区间的次数。
在 TLS 仪表盘中可将结果以饼图形式展示,更直观地反映不同写 IO 大小的占比,例如 128K~256K 区间的写操作次数最多,占比 33.35%。

顺序读占比分布
顺序读是指当前读操作与上一次读操作的偏移范围连续(即上次读的结束位置 = 当前读的起始位置)。
顺序读占比分布示例 SQL:
通过 WITH ... AS ...
子句定义了一个临时表 flagged
,可以复用在其他 SQL 中以简化 SQL 的书写。
临时表
flagged
句式分析:\_\_path\_\_, inode
:根据这两个字段联合分区。time
:在每个分区内按照该字段排序。LAG(offset)
:取排序后位于当前数据上一行的 offset 值。临时表包含句式:
LAG(...) OVER(PARTITION BY ... ORDER BY ...)
临时表句式 SQL 示例:
LAG(offset) OVER (PARTITION BY \_\_path\_\_, inode ORDER BY time )
临时表句式结果示例:

表格中同色背景行是相同的 \_\_path\_\_, inode
,属于同一个分区,在这个分区内按 offset, time
进行升序排列:
对于第 1 行,不存在上一行,因此结果
offset
为 NULL 。对于第 2 行,上一行是第 1 行,因此结果
offset
取第 1 行的 offset,也就是 32。对于第 5 行,由于其所在分区(橙色部分)只有 1 行,不存在上一行,因此
结果offset
也是 NULL。临时表中
CASE WHEN
子句分析:
如果当前读操作起始位置 offset 等于紧挨着的上一次读操作的 offset + length(也就是上次读的结束位置),那么当前读操作就是一次顺序读,此时返回当前读操作的长度 length,否则返回 0。这就是一次顺序读操作的 IO 大小,也就是 sequentialReadSize。
然后按 \_\_path\_\_, inode
分组,求每个文件的顺序读总大小 sum(sequentialReadSize)
与读总大小 sum(length)
的占比 ratio
,再按 ratio
分组求文件个数。
其中
GROUP BY 1
为 SQL 简写,1
代表SELECT
子句中的第一个字段,即重命名后的scope
。
得到类似以下的饼状图,即顺序读 IO 占比在 0~20% 的文件数量有 3139 个,占比为 48.73%。

覆盖写分析
覆盖写指当前写操作与上一次写操作的偏移范围有重叠。
例如,当前写范围 [32, 1024)
与上次写范围 [512, 2048)
有交集。
覆盖写分析示例 SQL:
第一个和第二个 CASE WHEN
子句计算的分别是同一个日志文件(__path__)、同一个操作对象(inode),每次写操作紧挨着的上一次写操作对应的 offset、length。
然后在每一行写操作的数据中加入了其紧挨着的上次写对应的 offset 和 length,并重命名为 lastOffset
和 lastLength
。
即对于每一行数据,都有两个数值范围 [offset, offset + length),[lastOffset, lastOffset + lastLength),这两个左闭右开的数值范围的交集是:
于是上述 覆盖写
SQL 的思路是:
1.通过 LAG(...) OVER(PARTITION BY ... ORDER BY...)
获取每次写操作紧挨着的上次写操作对应的 offset 和 length。
2.通过 CASE ... WHEN ... THEN ... ELSE ... END
将不存在上次写操作的 lastOffset 和 lastLength 赋值为 -1。
3.剔除 lastOffset 和 lastLength 为 -1 的写操作,然后计算与上次写操作的重叠大小,也就是覆盖写的大小。

文件生命周期分布
文件生命周期定义为从创建到删除的时间长度。
文件生命周期分布示例 SQL:
SQL 中的 JOIN 解析:
unlinkTable LEFT JOIN createTable
:用创建文件时记录的 inode 与删除文件时记录的 inode 进行关联。filename
和\_\_path\_
\
:标识文件的唯一性。DATE\_PARSE
:将毫秒时间戳转换为格式化的日期类型。 通过关联创建
和删除
两个操作,可以获取到同一个文件的创建时间和删除时间。SQL 中的
CASE WHEN
解析:
DATE\_DIFF
计算文件的 删除时间(unlinkTime)
减去创建时间(createTime)
得到的分钟数,然后将其划分为四个范围,即:'010min'、'1030min'、'30~60min'、'大于 60min'。
最后按上面的四个范围统计文件数量就可以画出以下的饼状统计图:

以上分析示例展示了从简单的操作统计,到复杂的顺序读检测、覆盖写检测、生命周期分析,均可以通过 SQL + TLS 可视化仪表盘轻松实现,为 JuiceFS 用户提供了灵活且深入的访问日志洞察能力。
实时监控
日志服务还提供强大的告警能力,可以监控规则,实时监控日志,及时发现并响应异常。 本章节通过“监控各个文件的写耗时是否有耗时异常过长”这一典型场景,介绍如何通过火山引擎日志服务(TLS)的日志告警功能,来实现监控告警能力。详情可参考快速设置日志告警(https://www.volcengine.com/docs/6470/1218714)。
创建内容模板
内容模板是一种预定义的文本模板,日志服务会按照内容模板中定义的内容向指定的通知渠道发送告警通知。日志服务提供默认模板 default-template,便于您直接使用。本案例基于默认模板创建了自定义内容模板。

创建 Webhook 集成配置
Webhook 集成配置用于管理您的飞书/企业微信/钉钉/自定义通知机器人的 Webhook 地址。(此处以飞书通知机器人为例。)

创建通知组
通知组用于管理告警通知的行动策略与通知对象。创建通知组时,需选择前两步创建的 webhook 和内容模板。

创建告警策略
告警策略中需要设置基本信息、监控任务和告警通知等配置。日志服务会基于告警策略定期分析日志,并定期将符合触发条件的告警发送给指定的通知对象。

监控对象和执行语句:选择对应的日志主题,输入对应的分析语句,每个任务会定期通过该语句进行检索与分析。
触发条件:检索分析结果中存在数据满足条件表达式时,触发告警。
图中案例表示如果 consumedTime 大于 2 则触发告警,级别为警告。每隔 10 分钟执行一次检查。
通知组:选择前一步所创建的通知组。
附加通知内容:添加内容变量 FireResults,可以将触发告警的数据通知出来。
查看告警
监控任务会周期性执行,当告警触发时,便会通过所配置的通知组进行通知。

由于篇幅有限,还有许多场景无法逐一介绍,欢迎前往日志服务 TLS 控制台进行体验。
结语
借助火山引擎日志服务 TLS 的一站式日志采集、存储、查询、分析、可视化、监控告警能力,能够帮助 JuiceFS 的用户高效解决日志分散、结构复杂等问题,实现从日志采集解析到多维分析、实时监控的全链路可观测能力。
无论是基础的操作统计,还是复杂的顺序读检测、覆盖写分析,TLS 的 SQL 查询与可视化功能均能轻松应对,并通过预设仪表盘模板可一键创建出同款仪表盘。
评论