在这里插入图片描述

在接口耗时、QPS、错误率、资源利用率等时序指标中,我们经常想知道:

在这一串数据里,到底哪些点算“明显异常”
是不是只有少数点特别离谱,其余都还算正常波动?

本案例基于 Kotlin Multiplatform(KMP)与 OpenHarmony,实现了一个Z-Score 时序异常打分与异常点排行器

  • 使用 Z-Score(标准分)对每个样本点进行“异常程度”打分;
  • 支持配置 TopK,输出最异常的 K 个点(按 \(|Z|\) 从大到小排序);
  • 给出均值、标准差等整体统计信息,帮助理解异常点相对位置与严重程度;
  • 通过 ArkTS 单页面展示完整报告,便于在终端侧快速识别异常点。

一、问题背景与典型场景

在 AIOps 与业务监控中,常见需求包括:

  1. 接口耗时异常点识别
    一串耗时数据中,哪些请求远高于整体水平?Z-Score 可以快速标出这些异常请求。

  2. QPS 或流量异常突刺
    某一时刻的 QPS 是否远超其他时间点?是否属于“流量洪峰”的一部分?

  3. 错误次数或重试次数异常升高
    某些时刻的错误计数或重试计数是否明显偏高,值得重点关注?

  4. 业务指标异常点检测
    对订单量、支付失败量、点击次数等指标进行异常打分,快速定位“异常行为”时段。

Z-Score 是一个经典、简单且易于解释的统计工具:

对于样本 \( x_i \),其 Z-Score 定义为 \( z_i = (x_i - \mu) / \sigma \),
其中 \( \mu \) 为均值,\( \sigma \) 为标准差。\(|z_i|\) 越大,说明 \( x_i \) 越偏离整体平均水平。


二、Kotlin Z-Score 异常打分引擎

1. 输入格式设计

沿用本系列案例的配置风格,本案例支持如下输入形式:

k=5
series=10,12,11,13,50,14,12,11,60,13,12
  • k:TopK 异常点数量(可选,默认可视为 5);
  • series:一串以 , / 空格 / ; / 换行分隔的数值样本。

也支持类似:

series=10,12,11,13,50
12,11,60,13,12

解析逻辑会从每一行中尝试解析所有可转换为 Double 的数值。


2. Kotlin 分析主入口

App.kt 中,我们定义了对外暴露的分析函数,并通过 @JsExport 让 OpenHarmony 端可以调用:

@JsExport
fun zScoreAnomalyRanker(inputData: String): String {
    val sanitized = inputData.trim()
    if (sanitized.isEmpty()) {
        return "❌ 输入为空,请按 k=5\\nseries=10,12,11,13,50,... 形式提供数据"
    }

    val lines = sanitized.lines()
        .map { it.trim() }
        .filter { it.isNotEmpty() }

    var k: Int? = null
    val values = mutableListOf<Double>()

    for (line in lines) {
        when {
            line.startsWith("k=", ignoreCase = true) -> {
                k = line.substringAfter("=").trim().toIntOrNull()
            }
            line.startsWith("series=", ignoreCase = true) -> {
                val parsed = line.substringAfter("=")
                    .split(",", " ", ";", "\n")
                    .mapNotNull { it.trim().takeIf { s -> s.isNotEmpty() }?.toDoubleOrNull() }
                values += parsed
            }
            else -> {
                val parsed = line.split(",", " ", ";", "\n")
                    .mapNotNull { it.trim().takeIf { s -> s.isNotEmpty() }?.toDoubleOrNull() }
                values += parsed
            }
        }
    }

    if (values.size < 3) {
        return "❌ 至少需要 3 个及以上样本点才能计算 Z-Score 异常分数"
    }
    val topK = (k ?: 5).coerceAtMost(values.size)

    val n = values.size
    val mean = values.sum() / n
    val variance = values.fold(0.0) { acc, v -> acc + (v - mean) * (v - mean) } / n
    val std = kotlin.math.sqrt(variance)
    if (std == 0.0) {
        return "ℹ️ 所有样本值完全相同 (均值=$mean, 方差=0),无法基于 Z-Score 识别异常点"
    }

    data class ScoredPoint(val index: Int, val value: Double, val z: Double)

    val scored = values.mapIndexed { idx, v ->
        val z = (v - mean) / std
        ScoredPoint(idx, v, z)
    }

    val ranked = scored.sortedByDescending { kotlin.math.abs(it.z) }
    val builder = StringBuilder()

    builder.appendLine("📊 Z-Score 时序异常打分与异常点排行报告")
    builder.appendLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    builder.appendLine("样本数量: $n")
    builder.appendLine("均值 (mean): ${round2(mean)}")
    builder.appendLine("标准差 (std): ${round2(std)}")
    builder.appendLine("TopK 异常点数量: $topK")
    builder.appendLine()

    builder.appendLine("🧮 原始序列")
    builder.appendLine(values.joinToString(prefix = "[", postfix = "]"))
    builder.appendLine()

    builder.appendLine("🏅 TopK 异常点排行(按 |Z-Score| 从高到低)")
    ranked.take(topK).forEachIndexed { rank, p ->
        builder.appendLine(
            "第 ${rank + 1} 名: 索引=${p.index}, 值=${p.value}, Z-Score=${round2(p.z)} (|Z|=${round2(kotlin.math.abs(p.z))})"
        )
    }

    builder.appendLine()
    builder.appendLine("🧠 工程化解读")
    builder.appendLine("- Z-Score 异常打分适合用于“整体波动较平稳”的指标,快速标记明显偏离均值的点;")
    builder.appendLine("- 一般认为 |Z| > 2 或 |Z| > 3 的点具有明显异常性,可作为告警候选;")
    builder.appendLine("- 在非高斯分布场景下,可结合分位数/箱线图等方法综合判断,避免单一标准产生误报。")

    return builder.toString().trim()
}

