QQ 空间社交广告系统技术架构实践


注:本文整理自 QCon 2017 北京站上的演讲,原题为:《QQ 空间平台百亿级流量的社交广告系统海量实践》

QQ 空间业务背景

空间里的广告除了一些效果类的展示广告外,大部分都是我们自己做的,比如有品牌类展示广告,这是我们 QQ 空间独立的 APP,会放一些像 OPPO 这样的品牌广告,还有品牌自身做的推直播、黄钻收入的广告,大广告主题和内部广告主题的问题是我们要解决的。

QQ 空间业务指标 QBOSS,取这个名字是定位于 Qzone 业务支撑系统,它每天日常有三百亿左右的流量,流量访问是很大的,QQ 空间等产品接入广告渠道有十百个,日常峰值 40 万每秒,性能要求高,我们的要求是整个计算耗时要在 50ms 内。

海量服务基础架构建设

做技术架构之前,先挑一些核心功能来看。首先要建立渠道的概念,这是很规范的管理。另外作为平台性来说,防止对用户进行骚扰,重复看过的广告还出现这是不允许的。再是定投,不可能什么广告都是全可见的,这也是很多做算法,做数据挖掘可以发挥的空间。第四,我们的客户系统是在播放端和广告主之间的一个桥梁,播放端不同渠道有开发诉求,广告投放这边广告的一些核心投放逻辑要实现,是排期性的广告系统。

定义秩序,中间核心是广告,根据广告的业务特点分了四个维度。左边这个就是广告位,这是很简单的事情,针对的角色就是做广告主的人。右边体现的是 QQ 空间方向,体现的是产品的运营导向,防止骚扰运作等等。最上面的方向是真正用户能够感受到广告体验的,资源分为两部分,一个是数据,比如这个图片是什么样子,视频是什么样子,这是素材问题。还有一个很值得的说的,对开发者来说我们给他一个展示模板的定义机制,能够支持开发者在客户层上面制定自己要的是什么样的逻辑,自己去开发。第四部分是对下面的用户细分,广告来了要给更合适的用户,数据上来说就是用户画像和号码包。

再讲讲腾讯通用的技术积累。经典的三层技术层里,接入层有 TGW WNS TSW 都使用的接入的方式,是比较线程可用的。中间这层特别 SPP 是腾讯内部最广泛使用的多线程模型服务框架,把网络通信相关的功能都已经收归了。还有一个同步中心,这是我们自研的一部分技术选型,这部分量级产品现在是深圳、上海、天津三地同步服务的,就涉及到数据层面同步问题,都抽象成一个服务建设出来做同步中心。还有是我们用的比较多的是 SSVR,它类似于一种队列型的服务,但是不像大家用到的云队列服务,我们是一个基于本地流水的服务,这个简单可控,实践证明这么多年用下来没出什么问题。

存储层研究比较多,首先 CKV 是最广泛使用的产品,CDB 是在 MySQL 上建立起来的 Cloud DB,Redis 用的也是比较多,TDW 是针对海量数据做的腾讯数据库。左边是一些系统或者组件,比如织云是我们的运维门户,L5 是做负载均衡的,罗盘是报表,因为我们广告系统用的比较多,DC 上报是分布式海量数据上报的收集系统,神盾做推荐的。那我们搭一个海量系统还需要做什么事情,除了设计之外,用它来凑一凑,海量系统就比较靠谱了。

整个广告系统麻雀虽小五脏俱全,很多服务过了三年都没法看了。我划分了一下,在线服务有策略中心、用户中心、数据中心,还有离线处理主要把用户的数据处理上去。主要介绍三个核心服务。

策略中心是很杂的部门,满足了广告系统的中控,主要是一些逻辑。

用户中心服务 DMP,一个部分是像左边这样,我们去搜集很多平台相关的活跃特性,比如你是不是黄钻,是不是登录活跃用户,把这些数据建立成标签,广告组就可以选出标签实现定投。还有一种是号码包方式,腾讯内部还有很多做数据挖掘的团队,包括数据产生,腾讯的业务实在太大了,很多的数据产生方不能都来我这里,必须用号码包的方式组织起来,这个用的比较广泛。

最开始最简单的架构就是这样。对外服务,提供一个协议是不是这个用户群的,是就回答 YES 不是就 NO,就是做了一个匹配逻辑,数据就很简单,把号码包的数据用户的数据存进去就解决了,这是最简单最开始的架构。

