CC防护误杀了订单怎么算?埋点归因实战

拦截率高不等于没损失,被误拦的下单请求根本到不了业务,也不会进埋点。本文用 10 个问答讲清埋点归因怎么做:防护日志与业务数据怎么对上号、误杀多少订单怎么算、口径怎么定。

Q:为什么拦截率 99%,订单还是在掉

  • 拦截率的分母是「边缘节点收到的请求」,被拦掉的那部分不会进业务库,也不会进埋点。它天生看不见。
  • 所以只看拦截率会漏两类损失:拦错的(真人被当成攻击者)和连带拦的(同一 NAT 出口里的正常用户)。
  • 反过来也一样:攻击者打的是搜索页、详情页,拦截率很好看,但带宽账单照样爆。
  • 结论:必须把「防护侧拦截记录」和「业务侧订单漏斗」做 join,各看各的永远说不清。

Q:两边靠什么字段对上号

  • 靠一条 request_id 贯穿,这是整个归因的地基。没有它,后面所有分析都只能靠时间窗猜。
  • 三个 id 分工不同:request_id 标识单次请求,最准;session_id 标识会话,用于跨页面;order_id 落到钱。
  • Nginx 侧生成并回传,业务侧接住,前端埋点带出去,三处都要有。
  • 常见坑:跨域时前端读不到自定义响应头,必须显式暴露,否则 X-Request-ID 白加。
# 1) Nginx 生成并回传
log_format with_rid '$remote_addr "$request" $status rid=$request_id '
                    'fp=$cookie_fp up=$upstream_status rt=$request_time';
access_log /var/log/nginx/edge.log with_rid;

add_header X-Request-ID $request_id always;
add_header Access-Control-Expose-Headers "X-Request-ID" always;

# 2) 业务侧接住并落库(PHP 示例)
$rid = $_SERVER['HTTP_X_REQUEST_ID'] ?? '';
error_log("rid=$rid path={$_SERVER['REQUEST_URI']}", 3, '/var/log/app/rid.log');

Q:请求都被拦了,埋点根本没机会上报怎么办

  • 这是归因的第一个坎:444 断连意味着用户什么都拿不到,你也什么都收不到。想算账就不能用 444 兜底。
  • 方案一,拦截页可上报:返回 429 或 403 时给一个轻量页面,用 navigator.sendBeacon 把「被拦事件」发出去,带上路径、是否登录、是否在结算页。
  • 方案二,边缘影子记录:确实要拦的请求,同时按「假如放行」记一条日志(upstream_status 为空、status 为 403/429),两者对照才能看出拦掉了什么。
  • 方案三,服务端兜底:支付回调、下单接口这类路径,在应用层也记录一份「收到但被前置拦截」的痕迹。
  • 三个方案优先级:先做方案一,成本最低、见效最快;方案二适合已经有边缘日志的站。

Q:怎么区分「拦对了」和「误杀」

  • 三个信号叠加判断,不要单看一条:是否带登录态、是否在结算或支付路径、该 session 一小时内是否有加购或下单动作。
  • 带登录态 + 在结算路径 + 被拦,基本可以判定误杀,优先级最高。
  • 无登录态 + 高频同一路径 + 不请求任何静态资源,基本可以判定是攻击。
  • 阈值上要区别对待:普通页面单 IP 每分钟 20 次是常见基线,但结算与支付接口必须更宽松,真人反复点支付按钮太正常了。
  • 支付回调是第三方服务器发起的,绝大多数没有浏览器特征,误拦它等于用户已经付钱而你没发货,必须白名单。

Q:影子模式怎么开,灰度怎么排

  • 影子模式的目的只有一个:拿到「如果按这套规则拦,会拦到谁」的分布,同时不影响任何业务。
  • 做法是用 map 生成一个 decision 变量(block / shadow / allow),shadow 时只在日志里打标,不执行 return。
  • 灰度顺序建议:影子 7 天 → 只对未登录用户的 5% 流量拦截 → 全量未登录 → 登录用户永远只挑战不封禁。
  • 回滚红线写死两条:误杀率超过 1%,或 429 占比超过 3%,立即退回 shadow。
