又快又省:SLS 新版日志聚类,从海量日志发现模式的智能引擎
本文联合创作作者:刘进步(石季)、隰宗正(霜键) 在分布式系统日益复杂的今天,一个典型的微服务架构可能包含数十甚至上百个服务组件,每个组件都在持续产生日志。据统计,一个中等规模的互联网应用,每天产生的日志量可达数 TB。面对如此海量的数据,传统的日志分析方式面临严峻挑战: 信息过载: 当告警触发时,工程师打开日志系统,面对的是刷屏般的日志流。哪些是关键信息?哪些是噪音?全凭经验判断。 关键字依赖: 传统方式依赖预设的关键字(如 ERROR、Exception)进行过滤。但问题是未预料到的异常模式可能被完全忽略。 上下文割裂: 即使找到了可疑日志,理解其含义仍需要大量的上下文信息。同一类问题可能以略有差异的形式出现成千上万次,人工难以归纳。 阿里云日志服务(SLS,Simple Log Service)是一款面向日志场景的云原生观测与分析平台,为用户提供日志采集、存储、查询、分析等一站式服务。作为日志分析的核心能力之一,SLS 很早就推出了日志聚类功能(以下称“旧版日志聚类”),帮助用户从海量日志中自动提取模式。 旧版日志聚类采用“写入时聚类”的架构:在日志写入时预先计算聚类索引,将每条日志映射到对应的模式。这种方式的优点是聚类较全面,但也带来了额外的索引存储成本,对于大规模日志场景可能成为负担。 本文介绍的“新版日志聚类”是对旧版的一次架构升级,采用了“查询时聚类”的全新设计思路。 它不再需要预先建立聚类索引,而是在用户发起查询时实时计算日志模式,从而实现了零额外索引流量、更灵活的分析能力和更优的成本效益。 新版日志聚类的核心思想是:让机器自动发现日志中的模式。 日志聚类基于一个关键洞察:虽然系统的日志量可能十分庞大,但这些日志往往是由有限数量的日志输出语句产生的。每一个日志输出语句产生的日志格式相同,可以用同一个“日志模板”来表示。 例如,以下三条日志: 可以被归纳为一个模板: 其中 通过这种抽象,原本需要逐条查看的成千上万条日志,被压缩为少数几个日志类别。工程师可以先在日志模板层面定位问题,然后再深入查看具体日志样例——这正是日志聚类带来的认知升级。 与旧版日志聚类相比,新版日志聚类最大的架构优势是零额外索引流量。 旧版日志聚类需要在数据写入时预先计算聚类索引,这意味着每条日志都会产生额外的索引存储成本。对于大型 LogStore,这个成本可能相当可观。 新版日志聚类采用了完全不同的策略:它基于已有的字段索引,在查询时实时计算日志模板。这种“查询时聚类”的方式,避免了预索引带来的存储开销,同时也让聚类结果能够即时反映最新的日志数据。 当日志量特别大时(例如时间窗口内有数千万条日志),全量分析既不现实也无必要。新版日志聚类内置了智能采样策略: 采样算法采用伯努利采样(Bernoulli Sampling),确保每条日志被选中的概率相等,从而保证采样结果的代表性。在模型构建阶段,系统会采样最多 5 万条日志用于模式发现;在结果匹配阶段,会采样最多 20 万条日志进行模式匹配和统计。 这种分层采样的设计,让系统在处理海量数据时仍能保持秒级响应,同时不会显著影响聚类效果。 日志聚类的核心挑战之一是准确区分日志中的“变量部分”和“常量部分”。新版日志聚类采用了更智能的变量识别算法,能够处理多种复杂场景: 数值型变量: 自动识别数字、IP 地址、端口号等数值模式,并支持范围统计。 枚举型变量: 对于取值有限的变量(如状态码、服务名),系统会自动统计 Top N 取值分布。 复合型变量: 对于复杂的变量模式(如 UUID、Trace ID),系统会智能识别其边界。 变量摘要( 新版日志聚类的核心计算逻辑通过 SLS 的 SPL 实现,形成了一条完整的聚类流水线: 对于对比分析场景,merge_log_patterns 可以将两个时间段的聚类模型合并,从而在统一的模式空间中进行对比,发现新增、消失或变化的日志模式。 在前端实现上,日志聚类组件面临的核心挑战是:如何高效地渲染和交互大量的聚类结果? 聚类结果可能包含数百甚至数千个日志模式。系统采用分页加载的方式,每页只渲染 15 条记录,配合虚拟滚动技术,确保界面始终流畅: 日志模板中的变量部分需要高亮显示,并支持点击查看变量分布。系统实现了一个专门的 在对比分析模式下,每个日志模式需要同时展示两个时间段的数据分布。系统使用双色柱状图实现这一需求: 通过视觉对比,用户可以直观发现: 在日志聚类页面发现了问题模式后,如何查看该类别的全部日志? 新版日志聚类通过正则表达式解决了这个问题。每个日志模板都会自动生成对应的正则表达式( 这种设计将聚类分析与原始日志查询无缝连接,让用户能够在发现问题模式后,立即深入查看具体的日志详情。 某电商平台在促销活动期间收到大量告警。运维工程师打开日志聚类页面: 整个过程不到 5 分钟,而传统的关键字搜索可能需要尝试多个关键字组合,耗时数倍。 开发团队发布了新版本,需要评估对日志模式的影响: 当日志库中包含多个模块的日志时,可以使用分组聚类功能: 这种分层分析的方式,特别适合大型系统的日志分析,避免了不同模块的日志相互干扰。 在设计新版日志聚类时,我们面临一个关键的架构决策:是在写入时预计算聚类索引,还是在查询时实时计算? 最终我们选择了后者,主要基于以下考虑: 灵活性: 预计算方式需要预先定义聚类的字段和参数,一旦配置就难以更改。而查询时计算允许用户动态选择聚类字段、过滤条件和时间范围,提供了更大的灵活性。 成本效益: 并非所有日志都需要聚类分析。预计算方式对所有日志统一处理,产生不必要的成本。查询时计算则是“按需付费”,只有真正需要分析时才消耗资源。 算法演进: 聚类算法是一个持续优化的领域。查询时计算让我们可以随时升级算法,新的分析自动受益于最新的算法改进,而无需重新处理历史数据。 采样是新版日志聚类的关键设计之一。一个自然的担忧是:采样会不会遗漏重要的日志模式? 我们的策略是“分阶段采样”: 模式发现阶段: 采样 5 万条日志用于发现模式。由于日志模式的数量通常远小于日志数量(这是日志聚类的基本假设),5 万条采样通常足以发现绝大多数模式。 模式匹配阶段: 采样 20 万条日志进行统计。这个阶段的采样主要影响数量统计的精度,而非模式的发现。 变量统计阶段: 对于每个模式,保留 Top 10 的变量取值。这足以让用户理解变量的分布特征。 实践表明,这种分层采样策略在绝大多数场景下能够提供足够准确的聚类结果,同时保持秒级的查询响应。 新版日志聚类代表了日志分析领域的一次范式转变:从被动的关键字搜索,到主动的模式发现;从人工的逐条排查,到智能的类别归纳。 它的核心价值在于: 展望未来,日志聚类还有更多可能性: 我们相信,随着这些能力的不断演进,日志分析将从一项繁琐的运维任务,转变为一种智能化的系统洞察工具。日志记录着每一次请求、每一个异常、每一行代码的执行轨迹。然而,当日志量从每天几万条膨胀到数亿条时,传统的关键字搜索和人工筛选方式已经力不从心。新版日志聚类正是为了解决这一困境而设计——它能够从海量日志中自动发现日志类别,提取日志模板,让工程师从“大海捞针”式的排查中解放出来。
为什么需要智能日志聚类
1.1 “日志洪水”中的认知困境
1.2 SLS 日志聚类的演进
1.3 从“看日志”到“懂日志”
Got exception while serving block-123 to /10.251.203.149
Got exception while serving block-456 to /10.251.203.150
Got exception while serving block-789 to /10.251.203.151Got exception while serving <BLOCK_ID> to /<IP><BLOCK_ID> 和 <IP> 是变量部分,会随每条日志变化;其余部分是常量,在同类日志中保持不变。核心设计理念
2.1 零索引流量:轻量化的成本优势
特性 新版日志聚类 旧版日志聚类 索引方式 查询时实时计算 写入时预计算索引 额外索引流量 无 有 成本模型 纯分析操作 按新增索引流量计费 2.2 智能采样:平衡精度与性能
// 采样策略:当日志量超过阈值时,自动降采样
const sampleQuery = logCount > 50000
? `| sample -method='bernoulli' ${getSampleNumber(logCount, 50000)}`
: ''2.3 变量智能识别:超越简单的模式匹配
// 变量摘要统计
| extend var_summary = summary_log_variables(variables_arr, '{"topk": 10}')var_summary)不仅记录变量的取值样例,还包含变量的类型推断(range / enum / gauge)和分布统计,为后续的深入分析奠定基础。技术实现亮点
3.1 SPL 算子驱动的聚类流水线
3.1.1 第一阶段:模型构建
*
| stats content_arr = array_agg("Content")
| extend ret = get_log_patterns(
content_arr,
ARRAY['分隔符列表'],
cast(null as array(varchar)),
cast(null as array(varchar)),
'{"threshold": 3, "tolerance": 0.1, "maxDigitRatio": 0.1}'
)
| extend model_id = ret.model_idget_log_patterns 是核心的模式提取算子。它接收一组日志内容,通过聚类算法自动发现其中的日志模板。算法参数包括:threshold:识别某个位置的 token 是否是变量的最小值支持度,threshold 越大,token 越不容易被判定为变量。tolerance:变量识别的容忍度,容忍度越小,高频出现的 token 越容易被判定为常量。建议使用默认值。maxDigitRatio:数字字符的最大比例阈值。3.1.2 第二阶段:模式匹配
*
| extend ret = match_log_patterns('${modelId}', "Content")
| extend pattern_id = ret.pattern_id, pattern = ret.pattern,
pattern_regexp = ret.regexp, variables = ret.variables
| stats event_num = count(1), hist = histogram(time_bucket_id)
by pattern_idmatch_log_patterns 将每条日志与已发现的模式进行匹配,提取出:pattern_id:所属的模式 ID。pattern:日志模板。pattern_regexp:模式的正则表达式。variables:变量部分的具体取值。3.1.3 第三阶段:对比分析(可选)
| extend ret = merge_log_patterns('${modelId1}', '${modelId2}')
| extend model_id = ret.model_id3.2 前端渲染:高性能的大数据展示
3.2.1 虚拟滚动与分页
// 分页逻辑
const [currentPage, setCurrentPage] = useState<number>(1)
const pageSize = 15
const pagedResult = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize
return filteredResult.slice(startIndex, startIndex + pageSize)
}, [filteredResult, currentPage])3.2.2 高亮变量的交互设计
Highlight 组件,能够:
3.2.3 对比视图的双柱状图



3.3 正则反查:打通分析到查询的最后一公里
pattern_regexp),用户可以复制这个正则表达式,配合 regexp_like 算子进行精确查询:* | SELECT * FROM log WHERE regexp_like(Content, '复制的正则表达式')
典型使用场景
4.1 场景一:快速定位故障日志
* and not LEVEL: INFO。Got exception while serving <*> to /<IP>: Connection timeout。<IP> 集中在 10.251.xxx.xxx 网段。4.2 场景二:版本发布对比分析
4.3 场景三:多模块分组分析
Component 或 ServiceName。算法设计思考
5.1 为什么选择“查询时聚类”?
5.2 采样的艺术:如何在效率和精度间取得平衡
总结与展望