核心步骤:

  1. 解析 kseries
  2. 计算样本均值 mean 与标准差 std
  3. 对每个点计算 Z-Score:\( z_i = (x_i - \mu) / \sigma \);
  4. 按 \(|z_i|\) 从大到小排序,取前 TopK 作为“最异常的 K 个点”。

三、OpenHarmony 侧调用与 UI 展示思路

在 ArkTS 页面中,可以像其他案例一样导入并调用:

import { zScoreAnomalyRanker } from './hellokjs'

页面状态建议包括:

  • kValue: TopK 数量(默认 "5");
  • seriesInput: 时序样本数据字符串(如 "10,12,11,13,50,14,12,11,60,13,12")。

调用示例:

const seriesLine = this.seriesInput.includes('series=') ? this.seriesInput : `series=${this.seriesInput}`
const payload = `k=${this.kValue}\n${seriesLine}`
this.result = zScoreAnomalyRanker(payload)

展示层可以:

  • 使用等宽字体展示“原始序列 + TopK 异常点列表”;
  • 对“TopK 异常点排行”部分做重点高亮,例如用不同颜色或图标标记;
  • 可以进一步将异常点索引映射回图形化折线图中,在 ArkTS 侧以“红点”形式标出。

四、复杂度与工程实践建议

复杂度分析:

  • 统计均值与标准差:\( O(n) \);
  • 计算每个点的 Z-Score:\( O(n) \);
  • 排序获取 TopK:\( O(n \log n) \);
  • 整体复杂度约为 \( O(n \log n) \),在典型终端侧数据规模下完全可接受。

工程实践中的扩展方向:

  1. 滑动窗口 Z-Score
    对滑动窗口内的数据计算 Z-Score,检测“局部异常点”,而不是全局异常。

  2. 与分位数/箱线图组合
    将 Z-Score 与 P95/P99、IQR 离群值检测组合使用,降低单一统计方法在非正态分布场景中的误报概率。

  3. 多维异常打分
    在多指标场景下,对多维数据计算“综合异常分数”(例如将各指标 Z-Score 做加权合成)。

  4. 端侧轻量异常检测
    将该 Z-Score 异常打分逻辑嵌入 OpenHarmony 设备端,实现本地轻量异常检测与 TopK 报告,无需依赖后端大数据平台。

通过这个 Z-Score 时序异常打分与异常点排行案例,你可以在设备侧快速定位“最值得关注的少数异常点”,
为后续的日志钻取、链路追踪与根因分析节省大量时间与精力。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