但是遇到了一些问题,一个是 DMP 要做强大,tag 数据一定要多,年龄、性别、装扮活跃、登录时间次数都是 tag 格式,tag 来源很多,很多的数据团队并不一定把数据放在你这个地方,另外更新频率也不同,就算是给你离线缓存权限,存整个平台的数据对客户系统来说也是很大的负担。成本还有开发效率,做这个系统的时候人力成本有限制,不像有的广告系统的团队专门做 DMP,我们这里只有一到两个人开发做这个。还有内存存储成本,存储的选型只能用内存,但是内存价格太高了,那这几百个指标要多少数据?

第一个问题,我们怎么提高用户画像 tag 的建设效率。腾讯内部现在还是这样做的,比如要做年龄和性别进来,一定数据存进来取一个名字,这样每增加一个 tag 效率是很低的。后来我们决定,放弃这个结构,按 ID 来划分,每增加一个 tag 增加一个 ID 是很简单的事情,ID 针对哪些 bit 位也是比较好映射的,比如有 64 位上去,再上去就是 128 了,反正编号编好,有序列号的,每一个 tag 要新增,就分配一些地方,我新增一堆 tag 没有工作量只需要分配协议。

还有是增加一个适配层来处理数据来源,有很多团队有数据,但是没有渠道扩大用户,我们把判断用户的权限开放。Adapter Server 是只要满足协议,把逻辑写好你自己来开发就好。

第三个问题是号码包定投,一开始我们想的也比较简单,存一下用户在哪个号码包里面,每次访问的时候拿这个数据判断就行了,看起来很简单很容易理解,但是实践的时候会发现缺点。投放难度是很大的,广告主投放一个五千万或者一亿的号码包,他需要把这个号码包所有用户的数据都拿出来读一遍写一遍,一个投放操作会影响在线的服务,因为要改在线的数据,在线的容量也会受到波动,广告主什么时候投放号码包也不确定,投放几个也不知道。

很大风险是投放一个等待时间又长,并且影响你的在线服务。我们有三个地方的服务,深圳上海天津,如果你在深圳投放还要同步这个服务到上海和天津。存储回收也比较麻烦,很多号码包跟着广告走的,广告一旦下线,号码包意义就不大了,很难回收它,你只能把这个号码包拿出来再更新一下用户。我们以前做过被动的更新,当用到这个用户的时候再拿出来检查号码包,这是非常被动的。

解决这个问题,我们用了 QZone 自研的存储利器,叫好友参与系统,非常适合号码包的 topic 存储。我们认为 topic 是个主题,号码包就是一个主题,用了号码包的都参与到这个主题中来,比如以前最火的 QQ 农场,你可以看到有多少个好友在玩农场,比如上千个好友都要看他是否在玩农场,这是非常大的计算量,于是我们就做了这个存储系统,可以根据存储系统的数据结构需求,把顺序的号码包处理成一个 Btree 树结构,用 tmpfs 处理这些号码包,使得每个 btree 文件就是可见的包,可视化的。我们的同步也很简单,原来需要跨地域,现在都是可视化操作了。

数据中心服务难点,主要管理用户的广告反馈数据。广告系统里面,数据中心和其他的和 UGC 很大的功能不同是,是读多写多模型,比如一次广告曝光,曝光之前需要看一下这个用户有没有看过这个广告,就需要读一次,如果觉得这个用户能够被曝光,就要写一下曝光记录,读和写的频次都是很高的。比如我发表一个说说相册,一般来说都是读的情况很多,写的情况相对很少。另外还是需要异地服务接入,数据同步问题也是,因为量大了,就容易堵塞通道。

数据中心服务架构剖析,每一个地区是这样一个架构设计,首先结构上是简单的逻辑服务, CKV 存储曝光点击记录,在逻辑读写命令是分开部署的,代码是一起的。另外做了旁路流水,每次写的时候要做很多事情,比如要统计广告的曝光点击量、点击率,第三方监控,在你这里投广告到底有没有投放需要统计一下,这些需求是很慢的,还有自己的一些统计功能,包括还有同步操作到另外两地都是很慢的,这个绝对不能放在在线的写操作里,写操作只能做关键操作即写 CKV。s_server 起到了一个队列作用,很快地接收数据值写到本地文件里去,另外再起一套进程读文件,按需跟通道通信,后端慢的服务也是按需服务的,只要跑得过流水产生的时间就行了。

