跳到正文
HA_

← 全部文章

· 阅读约 3 分钟

在广告仍在投放时拦截无效点击

ClickFortify 如何将广告点击流转化为 Google Ads 和 Meta 中的排除规则,以及为什么要基于 NATS 构建事件驱动服务。

architectureevent-drivenadtech

机器翻译。 阅读原文

广告主为每一次点击付费。其中一部分点击来自机器人、VPN、代理和数据中心,而几周后才送达的报告只能记录下已经发生的损失。ClickFortify 是我独立设计并运维的项目,它的任务是在广告仍在消耗预算时发现无效流量并将其排除。

问题的形态

几乎所有设计决策都由三个特性驱动:

  • 它从不停歇。点击持续不断地涌入,全天候、跨时区。
  • 每个广告主都是一个租户。每个广告主都有自己的账户、规则和数据,彼此之间绝不能泄露。
  • 动作发生在别处。检测到无效点击只完成了一半工作。排除规则必须通过 API 写回广告主的 Google Ads 或 Meta 广告账户,而这些 API 有配额限制,偶尔还会失败。

刻意选择事件驱动

采集点击、为点击评分和同步排除规则,三者的负载特征截然不同。采集流量突发性强,必须保持快速;评分是 CPU 密集型;同步则受制于别人的 API。把它们放在同一个进程里,会让最慢的环节决定所有环节的节奏。

因此,每个关注点都是独立的服务,通过 NATS 上的事件相互连接。点击被采集后发布出去;评分服务订阅、做出判定并发布判定结果;同步服务接收需要排除的判定。每个服务都能独立扩展,缓慢的广告平台 API 也永远不会拖慢采集。

为点击评分

没有任何单一信号能证明一次点击无效,因此评分会综合多种信号:行为信号、设备指纹,以及对 VPN、代理和数据中心流量的过滤。将评分与采集分离,意味着规则可以随时调整,而不必触碰那个必须始终在线的部分。

写回时不冲击 API

写回是最容易以各种“有趣”方式出错的环节。排除规则通过 Redis Bull 任务队列下发,这样每次同步都带有退避重试,一阵突发的判定也会变成平稳的 API 调用流,而不是一个尖峰。

// Illustrative: one queued exclusion per tenant, platform and IP
await exclusions.add(
  'exclude-ip',
  {tenantId, platform, ip},
  {
    jobId: `${tenantId}:${platform}:${ip}`, // idempotent: the same IP is never queued twice
    attempts: 5,
    backoff: {type: 'exponential', delay: 30_000},
    removeOnComplete: true
  }
);

稳定的任务 ID 让这个操作具备幂等性:即使同一个 IP 在一分钟内被标记十次,也只会同步一次。

多租户与一次在线迁移

由于每个广告主都有独立的隔离租户数据库,大规模变更可以逐个租户地规划,而不是一次性进行高风险的切换。正是这样,我在近乎零停机的情况下完成了 500+ 个租户数据库的线上生产数据库迁移。

给构建同类系统的人的建议

  1. 把写回广告平台视为真正的难点,而不是检测本身。
  2. 按负载特征拆分服务,而不是按名词拆分。
  3. 在需要重试之前,先让每一个外部副作用都具备幂等性。