map $cookie_fp $cc_decision {
    default                "allow";
    ""                     "shadow";   # 无指纹:只记不拦
    "~^$"                  "shadow";
}
map $request_uri $is_checkout {
    default                0;
    "~^/(cart|checkout|pay)" 1;
}
# 结算路径降级为影子,先看数据再决定
map "$cc_decision$is_checkout" $cc_final {
    "shadow1"  "shadow";
    "allow1"   "allow";
    default    $cc_decision;
}

Q:归因口径怎么定才不被质疑

  • 五条必须写死的约定,比数字本身更重要。
  • 归因窗口:拦截后 30 分钟内同 session 完成下单算「找回」,超过不算。窗口定完就别改。
  • 一单只归一次:按最后触达归因,避免同一订单在多个规则下重复计数。
  • 分母统一:误杀率 = 被拦且有下单意图的 session / 全部有下单意图的 session,不要按请求数算,否则分母被刷量放大。
  • 只算防护拦的:403/429/444 计入,源站 5xx 和超时单独统计,别混在一起。
  • 金额用实付:不用商品原价,不算未来复购(复购可以作为备注,不进主表)。

Q:SQL 怎么写

  • 前提是订单表里有 request_id 或 session_id。没有的话,只能先用「session + 时间窗」近似,误差要如实写进报表。
  • 按小时聚合,能直接看出误杀集中在哪个时段,通常和攻击时段重合。
-- orders: 订单表;edge_log: 边缘/防护日志(含 rid、status、rule)
SELECT date_trunc('hour', o.created_at)                       AS hr,
       count(*)                                               AS all_orders,
       count(*) FILTER (WHERE l.status IN (403,429,444))       AS killed_orders,
       round(100.0 * count(*) FILTER (WHERE l.status IN (403,429,444))
             / nullif(count(*), 0), 2)                        AS kill_pct,
       sum(o.paid_amount) FILTER (WHERE l.status IN (403,429,444)) AS lost_amount
FROM orders o
LEFT JOIN edge_log l
       ON l.rid = o.request_id
      AND l.ts BETWEEN o.created_at - interval '30 minutes' AND o.created_at
WHERE o.created_at >= now() - interval '7 days'
  AND o.status = 'paid'
GROUP BY 1
ORDER BY 1 DESC;

Q:误杀一般集中在哪些人身上

  • 移动弱网:请求超时后自动重试,一秒内出现三四个请求,被算成高频。
  • 企业、学校、园区出口:几百人共用一个公网 IP,单 IP 限频直接团灭。对策是按 /24 聚合判断,而不是单 IP。
  • 海外与代理用户:IP 信誉分天然低,风控规则一收紧就先中枪。
  • APP WebView 与小程序:cookie 不全,指纹拿不到,命中「无标识」规则。
  • 比价插件、密码管理器:自动填充触发高频表单提交,看起来很像机器。
  • 支付成功跳回页:用户反复刷新确认,短时间请求密度很高。

Q:算出来的数字怎么用

  • 调规则:把误杀最集中的那条规则从「拒绝」降级为「挑战」,通常一步就能把误杀砍掉大半。
  • 加白:支付回调、监控拨测、合作方接口,这三类必须进白名单,且要有到期日。
  • 报表:给老板一页纸,只放四个数——拦了多少、误伤多少单、损失多少金额、已修哪几条。
  • 决策:误杀金额长期高于防护月费,先降档再谈方案;反之维持现档,把钱花在支付链路加固上。

Q:有没有更省事的起步方案

  • 最小可行版本只做两件事:拦截页改 429 并挂 sendBeacon 打点、Nginx 日志加 request_id。一周内能上线。
  • 第二步,每天导出「带登录态却被 403/429」的名单,人工抽 50 条看,两三天就能看出误伤长什么样。
  • 第三步,再做上面那段 SQL 自动归因,接进周报。
  • 别一上来就上全链路 trace:埋点改造成本高、周期长,先把「看得见」这件事做到,再谈精细。
  • 最后提醒一句:归因的目的是把防护规则调到既不漏也不误伤,不是为了做一张好看的报表。