我们在统计数据做了两个通道,左边是腾讯公司层面做的一个,是基于用户维度,用户实际曝光和点击哪些广告,在罗盘系统上做出一个统计报表。我们自己做了一个统计的数据,根据广告 ID 的维度做,不想再处理很大量数据的问题,我们不关注用户信息了,只关注广告 ID 的质量,就可以合并写操作,数据量、访问量减少,很简单的数据统计就可以把这个报表生成。两个通道两个逻辑,得出的数据是一致的,这个报表的数据就是很准的。

异地同步通常先对比一下通常的功能产品的模型,一般都是三地读一地写,比如你要看照片三地都可以看,你要发表一个照片一定要先接入到深圳,然后再同步到上海和天津。它比较简单,要考虑时序性,如果哪个操作失败了要重试,一定会堵塞通道。我们一定是要多读多写的,在你还在同步过程中,第二次请求已经过来了,比如在上海点了一个广告,本来不会再次出现了,但是还出现了,因为数据还在深圳往上海赶的路上。所以需要同步起来,三地写、三地读就要建六个通道,比一般的产品同步量会大很多,因为它强调的是一致性和时序性。

根据这个我们大胆做了一个不做堵塞通道的同步逻辑,我们敢这么做,而且我们只同步一些信息,比如我在深圳点了一个广告,我并不告诉你深圳存的是什么,上海同步的是什么,只告诉增量信息,因为很多渠道的接触方式也不一样,最后上海天津的存储容量都不一样。这样做,假如通道有一些故障会形成三个孤岛,最大的问题是假如用户在上海点了个广告,下一次请求他又去深圳了,这个广告他又看到了,这种情况下,我们大胆地尝试不用考虑这个问题,实际验证一下,这样的用户按我们的统计,一天影响量才万分之五,我们就不这么纠结,这是比较有特点的异地服务。

平台海量服务运营能力,一是监控能力,接口级别监控,实实在在统计成功率和延时,用户端监控,真正看广告拉出来的延时,还有广告数据监控,广告有没有按时出现,点击率是否符合预期。二是服务质量容量,服务质量就是个容量问题,容量够了质量就好,容量不够质量就不好。容量怎么来?这是广告系统会遇到的问题,广告计算的负载率会徒增,特别是平台类型的广告系统,不像效果性广告可以保持平稳的广告输出量,我们这儿不一样,比如有一些广告对你的系统有一个徒增或突降的状况。

每个广告的计算量都不一样,我们能做的事情,按照云计算的弹性能力来做这个事情,现在的情况下保持很好的服务质量还是有难度。至少对未来要承担的负债有一个大约的预警,你就要有模型去算,因为广告主都会有提前量,比如下午的广告上午就投了,不会马上生效,总有一个时间去检测负载是否足够。

还有是做高效能,就留个 5 倍、8 倍的 buffer,你留多了运维就压力很大,需要提高单机性能,我们一些监测的数据不会在服务器里编解码,除了广告分析数据,都是些 UDP 的协议,尽量简化这个逻辑操作。还有各种分离部署减少毛刺,比如数据中心读和写以及回收等等,一定不是分离的,为了避免有些广告突然间量上来,有一些操作会带来代价,平均下来三个九、四个九的成功率没问题,但是偶尔就是几分钟这样。

容灾,我们不会在单机上存一些跟用户路由相关信息,除非某个机器挂了,做负载均衡覆盖掉就可以了。包括设备管理,把高优先级、低优先级的设备分配管理,针对性做一些工作。支持城市级别容灾,这是经过像天津大爆炸一样的检验。还有是细节,所谓容灾,在一些很关键的地方都是可以覆盖住的,但是把你弄死的都是一些细节,重在细节,找短板。

广告系统 ROI 优化之路

再讲一下广告系统 ROI 优化之路,这是我们走过的弯路。走 ROI 就是推荐算法,包括和腾讯内部合作做一些事情,做完之后可以看看我们做的一些事情。首先在 ROI 优化前先要找准自己的瓶颈,我们做推荐算法,量级并不大,因为都是大业务的需求,可能广告里就五个、六个,不大于十个的广告,但是效果是很大的,甚至会经过初选、细选,复杂度都不一样,真的可以做到千人千面。需求量也不稳定,但是广告质量是可控的,因为广告主离我们比较近,我们了解他们的需求。

