说明:本文讨论的是 AI 服务中调用方用量异常时的识别与止血,属于 AI 运维话题,不涉及具体模型版本与价格。AI 领域版本迭代极快,凡涉及版本号、价格、可用性,请以你阅读时的官方页面为准。文中代码为结构示意,请按自己的技术栈调整后再上生产。

一、先分清三种暴涨:真实增长、程序缺陷、恶意套利

本篇只谈调用方的滥用识别与止血,不谈配额怎么分配,也不谈供应商侧额度怎么谈。

2025 年 11 月的一个周二,九点二十,值班群弹出一条预警:某个企业主体的日用量在六小时内跑完了过去一个月的量。接手的人停掉该主体的 key,四十分钟后商务回话,对方正在上线客服机器人,量是真的,客户已经打电话来问。问题不在停用,在顺序:还没分清哪一种暴涨,就选了影响面最大的动作。

三种暴涨的第一动作完全不同:

类型典型成因曲线形态第一动作动作做错的代价
真实增长业务上线、投放、活动台阶式抬升,抬上去不回落确认容量与预算,必要时临时提额停掉正在创收的业务
程序缺陷重试风暴、循环调用、缓存击穿尖峰或锯齿,失败率同步抬升从客户端切断重试源头只限流不止血,流量反弹更高
恶意套利脚本刷量、转售、额度套利顶格跑、夜间为主、形态规整固定证据,再收敛权限证据被日志轮转覆盖,事后追不回

总量上三者长得一样,能分的是形状。真实增长是台阶:某个小时开始抬升,之后稳定在新水位,请求内容五花八门。程序缺陷是锯齿:总量涨十倍、错误率从 1% 涨到 35%,因为每次失败都被重试三四次。恶意套利是顶格:贴着上限跑,间隔方差很小、请求体相似度很高、来源分散但每个来源自身极稳定。

头十分钟:先看失败率,再看分布

真实增长不抬错误率;容量打满会,但表现是 P99 先涨、超时后涨,且集中在高峰。程序缺陷一定抬,且全天候抬,跟时段无关。恶意套利通常错误率很低——脚本照文档写,参数规整,只是量不对。所以第一条查询只打三个数:分钟级用量、错误率、重试率。重试率没有现成字段时,用同一请求指纹在一分钟内出现的次数近似。

-- minute-level usage per billing subject, last 6 hours
SELECT subject_id,
       date_trunc('minute', ts) AS minute,
       sum(input_tokens + output_tokens) AS tokens,
       count(*)                          AS calls,
       avg((status >= 500)::int)         AS err_rate,
       count(*)::float / nullif(count(DISTINCT fingerprint), 0) AS retry_ratio
FROM   usage_log
WHERE  ts >= now() - interval '6 hours'
GROUP  BY 1, 2
ORDER  BY 1, 2;

判据可以写死:错误率超基线三倍且重试率超过 0.2,按缺陷处理,去找客户端;错误率正常、重试率接近 1 而量顶格,按套利处理;错误率正常、量抬升后稳定,按增长处理。

一条反向判据:有没有外部解释

动手前先问一句:这个主体在业务侧有没有对应动作——发布、投放、活动、客户上线、SDK 升级。一条消息、五分钟,过滤掉的恰是最贵的一类误伤。它不替代证据,只改变顺序:先问,同时让限速顶上。限速可逆,停用不可逆。

二、五种滥用形态:动机不同,痕迹也不同

五种形态与它们的成本结构

滥用的共同点是用别人的成本换自己的收益,但换法不同,痕迹也不同,对应的处置权限也不一样。

形态动机成本落在谁身上第一个信号第二步怎么验
脚本刷量批量产出内容、跑数据你的推理成本单账号高频、间隔方差极小看请求体相似度与前缀
转售与二次分发低价转卖给第三方你的成本 + 品牌一个主体下多段互不相干的来源看来源 ASN 与调用风格差异
账号共享团队或客户之间凑一个号你的额度同一账号多地交替、并发超单人可能看会话切换频率与地理跳变
额度套利批量注册领免费额度你的获客预算新账号注册后几分钟内首次调用看设备与支付标识的聚集度
提示词套利把免费额度当通用算力你的免费额度单次输入极长、任务类型单一看输入输出长度比与模板重复率

