Twitter 是广告商吸引受众的一个热门平台。当广告商发起一个新的广告活动时,它们会限定一个广告预算。Twitter 的广告服务器会检查广告活动的预算,以便确定是否还能继续投放广告。如果没有这个检查机制,我们可能会在广告活动达到预算限额后继续提供广告服务。我们把这种情况叫作超支。超支会导致 Twitter 的收入损失(由于机会成本的增加——例如,我们本可以在那个位置显示其他广告)。所以,我们要建立一个可靠的系统来防止发生超支。
在深入研究超支是如何发生前,先来了解一下我们的广告系统是如何提供广告服务的。下面是我们广告服务管道的高级架构图:
1. 支出缓存(Spend Cache)——一个分布式缓存服务,可以跟踪每个广告活动的当前预算支出。
2. 实时广告支出计数器(Live Spend Counter,LSC)——一个基于 Apache Heron 的服务,负责聚合广告活动并更新支出缓存。
3. 广告回调(Ads Callback)——处理用户浏览事件的管道,为事件添加上下文信息,并将它们发送到 LSC。
4. 广告服务器(Ad Server)——在处理请求时,决定是否应该从广告支出缓存中获取当前活动的支出。需要注意的是,这里所说的广告服务器包括了向用户提供广告的多种服务。
当用户在 Twitter 上浏览广告时,我们会向广告回调管道发送一个事件。一旦活动支出计数器收到这个事件,它将计算活动的总支出,并在支出缓存中更新活动的支出。对于每个传入的请求,广告服务器管道都会查询支出缓存,以便获得活动的当前支出,并根据剩余的预算确定是否继续提供服务。
因为我们处理的广告活动的规模比较大(数据中心每秒有数以百万计的广告浏览事件),所以延迟或硬件故障随时都可能在我们的系统中发生。如果支出缓存没有更新最新的活动支出,广告服务器就会获取到陈旧的信息,并继续为已经达到预算上限的活动提供广告服务。我们将永远无法收取超出广告预算的那部分费用,导致 Twitter 的收入损失。
Twitter 有多个数据中心,每个数据中心都部署了整个广告服务管道的副本,包括广告回调管道、实时支出计数器和支出缓存。当用户点击广告时,回调事件被路由到其中的一个数据中心,这个数据中心里的回调管道将负责处理这个事件。
为了解决这个问题,我们给回调事件队列添加了跨数据中心复制功能,以便让每个数据中心都能够处理所有的事件。这确保了每个数据中心中的支出信息是完整和准确的。
尽管复制事件为我们带来了更好的一致性和更准确的支出信息,但系统的容错能力仍然不是很强。在过去,如果一个数据中心发生这些故障,我们会禁用这个数据中心的 LSC,并让其他数据中心的 LSC 同时更新本地缓存和发生故障的数据中心的 LSC,直到出现滞后的广告调管道和 LSC 重新追上来。
为了更好地解决这些问题,我们重新设计了管道,让它能更有弹性地应对故障,并减少运维人员的干预。这个解决方案有两个主要组成部分:
1. 跨数据中心写入:LSC 总是同时更新"备用"数据中心的支出缓存和本地缓存。它还会写入一些有关数据运行状况的元数据。
2. 数据集健康检查:在处理请求时,广告服务器管道读取两个版本的数据,并根据哪个数据集更健康自动选择使用哪个版本。
我们通过常见的故障场景来决定数据集的健康情况,包括延迟和丢失事件。为此,我们引入了支出直方图的概念。
支出直方图显示了每个数据中心的 LSC 在任意给定时刻正在处理的事件计数。LSC 将这些元数据与支出数据一起写入支出缓存。
在处理请求时,广告服务器同时读取本地和远程的数据集。它使用 SpendHistogram 根据最新时间戳和最高事件计数来决定使用哪个数据集作为事实数据来源。
通过引入跨数据中心复制因子的概念,我们可以将设计扩展到"N"个数据中心。复制因子控制每个 LSC 服务写入的远程数据中心的数量。
在推出这些变更之后,我们注意到团队的运维成本发生了重大变化。通过识别系统健康关键指标,设计出最简单的工程解决方案,并根据这些指标自动采取行动,我们解决了一个影响服务管道正确性的关键性问题。我们不仅构建了一个具有容错能力和弹性的系统,而且释放了工程资源,把它们用在更有价值的地方。