在互联网江湖中,始终流传着三大赚钱法宝:广告、游戏、电商。三杰之中,又以大哥广告的历史最为悠久,地位也最为不可撼动。君不见很多电商和游戏公司,也通过广告业务赚的盆满钵满。其发迹于Y公司,被G公司发扬光大,又在F公司阶段性地完成了其历史使命。F公司,在移动互联网兴起之际,利用其得天独厚的数据优势,终于能够回答困扰了广告主几百年的问题:我的广告究竟被谁看到了?浪费的一半的钱到底去了哪里?
从用户角度来看,广告其实是充斥着互联网的每个角落,但正如习惯成自然一样,对于越常见的事物,越少有人究其根本。对于互联网技术人员来说,由于广告业务具有高度的垄断性,能够接触到其本质的工程师相对较少,尤其有过大型系统经验的人更加稀缺。本文的目的在于对大型广告系统的整体架构和其中的设计权衡点有一个全面的介绍,为有志从事该行业的工程师提供一套思考的思路。
另外有几点说明。第一,广告系统一般分为搜索广告和上下文广告,由于上下文广告系统面临的问题要比搜索广告系统更加丰富,因此本文专注于讨论上下文广告系统。第二,本文适合对广告业务有一定了解的工程师,对于业务不了解的同学,推荐阅读刘鹏博士的<<计算广告>>。
俗话说,离开业务谈架构都是耍流氓。用一句标准的报告性语言介绍大型广告系统的特点就是:处理的数据量特别巨大,响应速度要求特别快,数据实时性要求特别高,系统可用性要求特别高。面对种种不可思议的困难,最初的一批误打误撞进入广告行业的的互联网工程师们,本着赚钱的目的,通过演杂技一般的对各种技术的拼接,出色地完成了任务。下面逐条分析一下系统特点。
- 数据量特别巨大
在上下文广告中,系统中一般主要包含四种数据(广告系统所有问题的讨论一般都围绕这四种数据展开)
广告本身的数据。一般包括名字、出价、投放时间、有效性(预算)、标题、描述、跳转链接、图片、视频等。这里的数据量一般不会特别巨大。几十万的广告主,已经足以支撑起业内顶尖的广告公司,广告的数量会比广告主的数量大2个数量级左右。
广告的定向数据。其数据量和系统提供的定向维度有关。例如用户的搜索记录定向,网页分词定向,购买的商品记录定向,APP安装列表定向,用户人群定向等。其中每一种定向维度中,广告主都可以设置大量的定向数据。例如搜索记录定向中,广告+关键词的组合个数甚至会超过int最大值,如果在内存中高效地组织这些数据,是一个挑战。
插一条案例。在团购大战时代,某美国团购鼻祖高调杀入中国,曾经创下过购买百万级关键词的记录,当然最后被中国的资本市场实实在在地教训了一把,结果大家都知道。类似的不理智行为还曾发生在视频大战、电商大战、分类信息网站大战,最终要么合并,要么抱大腿,唯留得广告公司内心窃喜,期待下一场大战爆发。
用户的特征数据。其数据量和面向的市场有关。如果面向的是中国市场,那么就要做好处理世界上最复杂问题的准备(下一个这样体量的市场是印度)。君不见各家PR稿,没有3亿用户都不好意思出来打招呼,且不说数据量是真是假以及是否有用,起码这表明了大家都认可“用户数量是衡量广告系统优劣的一大标准”。进一步说,特征数据是根据用户的行为数据计算出来的(例如浏览过哪些页面,购买过什么物品)。数亿的用户,一般都会用历史一段时间的行为数据和当天的行为数据,计算出用户的历史特征和实时特征。注意,用户的行为数据包括用户在广告系统内部和外部两种行为数据。用户在广告系统内部的行为数据包括用户看到广告的展示、点击广告、以及发生转化行为(CPA结算方式)等。用户在广告系统外部的行为数据包括网页浏览记录、交易记录、APP使用记录等。总体数据量是TB级别,而且也涉及到大量的计算,如何高效地计算和存储这些数据,并且保证高效的查询,是用户数据处理的核心问题。当然,用户数据是需要实时更新的,如果保证实时性在下文中讨论。
广告展示环境的特征数据。展示环境一般分为网页和APP。处理方法和用户特征数据类似,区别在于量级更加大,涉及的运算更加多。试想,将中国所有(重要的)网站的页面爬取下来并分词,再从其中提取出页面的特征信息,需要处理的数据量级有多少。同时,页面可能会经常变化,因此这项工作需要定期重做。这里存在着投入和产出的衡量,例如访问量很小的网站就没必要抓取;小说类网站页面量巨大,但对广告投放的指导性很差,也可以不抓取;但垂直类网站一般都包含了明确的定向信息,是处理的重点。
一般来说,用户特征和广告展示环境特征的数据会存储在独立的分布式集群中。数据存储在内存和磁盘两级,内存中存放热点数据,磁盘中存放全量数据。同时,内存中的数据包括历史数据和实时数据两部分,实时数据流会更新实时数据,在查询的时候,集群负责同时查历史和实时两份数据,合并后将结果返回。
广告数据和广告的定向数据一般存储在检索服务内部,在初期都是全内存的数据结构。当数据逐渐增长,超出单机内存存储极限之后,可以先进行水平拆分,即多个检索服务器组成一个分组,一个分组维护全库数据,在查询时同时查询一个分组内的每台机器,由上游机器对结果做合并。再进一步,因为并不是所有数据都可以进行拆分,数据仍然可能超出单机存储极限,这时可以采用内存-磁盘两级存储的结构,也可以拆分出单独的服务。由于广告系统一般都存在热点数据,因此内存-磁盘两级存储是优先的考虑方案。同时,仔细地设计内存中的数据结构,高效地建立索引,能带来巨大的收益。
一般系统使用的存储结构是B+树,如果使用不当会造成内存的巨大浪费,在后续的文章中会有专门的篇幅讨论这个问题
- 响应速度要求特别快
这一点毋庸置疑,广告对于网站或者APP是附加功能,只能比内容更快地展现给用户。同时,一些特定的广告形式对用户有跳出感,例如开屏、插屏广告,对响应时间要求更加短。另外,在RTB系统中,由于exchange的存在,增加了一次网络请求,DSP系统的响应时间就要更加短。一般来说,一次对广告系统的请求必须在100ms以内完成。其中60%-70%的时间消耗在网络中,另外的部分是主要消耗在核心检索模块中。
网络包括媒体和广告系统之间的网络,和广告系统各模块之间的网络交互。在设计架构时,既要保持系统一定的可扩展性和可伸缩性,也要考虑尽可能地减少内部网络请求次数。同时,在设计和选择RPC框架时,要充分考虑QPS,latency,请求长度三个因素。
核心检索模块中,一次请求会触发多个定向策略同时检索,因此索引数据设计的是否高效是决定检索性性能的核心要素。因为大量的查询操作,CPU往往会成为检索系统的瓶颈,所以很多检索模块的QPS并不高。在实战中,对索引的使用不当也会造成性能的下降,因此需要工程能力比较强的人做 code review 把关。
- 实时性要求特别高
实时性是指数据更新的实时性。下面逐条讨论。
广告数据的实时性。这里最频繁变化的是广告有效性和出价。例如,广告必须在广告主指定的时间段内投放,时间变化时,必须及时上下线。广告主出价发生变化时,必须立即反馈到系统中。广告预算消费完毕后,必须立即将广告下线。
以CPC系统为例,曾经有很长一段时间,很多广告主利用广告系统计费的延迟性骗取大量的点击。例如,给广告设定一个很小的预算(可能只够一次点击),实际产生点击和检索系统接收到计费数据之间,可能会有分钟级的延迟,这期间发生的其他点击,产生的费用广告主就无需支付。
广告定向数据的实时性。与广告数据类似,不展开讨论。
用户特征数据的实时性。用户特征数据往往是根据用户的历史行为计算出的一些兴趣点数据,在起初对实时性的要求并不是很高,主要是因为用户的兴趣点形成往往是一个长期过程,并且变化很平缓。例如,喜欢足球的用户可能每天都会看一下体育新闻的足球页面,餐饮、母婴、装修、军事等垂直领域的用户,也会长期关注相关网站。然而随着电商的兴起,以及移动互联网将时间更加碎片化,用户的兴趣点转移变得非常快。例如,某用户最近对相机比较感兴趣,在某电商网站浏览了10分钟相机产品后离开,打开门户网站开始浏览新闻,这时如果出现了相机广告,将很可能引起转化,这其实是电商类广告最有效的定向方式——retargeting。当然,这只是为了说明实时性的重要程度而举的一个非常粗浅的例子,其中有很多细节有待考量。例如用户如果发生了购买行为之后,显然不应该再推送相机广告。有些快消类产品,重复购买率高,可以定期给用户推荐,但类似相机、汽车、房产等大宗商品,在用户发生购买后,显然不应该再继续投放,而应该投放与此相关的其他广告。在策略处理上,对不同类型的兴趣点的时效性应该区别对待。
另外,在RTB系统中,这一点尤为重要。试想相机的例子,当用户已经发生购买之后,DSP如果没有识别出该行为,认为用户仍然具有该兴趣点,继续出高价购买流量,显然是收益极低甚至可能亏损的。
广告展示环境的特征数据的实时性。网页和APP的内容一般不经常发生变化,抓取一次可以在很长一段时间内是有效的。比较特殊的是新页面,尤其是内容类网站(例如旅游攻略,实时新闻),每天会产生大量的新页面,如果不能及时抓取,在广告投放过程中就无法利用广告展示环境的数据。尤其在移动端,用户的场景化更加强烈,在未来场景定向的重要程度很可能会超过用户定向。在传统的PC广告系统中,一般是将网站分级,优先级越高的网站爬去的频率越高,甚至是API对接。在移动端,有一种方案是在请求中带入网页的重要特征,例如标题、重要关键词等,这需要媒体的支持,广泛使用还有待时日。另外,实战中还往往采用
near line 的设计模型,即当发现请求中出现了新的页面,实时通知爬虫立即爬去并分析,在处理后续的请求中使用。
用户特征数据和网页/APP的特征数据往往数据量巨大,为了能够高效地利用内存,存储这些数据的缓存集群往往使用了只能提供读取功能的数据结构。因此,一般是将历史的特征和实时的特征分开存储在不同的数据结构中,实时的特征可以随时更新,只存储当天数据,在查询时,同时查询两个数据结构,将结果合并后返回。
- 系统可用性要求特别高
这一点比较容易理解,分分钟都是钱,所以广告系统一般都有大量的热备冗余机器,部署在多地多个机房。除了常见的分布式系统高可用方案之外,广告系统还有如下两个重要的方案。
自动降级。由于上文讨论的实时性问题,广告系统很难像传统用户类网站一样,提供一些静态的只读内容,以备在集群全体宕机的时候使用。但在系统内部设计中,可以做到模块级别的容灾,系统化点的称为叫自动降级。即当某些模块出现问题的时候,或者系统资源不够用的时候,系统能够自动地移除出问题的模块,或者非核心模块,保证基本功能可用。比较典型的例子是,如果某一种策略的计算逻辑出现问题,或者CTR预估集群整体宕机,系统还能够正常返回广告,只是收益不如原来高。当然,自动降级只是一种防御手段,当发生这种情况的时候,应该视为线上集群整体宕机同等严重的事故,必须第一时间处理。例外的情况是自动降级是人为预期的,例如有些业务激增场景一年只发生一次,公司不可能为此常年准备大量机器,此时也可以用自动降级的手段保证业务基本可用。
减少启动时间。前文提到,大型广告系统使用的数据量甚至会超过单机内存极限,这时系统的启动时间会非常可观。例如笔者曾经开发过的广告系统,即使进行了水平拆库,单机使用内存仍然达到50G以上,启动时间在30分钟左右,经过后续的优化减少到15分钟。减少启动时间,主要好处有两个:减少运维成本,减少容灾成本。
减少运维成本。和其他互联网系统一样,广告系统也会采用快速迭代的上线方案。有几千台服务器的广告系统,可能会一周多次上线。上线时,为了使服务仍然可用,会分批操作,例如一次只操作5%的机器。这对运维人员是非常痛苦的一个过程。例如1000台机器,每次操作5%,每台机器启动时间在30分钟,整体上线流程将达到10小时,这样的事情每周发生几次,显然是无法接受的。当然,可以选择流量低谷的时间段上线,增加每次操作的机器数量,这样又引入了运维成本。因此减少系统启动时间意义重大。
减少容灾成本。很长的启动时间,会使系统在请求量激增的情况下无法及时使用冷备机器扩容,而增加很多热备机器,第一会增加成本,第二实际情况还是可能会超出预留。而且,当热备机器也难以处理所有请求时,很可能会导致刚刚启动完毕的机器也被打满而无法正常提供服务,触发雪崩效应。此时,必须切断所有服务,重启集群,等所有服务都重启并检验数据完毕后,才能开始对外提供服务。一般来说,当我们听说一些大型网站发生整体宕机,若干小时后才恢复,很可能都是发生了雪崩事故。
据说,历史上某E字辈美国购物网站曾经发生过一次这样的案例,导致整体服务宕机8小时。近两年Amazon的公开的几次事故恢复时间也都在小时甚至天级别,都和复杂的启动流程有关。
作为大型广告系统架构的开篇,本文主要阐述了大型广告系统面临的核心问题的业务来源、处理方案、以及选择方案的时候考虑的一些权衡点。在接下来的文章中,会深入每个模块,详细地讨论技术细节。下一篇会重点讨论检索模块,欢迎关注。