脚本刷量:人手动点,间隔方差大;脚本按 sleep(3) 跑,方差接近零。请求体做 shingle 哈希后,同一账号一小时内前十大哈希占九成,基本可判模板化。缺前端特征也算一条:正常客户端带版本号与设备标识,脚本通常只有鉴权头。

转售与二次分发:最容易被误判成"大客户":量稳、错误率低、付费正常。判据是来源分散度——一个主体挂着十几个互不相干的来源段,调用风格还各不相同(有的用长 system prompt,有的走流式)。正常企业的内网出口就那么几个,风格统一。

账号共享:地理跳变与会话交替。同一账号十分钟内从相距上千公里的两个出口发起请求,物理上不成立。并发反复顶到某个固定值又落下,也像多个客户端在抢同一个额度池。

额度套利:账号维度看很正常,得把主体、设备、支付标识、邀请关系拉到一张表上看。判据是聚集度:一批新账号在同一设备指纹下注册,或注册后五分钟内首次调用,或用量恰好停在免费额度的九成不再增长。

提示词套利:把免费额度当通用算力,拿去跑批量改写、翻译、抽取。信号是输入输出长度比异常:正常对话大致几比一,批量改写稳定在很窄的区间。任务描述里大量出现"请输出""只输出结果"这类把模型当函数用的措辞,是第二个信号。

形态会叠加,别指望一条规则覆盖

这五类不互斥。转售的人会先用额度套利拿到一批账号,再共享给下游,最后用脚本把量刷满,按单一形态写规则只会看到一堆"每个都不完全符合"的样本。

聚合维度是计费主体,不是账号、key 或 IP。账号能批量注册,key 能轮换,IP 能换,只有付钱的主体换起来成本高。

三、识别信号:单看一个都会误判

曲线形状:三种形状,三种解释

阶梯:量抬升后停在新水位。可能是业务上线,也可能是调度器把配额吃满:上线带来的请求是散的,打满的请求是齐的。

尖峰:短时间内冲高再落回。一次性批处理、压测、重试风暴、攻击都长这样,得配合错误率区分:错误率高的是重试或攻击,低的是批处理。

夜间为主:把调用集中在本地凌晨。这不必然是坏事,跨境团队、海外客户、定时任务都这样。信号是"夜间占比突然从两成跳到八成",不是"夜间有量"。

调用分布:间隔、模板、长度、组合

四个维度各算一个数:

  • 间隔方差:同一账号相邻请求时间差的变异系数。低于 0.1 基本是程序。
  • 模板重复率:一小时内请求体 shingle 哈希的 Top10 占比。
  • 长度比:输入 token 与输出 token 的比值,按任务类型分组看。
  • 参数固定度:模型、温度、最大输出长度这几个参数的组合在一个账号内的分布。人会在几种组合间切换,脚本通常只有一个。

单看任何一个都会误判:方差小可能是 SDK 内置了重试;模板重复率高可能是产品本身模板化;长度比异常可能是客户在做批处理。

组合判据:两个维度同时异常才升级

给每个信号算一个 0 到 1 的分值,加权求和再分档。权重不重要,重要的是"至少两个维度同时异常"这条硬约束。

⚠️ 代码待验证

import statistics as st

W = {"interval_cv": 0.35, "template_top10": 0.30,
     "source_spread": 0.20, "param_fixed": 0.15}

def score(features: dict) -> tuple[float, int]:
    # features are already normalized to 0..1 by the batch job
    s = sum(W[k] * features.get(k, 0.0) for k in W)
    abnormal = sum(1 for k in W if features.get(k, 0.0) >= 0.7)
    if abnormal < 2:
        return round(s, 3), 0          # tier 0: watch only
    if s >= 0.75:
        return round(s, 3), 3          # tier 3: suspend + evidence pack
    if s >= 0.55:
        return round(s, 3), 2          # tier 2: isolate
    return round(s, 3), 1              # tier 1: throttle

