阿里云云原生挑战官方用例SPL
GetPatternsAnalyzer
# 定义一个叫做t0的临时表格
with t0 as (# 从原始日志表log中选择spanName和ServiceName两个字段select spanName, serviceName, # # JSON_EXTRACT_SCALAR 是 SPL 内置函数,用于从 JSON 字符串中提取标量值# 路径 '$["k8s.pod.ip"]' 表示访问 JSON 对象中键为 "k8s.pod.ip" 的值# 提取结果命名为 pod_ipJSON_EXTRACT_SCALAR(resources, '$["k8s.pod.ip"]') AS pod_ip,JSON_EXTRACT_SCALAR(resources, '$["k8s.node.name"]') AS node_name,JSON_EXTRACT_SCALAR(resources, '$["service.version"]') AS service_version, # 设置异常标签if((statusCode = 2 or statusCode = 3), 'true', 'false') as anomaly_label, # 使用if判断 statusCode 是否为 2 或 3。如果是,返回 1;否则返回 0。# 然后使用 cast(... as double) 将整数转为双精度浮点数。# 实际返回是否出错的数值表示 0或者1cast(if((statusCode = 2 or statusCode = 3), 1, 0) as double) as error_count from log # 设置占位符 实际使用时将被替换成具体的执行添加# 在当前案例中的是上一个阶段生成的根因span条件where {self.condition}
),
假设输出的t0结构如此
spanName | serviceName | pod_ip | node_name | service_version | anomaly_label | error_count |
---|---|---|---|---|---|---|
“/login” | “auth-service” | “10.1.2.3” | “node-a” | “v1.2.0” | “false” | 0.0 |
“/getProfile” | “user-service” | “10.1.2.4” | “node-b” | “v2.0.1” | “true” | 1.0 |
“/login” | “auth-service” | “10.1.2.5” | “node-a” | “v1.2.0” | “false” | 0.0 |
# array_agg(column) 是 SPL(以及标准 SQL)中的聚合函数,用于将某一列的所有值收集到一个数组(list)中。
t1 as (select array_agg(spanName) as spanName, array_agg(serviceName) as serviceName, array_agg(pod_ip) as pod_ip, array_agg(node_name) as node_name, array_agg(service_version) as service_version, array_agg(anomaly_label) as anomaly_label, array_agg(error_count) as error_count from t0
),
# 执行后,t1 只有一行,包含 7 个字段,每个字段都是一个数组,数组长度等于 t0 的行数
# 目前来看 t1保留了很多的字段 但是只使用了spanName 和 serviceName 可能是为后续进行了一些保留
# t1
{"spanName": ["/login", "/getProfile", "/login"],"serviceName": ["auth-service", "user-service", "auth-service"],"pod_ip": ["10.1.2.3", "10.1.2.4", "10.1.2.5"],"node_name": ["node-a", "node-b", "node-a"],"service_version": ["v1.2.0", "v2.0.1", "v1.2.0"],"anomaly_label": ["false", "true", "false"],"error_count": [0.0, 1.0, 0.0]
}
# 将 t1 中的 spanName 和 serviceName 两个数组字段封装成一个结构化的行对象,便于作为整体传递给后续处理逻辑(如自定义函数),强调这两个字段的逻辑关联性。
t2 as (# row( ) 是一个用于构造结构化复合数据类型的函数,它返回一个 ROW 类型(也称为“行类型”或“结构体”)的对象。 select row(spanName, serviceName) as table_row from t1
),
# 返回一个 ROW(ARRAY<STRING>, ARRAY<STRING>) 类型的值
# 一个包含两个元素的结构体;第一个元素是 spanName 数组;第二个元素是 serviceName 数组
# 如果没有显式指定名称,SPL 会默认使用 col0, col1, col2, ... 作为内部字段名。
# table_row.col0 → 对应 spanName
# table_row.col1 → 对应 serviceName
# t2
{"table_row": {"col0": ["/login", "/getProfile", "/login"],"col1": ["auth-service", "user-service", "auth-service"]}
}
# get_patterns用于从 trace 数据中提取高频或异常的调用序列模式
# table_row是上面的数组 ARRAY['spanName', 'serviceName'] 指明 col0 代表 spanName col1 代表 serviceName
t3 as (select get_patterns(table_row, ARRAY['spanName', 'serviceName']) as ret from t2
)
select * from t3
# 该函数将统计所有(spanName,serviceName)的组合
# 某题实际的响应内容如下
{'ret': '[["serviceName=payment"],[118],null,null]'}
DiffPatternsAnalyzer
# 进行原始日志过滤以及字段的提取 大部分内容同上
with t0 as (select spanName, serviceName, JSON_EXTRACT_SCALAR(resources, '$["k8s.pod.ip"]') AS pod_ip,JSON_EXTRACT_SCALAR(resources, '$["k8s.node.name"]') AS node_name,JSON_EXTRACT_SCALAR(resources, '$["service.version"]') AS service_version, # 根据上面给出的根因span进行标记if(({self.condition}), 'true', 'false') as anomaly_label, cast(if((statusCode = 2 or statusCode = 3), 1, 0) as double) as error_count from log where statusCode > 0
)
# 将 t0 中所有行的每个字段聚合成一个数组 同上
t1 as (select array_agg(spanName) as spanName, array_agg(serviceName) as serviceName, array_agg(pod_ip) as pod_ip, array_agg(node_name) as node_name, array_agg(service_version) as service_version, array_agg(anomaly_label) as anomaly_label, array_agg(error_count) as error_count from t0
)
# 只关心服务与是否异常的关联性 同上
t2 as (select row(serviceName, anomaly_label) as table_row from t1
)
t3 as (select diff_patterns(table_row, -- 包含维度字段和标签字段的 ROW 对象ARRAY['serviceName', 'anomaly_label'], -- 字段名映射数组'anomaly_label', -- 用于分组的标签列,用于划分正常数组和异常数组'true', -- 正样本标签值'false' -- 负样本标签值 ) as ret from t2
)
该 diff_patterns
函数首先会对输入的所有记录按照异常标签(anomaly_label
)进行分组:将 anomaly_label = 'true'
的记录划分为“异常组”(Group A),anomaly_label = 'false'
的记录划分为“正常组”(Group B),并分别提取两组中对应的 serviceName
值。
接着,函数会统计每个服务名称在异常组和正常组中的出现频次,例如某个服务如 payment-service
可能在异常组中出现了 200 次,而在正常组中仅出现 50 次,而另一个服务如 auth-service
可能在异常组中出现 50 次,却在正常组中出现 1000 次。
基于这些频次数据,函数进一步计算关键的差异指标,包括提升度(Lift,即异常组中该服务的占比除以正常组中的占比)、差异比例(Diff Ratio)以及统计显著性(如 p-value),用以量化每个服务与异常行为的关联强度。最后,函数会根据这些指标(通常是 Lift 或显著性)对服务进行排序,并返回最显著的差异模式列表,从而帮助用户快速识别出与异常高度相关的核心服务。
具体内容详见差异模式挖掘函数diff_patterns通过分析表格数据的维度和指标差异,识别测试组与对照组之间的显著模式,适用于云数据诊断与分析。
LogPatternAnalyzer
stage1
# t1:聚合所有日志消息为一个数组
with t0 as (select statusCode, # 状态码 statusMessage,# 错误信息描述 serviceName,# 服务名# 拼接出一个人工日志文本 如[serviceName] statusMessageCONCAT('[', serviceName, '] ', statusMessage) as combined_message from log # 对根因节点列表中状态码大于0的部分进行检索where {self.condition} and statusCode > 0
)
# 聚合所有日志消息为一个数组
t1 as (select array_agg(combined_message) as contentsfrom t0
)
# 模拟结果
["[auth-service] Invalid token","[user-service] User not found: id=123","[user-service] User not found: id=456",...
]
# 调用日志发现函数
t2 as (select contents,get_log_patterns(contents, -- 日志行数组ARRAY[' ', '\\n', '\\t', '\\r', '\\f', '\\v', ':', ',', '[', ']'], -- 分词分隔符列表null, -- 时间格式(null 表示不解析时间)null, -- 正则过滤(null 表示不过滤)'{"threshold": 3, "tolerance": 0.3, "maxDigitRatio": 0.3}'-- JSON 字符串,控制聚类参数# "threshold": 3 最小支持度 一个模式至少出现 3 次才被视为有效模板# "tolerance": 0.3 容错率 允许 30% 的 token 差异仍归为同一模式(用于模糊匹配)# "maxDigitRatio": 0.3 若一行日志中数字字符占比 > 30%,可能被过滤或特殊处理(避免纯 ID 日志干扰)) as ret from t1
)
# 提取所需的关键结果 只要模式id和对应的错误信息(可以去阿里云文档看看get_log_patterns的返回内容)
select ret.model_id as model_id,ret.error_msg as error_msg
from t2
stage2
# 效果同上
with t0 as (select CONCAT('[', serviceName, '] ', statusMessage) as combined_messagefrom logwhere {self.condition} and statusCode > 0
)
# 调用match_log_patterns
# 使用指定的模型(model_id)对单条日志(log_line)进行解析,判断是否匹配已知模式,并返回结构化结果。
select match_log_patterns('{model_id}', combined_message) as ret, combined_message
from t0
# 外层循环 筛选并输出结构化结果
select ret.is_matched as is_matched,ret.pattern_id as pattern_id,ret.pattern as pattern,ret.regexp as pattern_regexp,ret.variables as variables,combined_message as content
from (... -- 上述子查询
)
where ret.is_matched = true
limit 1000