Skip to content
Charles Shao
Go back

供应链透明度(下):OpenRTB schain 运行时逐跳核验

Updated:
–views

上篇《ads.txt / app-ads.txt / sellers.json》 探讨了三份静态声明——媒体(Publisher)通过 ads.txt 或 app-ads.txt 声明「谁被授权售卖我的库存」,而中介则通过 sellers.json 声明「系统内每个账号背后真实的卖方身份」。然而,静态声明只能回答「谁被授权」,却无法回答「一条具体的广告请求实际经过了哪些中介,且顺序如何」。为了补上这后半个问题,OpenRTB 引入了 SupplyChain 对象(schain),这正是本篇的主角。

正因如此,如果说三份静态声明是事先公布的授权表,那么 schain 则是随每条 bid request 逐跳生长的运行时记录。每个中介在转发请求前,都必须将自身作为一个 node 追加进去,从而清晰记录「我是谁,又从谁手中接手」。当买方(Buy Side)收到请求后,只需将每一跳与授权表进行比对,即可判定这条供应链是否完整、每一手转售是否均被授权。需要强调的是,本篇还将深入剖析买方侧的完整工程实现:从离线抓取三份声明并编译成内存索引,到在竞价热路径上利用 schain 完成亚毫秒级的逐跳核验。

本文是 供应链透明度与优化 系列的第 2 篇(运行时 schain)。 全系列 3 篇:

  1. 供应链透明度(上):ads.txt 与卖方授权声明
  2. 供应链透明度(下):OpenRTB schain
  3. 供应链优化(SPO)

一句话定位:OpenRTB 的 schain 作为随请求逐跳生长的运行时记录,配合静态声明实现了供应链的交叉核验,并为买方剔除冗余路径提供了数据支撑。

SupplyChain 对象结构:一个紫色面板标题 Bid Request · source.schain(OpenRTB 2.6),内部自上而下三个卡片——顶部 schain 头部 { ver: "1.0", complete: 1 };nodes[0] 最上游·直连媒体的 SSP(asi: ssp-a.com、sid: 540123、hp: 1);nodes[1] 转售交易所(asi: adx-b.com、sid: 8842、hp: 1);卡片之间用向下箭头表示顺序;底部旁注说明每个中介转发请求前必须把自己作为一个 node 追加进 schain(有序、nodes[0] 最靠近媒体),complete=1 表示整条链从媒体到发出请求的交易所完整无缺、hp=1 表示该节点参与本次结算,node 的 asi 对应 sellers.json 域名、sid 对应其 seller_id,由此把运行时路径接回前两张静态声明 schain 是一条「谁经手了谁」的运行时账。nodes[0] 最靠近媒体,每多一跳中介就 append 一个 node;complete=1 是完整性承诺。每个 node 的 asi/sid 正是 sellers.json 和 ads.txt 的对账键——运行时记录在这里接回上篇的静态声明。

TL;DR

Table of contents

Open Table of contents

一、schain 是什么:运行时逐跳记录

上篇介绍的 ads.txt 与 sellers.json 都是静态声明,即媒体和中介各自「事先」贴出的授权表。但这引出了一个关键问题:一条具体的 bid request 到底实际经过了哪些中介,且顺序如何?为了解答这个问题,我们需要引入 OpenRTB 里的 SupplyChain 对象(schain)。它随请求逐跳生长,忠实记录了真实的流转路径。

schain 挂载在 source.schain(OpenRTB 2.6 规范;早期实现则放在 source.ext.schain),其核心是一个有序的 node 数组。node 与顶层字段的具体含义如下:

字段含义
asi广告系统标识(该中介的域名)——对应 sellers.json 发布域名与 ads.txt 的广告系统域名
sid卖方在该系统里的账号 ID——对应 sellers.json 的 seller_id 与 ads.txt 的账号 ID
hp该 node 是否参与本次结算(handles payment),通常为 1
rid / name / domain请求 ID、可选的名称和域名
complete(顶层)1 代表链完整(从媒体到发出请求的交易所每一跳都在);0 代表不完整
ver(顶层)schain 版本