def interval_cv(ts: list[float]) -> float:
    if len(ts) < 30:
        return 0.0
    gaps = [b - a for a, b in zip(ts, ts[1:])]
    m = st.mean(gaps)
    return 0.0 if m == 0 else min(1.0, 1.0 - st.pstdev(gaps) / m)

abnormal < 2 这个闸门比总分更重要。它挡住的是"单一维度极端异常"——单维极端往往是采集口径的问题,不是行为问题。我遇到过一次间隔方差算成 0 的事故:客户端把一批请求并发发出,服务端时间戳只到秒,几百个请求落在同一秒里。按单信号判,整个主体的客户都会被标成脚本。

四、止损顺序:限速、隔离、停用、追偿

顺序不是流程规定,是误伤成本排出来的

四步的每一步,可逆性和影响面都差一个量级:

步骤具体动作可逆性客户感知需要的前置证据
限速降并发、降 QPS、降每分钟 token、压低单请求上限秒级可撤销变慢,个别请求超时只需确认量异常
隔离切独立队列、降级到小规格、只读、限来源分钟级可撤销部分功能不可用需确认异常集中在某类请求
停用停 key、停主体、冻结额度小时级,需人工恢复完全不可用需证据包与二次确认
追偿扣额度、终止合同、法务函基本不可逆关系破裂需完整调用记录与书面告知

这张表解释了为什么不能一上来就封。封是唯一一个恢复要走人工流程的动作:停掉一个 key 用一秒,恢复它要确认、审批、通知客户,中间还夹着客户的排障时间。误伤一个大客户的成本,通常比放过一次套利高。

还有一层是证据。停用是对外动作,得先有依据,依据来自前三步的记录:流量变化、行为变化、来源与内容采样。先封后查,日志一轮转,依据就没了。

每一步限的是什么

限速不是只限 QPS,要限四个东西,缺一个都会漏:

  • 并发数:控制同时在飞的请求,直接决定峰值成本。
  • 每分钟 token:防止"请求少但每个都很长"绕过 QPS。
  • 单请求最大 token:控制单次成本上限,防止一个请求打满上下文。
  • 队列深度:控制排队时间,防止限速变成雪崩式超时。

限速的维度按主体,不按 IP。按 IP 限,共享出口的企业客户第一个被打到;按 key 限,换个 key 就绕过;按主体限,才是"一个人的额度由一个人承担"。

⚠️ 代码待验证

# per-subject guardrail, applied by the gateway at request time
subject_id: "org_xxx"
tier: 1
limits:
  concurrency: 8            # baseline 64, cut to 1/8
  rpm: 120
  tokens_per_min: 90000
  max_tokens_per_request: 4096
  queue_depth: 200
scope: "subject"            # not ip, not key
exclude:
  - "batch_job:*"           # keep the customer's nightly sync alive
review_at: "2026-11-01T12:00:00Z"
reason: "usage 11x baseline, error rate normal, evidence pending"

exclude 是给误伤留的口子。很多客户是"主体里一个正常业务 + 一个异常调用方",一刀切会把正常业务也按住,客户感受立刻从"变慢"变成"挂了"。

时间线:一次处置的九十分钟

每一步都要记时间戳,复盘时靠它算指标:

  1. T+0 预警触发,自动规则给出主体、倍数、持续时长。
  2. T+4 值班确认并分流,跑那三条查询,缺陷 / 增长 / 疑似滥用三选一,写进事件记录。
  3. T+15 升到一级。限速生效,同时给商务与客户成功发消息问外部解释。
  4. T+30 观察。量回落说明是脚本,不回落说明有调度器在补。
  5. T+45 确认异常且集中,升到二级隔离,同时固定证据:样本请求、来源分布、时间序列快照。
  6. T+75 证据齐、外部解释没有,升到三级停用,此时才通知客户,附上样本与时间范围。
  7. T+90 出简报,四行:发生了什么、做了什么、客户影响、下一步。