做了三种尝试。第一种是最开始走的弯路,有用户进来通过各种方式查数据,查在线的优化,会发现就算你知道用户喜欢哪一类广告,但是现在的库存里面没有这类广告,你没办法的。第二种方式,同样一个项目广告通过更多渠道去做,可能这个广告的用户群并没有在这个渠道上投,他去了另外一个渠道也可以做,对于广告主来说投放的代价是很大的,因为每个投放会带来开发成本和投放成本增加,每个渠道都是不一样的。

第三种是非常有用的方式,还是渠道给你,但是你不能再用一个广告了,必须要设计多个广告进来自己跟自己竞争,不同点在于广告 A 投的是这个用户群,广告 B 投的是另外的用户群。让用户投票,二三十分钟就可以得出每个广告的点击率,让点击率高的广告渗透下来,起到广告主和用户间的沟通桥梁,你做的工作好坏用户会给你反馈,这个是刺激到广告主要去新做一个广告。

ROI 另外一些很重要的功能点就是负反馈。比如有的人看广告七次都不会点进来,你给他投放再多次他也不会点,他选择性地忽视了,这部分人群不要给他出广告,对他来说体验也好了,对我们来说收益上也没什么损失。还有是频率限制,流量出来后并不是马上消耗完,平稳地在一天或者更多天消耗完。深圳发现一个好处,第三方投放的时候,因为他们往往不太知道流量,等到网站顶住了的时候,流量消耗完了,广告主不知道我们 Qzone 的流量有多厉害,但他肯定知道系统能顶多少量,让我们给他频率限制住。

还提供一个工具叫 QBOSS 人群分析统计,你投完广告之后我们会分析广告投放地好还是不好,选一组特征值,曝光人群里占特征值比例有多少,点击人群占百分比有多少,如果两个持平了,可能说这个在广告受众里不是很明显的东西,如果点击比例高于曝光的比例,这就是很正向的事情,你后续投这类广告的时候就要优先投这部分人群。我们提供的只是一种工具,一个渠道让你做个正负的反馈,广告主的优化必须是你自己想办法做的事情,做的好就是百分之四百,做的不好还是百分之十几。

写在最后

整个客户层还有很多体现腾讯海量服务价值观的东西,比如动态运营、全网调度等等,都在用,我不再赘述。

最后讲点心得,技术价值一定要体现在业务上面,我们开始做的广告系统,比如三大系统听起来很朴素,但是能解决业务问题就是一个好的东西。还有做职责识别,我们把一个广告系统分成一个大的方向,每个大的方向有它的职责问题,抽象一下也就能满足大系统小做,把一个复杂问题简单化,比如用户中心只需要判断这个用户是不是用户群的,别的问题都不用管。

再是抽象一个层,可以解决很多需求问题,有些 tag 的识别是非常特殊的,但抽象一个层往往能解决很多复杂问题。善于在存储上做性能提升例如 redis 的成功,逻辑很简单,性能也非常好,包括刚才介绍的好友参与系统都能很快解决问题。

低成本,一上来内存做的成本高,可能考虑到其他的存储介质,看看有没有一些协议压缩等等,很多技巧大家都会掌握,但是我建议大家还是从业务角度上考虑问题,可能给你带来的是上十倍、百倍的成本节约。尽量做无状态的服务,能够保证你的负载均衡、一致性,一旦有状态真的要悲剧了。还有各种部署的分离,在一些小流量系统里这个有点绕,但是在海量系统里这个还是很有必要的。我们所有的架构设计都是依赖自己的数据经验的,但是在你上线后一定要通过数据验证它。做异地同步的问题也是,上线后到底少到什么程度,才能让这个架构继续工作。

作者介绍

冯启航,2010 年毕业于四川大学加入腾讯,负责 QQ 空间功能后台开发工作。负责建设了社交平台基础增值会员体系、个性化特权业务、营收广告系统、营销活动平台等海量服务。技术上践行海量服务之道和敏捷开发的结合,喜欢做技术驱动型的工作,挖掘技术在业务上的价值体现。2016 年主导了空间宠物 AI 机器人聊天项目,在人工智能领域从新开始。追求团队把算法和工程有机结合在一起,目标为导向让机器学习、人工智能真正走入应用场景。

马军伟
关于作者 马军伟
写的不错,支持一下

先给自己定个小目标,日更一新。