正如上篇反复强调的,asi 与 sid 正是对账键:asi 是广告系统域名(据此即可拼出 https://{asi}/sellers.json),而 sid 是卖方在该系统内的账号编号。因此,schain 的每个 node 实际上代表了「在某个 asi 内、由某个 sid 经手的这一跳」。运行时记录采用的正是静态声明的同一套键,从而确保两边能够完美对齐。

在工程实现上,有几条必须遵守的规则:

需要强调的是,schain 并不替代 ads.txt 或 sellers.json,而是将它们串联起来:它给出「实际路径」,前两者给出「每一跳是否被授权」。买方只有以 schain 的每个 node 回查另外两份声明,方才完成最终的核验闭环。

这恰好回应了上篇留下的库存洗钱难题。洗钱的本质是「买方看不到经了几手、每一手是否被授权」。如今,schain 将「经了几手」变成一个公开的有序数组。若想隐去中间跳,就必须不把自己加入,此时 complete 便只能标 0,从而暴露异常;若如实全部加入,那几手转售则完全暴露在买方眼前,进而进入下文的逐跳核验。无论哪种方式,洗钱的路径都被彻底堵死。

二、把声明和运行时接上:买方交叉核验闭环

单看每个标准,它们都只是一份孤立的表;真正起作用的是买方将它们进行交叉核验。这一核验逻辑本质上是一条层层递进的决策链:

买方核验决策树:从一条 bid request 出发,自上而下四个判断,每个判断「是」向下继续、「否」向右指向一个失败结论——① schain.complete==1(链是否完整)?否→链不完整/被截断:降权或直接拒(琥珀);② node 的 asi.sellers.json 里存在该 sid?否→未知卖方 unauthorized:拒绝出价(红);③ 卖方域名的 ads.txt/app-ads.txt 授权了 (asi,sid)?否→域名伪造/非法转售:丢弃(红);④ 路径是否无冗余跳数(SPO 视角)?否→可信但路径冗长:SPO 降低优先级(琥珀);四步全部「是」落到底部绿色结论:全部通过→可信供应路径,正常竞价并据 schain 跳数做 SPO 选最短路;底部旁注给出域名伪造实例(请求声称 nytimes.com 但其 ads.txt 未列出该 (asi,sid) 即判伪造丢弃)并强调核验是尽力而为、对不完整或对不上的路径多用降权而非硬拒以免误杀真实库存 核验是一条「层层收紧」的漏斗:先看链完不完整(schain.complete),再看每一跳的卖方在不在中介的 sellers.json 里,再回查卖方域名的 ads.txt 授没授权这一跳;全过才是可信路径,还能顺带按跳数做 SPO。任一步不过,对应到「降权」或「丢弃」。

一条 bid request 的完整核验,就是对它的 schain 逐跳走一遍:

  1. 完整性:首先检查 schain.complete == 1 是否成立。如果不完整,说明有隐藏的中间跳——保守的买方会直接拒绝,而多数买方会选择降权处理。
  2. 卖方存在性:对每个 node,去 asi 的 sellers.json(即离线建好的 sellersMap,详见第三节)里查 sid 是否存在。如果查不到,即视为未知卖方(unauthorized),直接拒绝出价。
  3. 授权一致性:从 sellers.json 拿到该卖方的 domain,回查该 domain 的 ads.txt 或 app-ads.txt(即离线建好的 authMap),确认其中是否有 (asi, sid) 这条授权记录。如果没有,则判定为域名伪造或非法转售,直接丢弃。
  4. 路径质量(SPO):全部通过后,还需要评估链条是否有冗余跳数。这不是「真伪」问题,而是「效率」问题。买方会据此优先走更短、更少抽成的路径(详见第五节)。

以域名伪造为例:假设一条请求声称库存来自 nytimes.com,且 schain 中带有某个 (asi, sid)。买方回查 nytimes.com/ads.txt 时,发现其中并无这条 (asi, sid) 的授权,便会判定其为伪造并丢弃。伪造者虽然可以篡改 bid request 里的字段,却改不了 nytimes.com 根目录下那份由纽约时报自身控制的 ads.txt。

不仅如此,再以上篇那条库存洗钱链(X → SSP-A → ADX-B → SSP-C)验证一遍:若 X 试图隐去自己这一跳,schain 中便少一个 node,complete 应为 0,在第 1 步即被拦下;若如实记录,则走到第 3 步,回查媒体的 ads.txt 会发现并无授权 X 对应的 (asi, sid),同样会被丢弃。无论隐去与否,洗钱链都通不过这个严密的闭环。

正因如此,这四件套单独拿出来均可被绕过或造假,但交叉核验把它们相互锁定。伪造者必须同时篡改媒体的 ads.txt、中介的 sellers.json 和请求的 schain,并使三者完全自洽,造假成本陡增到极不划算的地步。

三、买方侧工程(离线):全网抓取、解析、建索引

核验逻辑虽然清晰,但真正的工程量在于把这三份文件抓取完整、清洗干净,并编译成可在竞价热路径上 O(1) 查询的索引。这是一道典型的「离线重、在线轻」读写分离题,与频次控制工程篇属于同一套架构思路。

首先是离线抓取与定时刷新。买方需要维护以下三类全网数据:

这本质上是一个分布式爬虫、存储与调度问题。系统需要按域名分片抓取、遵守缓存头、错峰刷新(热门域名勤刷、长尾域名稀刷),同时处理超时与 404 错误,并监控覆盖率。由于文件并非一成不变,媒体会增删授权,中介也会调整 sellers.json,因此刷新周期直接决定了核验的时效与误杀率。此外,sellers.json 还常见大厂文件达数十万条、上百 MB(需用 gzip 加 ETag 增量刷新)、302 跳转至 CDN 等复杂情况。

在抓取阶段,有两个极易踩中的坑,需要特别强调:

其次是预构索引。把抓来的原始文件编译成热路径能 O(1) 查的结构,典型的是以下三张内存表:

authMap:    (publisher_domain) → set of authorized (asi, sid)   # 来自 ads.txt/app-ads.txt
sellersMap: (asi) → { sid → {domain, seller_type, confidential} } # 来自 sellers.json
bundleMap:  (app.bundle) → developer_domain                      # 来自商店爬取

这三张表构成了核验闭环的查询面:authMap 回答「该媒体授权了哪些 (asi,sid)」,sellersMap 回答「某 asi 下某 sid 背后是谁」,而 bundleMap 将 App 请求翻译为可回查的开发者域名。它们与频次控制的本地缓存一致:离线全量灌入进程内存、定时刷新,在线纯读。下一节的逐跳核验,读的正是这三张表。

四、买方侧工程(在线):热路径逐跳核验

索引备好后,核验逻辑就要跑在十万级 QPS 的竞价热路径上。由于重活儿(全网抓取、解析、建索引)已在离线阶段做完,热路径只需纯读那三张内存索引,即可在亚毫秒内完成判断。

每当一条 bid request 进来,核验本质上就是对 schain 的一次线性扫描:

extract publisher_domain / bundle + source.schain
for each node in schain:
    seller = sellersMap[node.asi]?.[node.sid]
    if seller == null            → 未知卖方,标记不可信
    if authMap[seller.domain] not contains (node.asi, node.sid)
                                  → 未授权或伪造,标记不可信
if !schain.complete or 任一 node 不可信:
    → no-bid / 降权 / 不参与(按买方策略)

在热路径的工程实现上,有几个关键点需要注意:

需要强调的是,核验是尽力而为的,我们必须承认以下几个现实边界:

五、透明度之上:供应链优化(SPO)

核验解决了「真不真」的问题,而 schain 还一并解决了「值不值」的问题。由于 schain 把真实跳数与每一跳的中介都摊开在阳光下,买方首次得以回答:同一份库存,究竟是经 3 跳中介买到的,还是经 1 跳直连(DIRECT)买到的?中间每一跳都在抽成,这正是生态图所讲的 ad tech tax。

供应链优化(SPO,Supply Path Optimization) 便是买方据此收敛路径的策略:面对同一份库存的多条可达路径,优先选择更短、更少抽成、更多 DIRECT、可核验性更高的那条,果断砍掉冗余的转售跳。这既能节省预算,让更多资金落到真实库存上,也能降低风险,因为路径越短,越难藏匿未授权环节。

正因如此,透明度与 SPO 互为因果:先有可核验的供应链(透明度),才谈得上有依据地优化供应链(SPO)。如果没有 schain,买方无从得知预算经了几手,优化也就无从着手。这恰好回应了生态图第 7 节那 15% 的 unknown delta:浪费只有被测得到,才谈得上被消除。关于 SPO 的打分、席位策略与需求方平台(DSP)的落地细节,详见专文《供应链优化(SPO)》。

六、常见误区 ↔ 正解

常见误区正解
schain 能替代 ads.txt 或 sellers.json不能。schain 是运行时路径,那两者是授权声明;核验要三者交叉,缺一不可
complete=1 就一定可信它只是一句可被证伪的承诺;仍要逐 node 回查 sellers.json 和 ads.txt 才算数
schain 里有 node 就代表被授权node 只是「自报经了这一手」;是否被授权,要拿 (asi,sid) 回查 sellers.json 与 ads.txt 才知道
核验就该「对不上就拒」「查不到」(覆盖/时效/confidential)降权、「明确对不上」才拒——尽力而为,避免误杀真实库存
抓到 HTTP 200 就是有文件很多站点对不存在的路径返回 200 网页或软 404;必须校验是纯文本且可解析记录(sellers.json 须是 JSON)才算数
schain 只是防欺诈的它同时是 SPO 的数据基础:暴露真实跳数,才能收敛路径、降低 ad tech tax
S2S 或 Header Bidding 不用管 schain恰恰相反——服务器到服务器的多跳场景更依赖 schain 逐跳生长来还原真实路径(见 Prebid Server)

排障与调优口诀:交叉核验锁闭环;查不到则降权;对不上才硬拒;跳数越少越优。

静态声明回答「谁被授权」,schain 回答「谁实际参与了」。核验过关之后,跳数和每一跳的中介就可以拿来做路径收敛——详见下一篇《供应链优化(SPO)》。


延伸阅读

同系列(供应链透明度与优化):

跨系列相关:

一手资料与规范:


–views
Share this post on:

Previous Post
RTA 竞价前过滤:第一方数据不出域地影响出价
Next Post
供应链透明度(上):ads.txt 与卖方授权声明