跳过"观察"这一步的人最多。限速生效后不看回落直接封,等于放弃了唯一一次低成本试探——限速本身就是实验,它能告诉你对方有没有在自适应。

五、误伤:正常的大客户长什么样

三种"看起来像滥用"的正常形态

正常形态曲线形状和滥用的区别误判后果
批量任务方波,满并发持续数小时,整齐归零有排期、有报备、任务有明确结束点客户的批处理中断,要重跑
定时同步每天固定时刻、量稳定、间隔稳定时间点固定且长期一致(数月)数据延迟一天,下游报表错
压测短时高并发、错误率高、来源集中提前报备、来源是固定网段、时长可控压测结论作废,上线延期

判据不是"像不像",是"有没有规律的历史"。一个主体连续三个月每天凌晨两点跑一批,那个夜间高峰就是它基线的一部分,所以基线按主体算、按小时分桶,不用全局平均。

规则上我写死了一条:同一时段连续四周有量、波动系数小于 0.3,这个时段进该主体的白名单,异常判定只在白名单外生效。

误伤最常发生在三个地方

第一是共享出口。企业内网、云厂商 NAT 段、运营商地址转换,会让成百上千个终端共用一个出口地址,按 IP 处置第一个打到的就是这些客户。判据:看这个地址上挂着多少个主体,超过十个就别按地址处置。

第二是客户端升级导致的重试放大。SDK 某个版本把超时调短,服务端抖一下,客户端开始重试,重试又加剧抖动,这时量最大的客户最像攻击者。判据是错误类型分布——超时类占绝大多数、且集中在某几个客户端版本上,先查自己的服务端和 SDK。

第三是反向代理、企业网关、Serverless 出网把来源抹平,这类环境来源维度直接失效,只能靠内容与行为。

申诉、恢复、补偿

申诉通道要让客户自己提交,别让客户找商务转达。表单四个字段够用:主体、受影响时间段、业务说明、可联系的负责人。

恢复分三步,不要一次全放开:先恢复读、观察十分钟;再恢复写;最后把额度调回原值。全放开的问题是你没法判断"恢复后会不会又冲上来",第二次处置会更难,因为客户已经知道你会封。

def restore_plan(incident: dict) -> list[str]:
    steps = ["read_path"]
    if incident["write_blocked"]:
        steps.append("write_path")
    if incident["quota_cut"]:
        steps.append("quota_restore")
    steps.append("close_incident")
    return steps

def needs_review(incident: dict) -> bool:
    # false positive: customer had a prior baseline in the same hour bucket
    return (incident["baseline_match"] and
            incident["reported_before_action"] and
            incident["error_rate"] < 0.05)

needs_review 返回真,说明这次处置大概率是误伤,该进复盘而不是结案。三个条件对应三件事:历史有基线、客户在动手前报备过、错误率正常。第三条最容易被忽略——真正的滥用很少伴随高错误率,误伤反而常常伴随它,因为被打到的是正常业务。

补偿按停机时长折算额度,不发现金。现金要走财务流程,周期以周计;额度当天到账,双方用同一个计量单位,不容易扯皮。

完整版资料清单:本文用到的用量基线表、分级处置卡与申诉单模板都整理在里面了,扫码即可获取:

配套资料获取

六、架构侧预防:身份、审计字段与一条熔断

身份、凭证与请求指纹

很多系统把额度挂在 key 上,这是所有止损动作都变重手的根源——封 key 等于封客户,而客户可能只是某台机器上的 key 泄露了。

正确的模型是三层:计费主体(付钱的法律实体)、凭证(key,可多个、可轮换)、额度池(挂在主体上,可按 key 拆分)。处置粒度因此有得选:停一个 key、停一个主体的全部 key、只冻额度不动凭证。

指纹要回答的是"这两个请求是不是同一个来源",用处是把看起来不同的请求聚成同一个行为主体,要素四样:客户端版本与 UA、TLS 握手指纹、请求体 shingle 哈希、时间桶。只存哈希、不存原文——请求体里可能有客户的业务数据,原文留在审计表里等于给自己造一个合规问题。

审计字段:缺一个,事后就查不出来

能查多深,取决于之前记了什么,下面这些字段是底线:

字段用途缺了会怎样
主体 ID + 凭证 ID处置粒度、归因只能整账号处置,误伤面变大
请求 ID + 上游追踪 ID串起网关、队列、模型调用三段日志定位不到是哪一段放大的量
输入/输出 token 分列成本归因、长度比信号只知总量,分不清是长输入还是长输出
模型与参数组合参数固定度信号判断不了是不是同一个脚本在跑
来源 ASN 与网段区分共享出口按 IP 处置必误伤企业客户
是否命中缓存成本核算与"重复请求"判定把缓存命中算成真实成本,账对不上
处置标记与处置人复盘时区分"自然回落"与"被限速"曲线回落的原因说不清

最后一行最容易被漏。没有处置标记,事后只看得到"量掉下来了",分不清是脚本自己停了还是你限速起效了,而这两个结论导出的下一步完全相反。

成本熔断:阈值要双条件,动作要分级

阈值用"基线倍数 + 绝对值下限"两个条件取与。只用倍数,小主体会被误熔断(基线 100 次,涨到 1000 次就是十倍,绝对值却微不足道);只用绝对值,大主体的异常增长永远触发不了。

熔断动作分三档,跟止损四步对齐:一档只告警,二档自动限速,三档自动停用且必须人工确认才能恢复。三档不要做成自动恢复,否则攻击者只要等窗口过去就能继续。

熔断还要回答"谁来解"。这条写不清楚,熔断就会变成永久封禁:值班的人不敢解,业务方找不到人解。

七、度量与复盘:四个数字与一页纸

四个指标

发现时长:从异常开始到有人确认,看中位数,长尾会掩盖常态。

止血时长:从确认到量回落,要减去"自然回落"的部分。判据是看回落时刻是否与处置时刻吻合,这就要靠上一章那个处置标记。

误伤率:被处置主体里事后判定为正常业务的比例。不用很精确,按月抽十条就够,作用是防止团队为了指标好看而放宽判据。

损失账:异常期间的实际成本,减去该主体的历史同时段基线成本。别把全部用量算成损失,那会把正常业务也算进去。

-- loss ledger for one incident window
WITH base AS (
  SELECT subject_id,
         avg(tokens) AS tokens_per_hour
  FROM   usage_hourly
  WHERE  subject_id = :subject
    AND  hour >= :start - interval '28 days'
    AND  hour <  :start
  GROUP  BY 1
)
SELECT :subject                                   AS subject_id,
       sum(u.tokens)                              AS actual_tokens,
       b.tokens_per_hour * :hours                 AS expected_tokens,
       sum(u.tokens) - b.tokens_per_hour * :hours AS excess_tokens,
       sum(u.cost)                                AS excess_cost
FROM   usage_hourly u JOIN base b USING (subject_id)
WHERE  u.hour >= :start AND u.hour < :end
GROUP  BY 1, 3;

一页纸的值班手册

值班的人只读一页纸,上面四件事:

谁能按哪个按钮。一级限速值班自行决定,二级隔离要值班加一人复核,三级停用要负责人书面确认。写名字或角色,不要写"相关负责人"。

按之前留什么。三条查询的结果、样本请求、时间序列快照。这一步要写进流程,否则出事时没人有心情留。

按之后通知谁。客户成功、商务、值班群,三个都要。只发值班群,客户会从自己的监控上先发现问题,然后带着情绪来问。

白名单怎么加、解封谁批。这是误伤发生后唯一的出口,写不清楚,客户就被卡在"没人有权解"的状态里。

复盘:一次事件要产出两条规则

一条检测规则,一条豁免规则。前者描述这次是怎么被发现的,后者描述这类正常业务以后怎么不被误判。只写检测规则,同类误伤会在下个月重演。

规则要有过期时间。每条规则带 expires_at,到期自动降级为只观察不处置。滥用手法会变,检测规则会钝化,钝化的规则留在系统里只会制造误伤。到期强制重审,比定期"清理规则"这种没人认领的动作可靠。

完整版资料清单:本文用到的用量基线表、分级处置卡与申诉单模板都整理在里面了,扫码即可获取:

配套资料获取

附表 A:关键取舍一览

取舍本文结论判断依据位置
先看总量还是先看形状先看形状,再看驱动源三种暴涨在总量上是同一个现象第一章
先看什么指标先看错误率与重试率增长不抬错误率,缺陷一定抬第一章
动手前要不要问业务侧要,且与限速并行成本五分钟,过滤最贵的误伤第一章
聚合维度用什么计费主体,不是账号或 IP主体换起来成本最高第二章
单信号能不能定罪不能,至少两个维度同时异常采集口径问题会伪造单维极端值第三章
第一步动作是什么限速,不是停用停用恢复要走人工,代价最高第四章
限速限几个维度并发、每分钟 token、单请求上限、队列深度只限 QPS 会被长请求绕过第四章
按什么维度限速按主体按 IP 打到共享出口,按 key 一换就绕第四章
停用前要有什么证据包与二次确认停用是对外动作,没依据撑不住第四章
基线怎么算按主体、按小时分桶、看四周全局平均会吃掉正常夜间高峰第五章
恢复怎么做读、写、额度分三步一次全放开则无法判断是否反弹第五章
补偿给什么折算额度,不发现金现金走财务周期,信任补不回来第五章
额度挂在哪主体,不挂在 key 上挂在 key 上则封 key 等于封客户第六章
指纹存什么只存哈希原文入审计表会带来合规问题第六章
熔断阈值怎么定基线倍数与绝对值下限取与单条件在小主体或大主体上各失效一头第六章
熔断后自动恢复吗不自动,必须人工确认自动恢复等于给攻击者一个等待窗口第六章
规则要不要过期要,到期降级为只观察钝化规则只制造误伤第七章

附表 B:术语速查表

术语含义
计费主体承担费用的法律实体,是额度与处置的归属单位
凭证调用用的 key 或令牌,一个主体可有多把,可轮换
用量基线按主体、按小时分桶统计的历史正常水位
重试率同一请求指纹在短窗口内重复出现的比例,用于识别重试放大
间隔方差相邻请求时间差的离散程度,程序化调用的典型特征是方差极小
模板重复率请求体哈希的集中度,反映调用是否由固定模板批量生成
长度比输入 token 与输出 token 的比值,用于区分对话与批处理任务
参数固定度模型与采样参数组合在主体内的分布集中程度
请求指纹由客户端特征、来源特征与内容哈希合成的标识,用于聚合行为
限速降并发、降速率、压单请求上限,可秒级撤销
隔离把异常流量切到独立队列或降级路径,影响面限于部分功能
停用冻结凭证或主体,恢复需人工流程
成本熔断用量越过阈值时自动降档或停用的机制
方波满并发持续数小时后整齐归零的曲线,批量任务与压测的典型形状
误伤率被处置主体中事后判定为正常业务的比例
止血时长从确认异常到用量回落的时间,需扣除自然回落部分

写在最后:这篇用到的资料

写这篇文章时,我把几个线上事故的时间线和处置记录都翻了一遍,顺手整理成几份配套的东西:

  • 大模型学习路线图:从 LLM 基础到 Agent 开发,各阶段该学什么、用什么资料
  • 大模型全套教程:按主题分好的视频与文档清单
  • 大模型实战好书:24 本,附每本适合的阶段

资料是我自己整理的,放在下面这个码上,扫码即可获取:

配套资料获取

大模型全套教程

大模型实战好书

添加时备注「大模型」,优先通过。

拿到之后建议先看学习路线图那一份,先定位自己在哪个阶段,再决定学什么,比一上来就啃框架效率高得多。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