广告引擎平台化演进之路

时间:2022-11-21 13:05:44

   1、平台背景

  广告是互联网最常见的商业模式,几乎遍布所有的互联网产品。网易程序化广告团队主要负责网易系自有流量的变现,包括网易新闻、邮箱、公开课、PC&WAP等,对外承接广点通、穿山甲、快手等外部ADX广告流量。变现方式包括品牌广告、效果广告、RTB广告。广告投放引擎作为流量承接和广告决策的核心系统也逐步进行技术的重构和迭代,在满足业务快速发展的同时,研发了广告引擎平台,提供统一的检索、计费、排序、高速缓存、日志和分布式通信等基础能力,并且先后将各个业务线迁移到了引擎平台。这次主要介绍整体平台化的过程以及重点服务的技术演进。

  1.1 从0到1系统搭建

  从0到1搭建系统的过程,最重要的就是要快速布局、开发、上线。系统的初期都是进行小步快跑的迭代,但是也因此欠了不少的技术债。随着业务的不断发展和壮大,衍生出了品牌广告引擎、效果广告引擎来承接不同场景的业务。品牌广告主要是为KA广告主服务,售卖方式以CPM为主,效果广告主要是为中小广告主服务,以点击、转化效果为核心目标。综合来看投放引擎基本上有以下特点:

  高并发:每天承接10亿次以上的广告请求

  高可用:因为广告和收入相关,需要保证系统的可用性

  实时计算:实时进行广告的召回、计费,满足广告主多维度定向、用户频次、平滑消耗等投放要求。通过算法模型针对流量特征对广告进行实时排序。

  海量存储:记录用户级别广告浏览点击等行为信息、海量的广告过程日志存储和分析。

  1.2 平台化系统重构

  随着业务的发展,单体架构的引擎逐渐变得臃肿、冗余。在业务上品牌和效果广告虽然有所差别,但是技术上有很多可以共用的地方。结合多年广告行业的经验以及对于业务的理解、技术的抽象,团队进行了广告投放平台的领域建模,通过基础架构的升级改造,将单体结构的引擎升级成了分布式系统架构。主要包括以下方面:

  构建统一的服务层,提供广告索引、计费、用户标签、频次控制、排序等技术能力,使每个领域的能力都能深度发展,与业务解耦;

  各个业务线按需接入所需的服务能力,避免重复开发,统一技术架构;

  对于上层业务提供投放全链路的统一监控和报警能力;

  完成技术的积累和提升,使技术可以和业务并行发展,深入研究。

  平台化的发展是以易效引擎为基础进行迭代的,历时半年的时间完成了平台化的建设、服务的搭建、易效业务的迁移。整个广告平台的系统架构如下图所示,主要分为:业务层、服务层、能力层和数据层。  

广告引擎平台化演进之路

  业务层:易效总控,易投总控,易品总控。涵盖了品效三条具体的业务线。其中,易投是有着10年历史的老系统。

  服务层:索引服务、计费服务、效果服务、uts服务、频次服务、算法服务,为上层提供公共的服务能力。

  能力层:监控服务、配置中心、注册中心、实验平台,主要是为了服务层提供更加完善的辅助能力,使得服务层更加高效和稳定。

  数据层:使用NCR、MySQL和hive等组件,提供统一数据存储功能。

  技术架构升级完成之后,带来了技术和业务两方面的收益:

  机器成本:节约30%。开发成本和代码维护成本平均减少了50%。

  性能提升:在整个架构的演进过程中,不断重构了业务模型,删除老旧代码,优化各类数据结构和协议。比如易投广告业务在接入平台之后,广告检索平均耗时从原来的12ms降到了9ms。

  灵活高效:在整个架构、业务重构的过程中,我们将各个业务线的个性元素抽象成可配置广告策略,共性元素下沉到服务层,更好地支持业务层的灵活变动。

  各个服务可以各自进行技术演进,这个也是平台化建设的重点。平台化之后,每个服务可以脱离语言、技术和平台等限制,分别演进,更好地为业务发展保驾护航。

  平台化搭建完成之后,我们又将有10年历史的CPD投放系统接入了引擎平台,依托于引擎平台提供的通用技术能力,解决了这个系统不可维护、开发效率低、服务器资源占用多等问题。截止到目前,所有投放引擎的广告投放都是基于引擎平台提供的服务来完成的,达到了资源最大化的复用,以及保证了各个系统的稳定。在引擎平台下各个服务的纵向深度进化,主要包括:索引服务、用户标签服务、监控服务。

   2、引擎平台主要服务能力

  2.1 索引服务

  广告索引服务主要功能是提供实时广告召回。拥有一个高性能、高可用的索引服务是广告平台最基本的要求之一。在广告业务中,该服务主要提供倒排检索和正排检索两个功能。倒排检索:通过输入广告请求的用户、位置信息等相关特征,返回一批符合条件的广告素材。正排检索:输入广告素材id,返回该广告的物料、监测等信息。

  索引服务需要解决的问题就是如何保证在大流量、海量数据下的广告数据查询、更新和存储。索引服务是我们广告平台中的重点项目之一,为了不断优化性能、将功能检索能力下沉,我们对该服务进行了多次的技术升级。

  2.1.1 索引服务v1 :服务抽象和平台能力建设

  最初,品牌广告和效果广告因为业务需求的不同是分开部署和开发的,都有自己的基于内存的索引模块,造成了很多功能的重复开发,所以就产生了构建统一索引服务的想法:如果能开发一个索引服务提供给上层业务调用,那么人力和运营成本都将大大减少。为此,我们将在梳理各个业务方的需求之后,将该服务拆分出来,那就是索引服务v1。整个索引服务v1主要有两个角色,分别是queryNode和updateNode。

  queryNode:负责提供广告检索功能,以集群方式部署。主要提供广告检索功能。为了保证高效的水平扩/缩容、该角色被设计成无状态的服务。并且采用bitset作为存储结构,bitsit做交、并等操作使用位运算,并且占用空间非常小,保证倒排查询的效率。

  updateNode:负责数据同步、数据结构打平和索引建立,同时负责调度queryNode读取索引数据。 主要解决广告数据更新的问题。在业务中,我们发现不同的广告信息,对于数据的实时性的要求是不一样的。比如投放状态这类相关的属性字段,对于实时性有较高的要求。但是有些信息,比如投放条件等,用于广告召回的字段,对于实时性要求不高。所以,我们采用分而治之的策略去解决这个问题。首先,对于实时性要求高的字段,我们称之为属性字段,我们可以直接修改正排,毫秒级就能生效。其次,对于第二种实时性要求没有那么高的字段,我们称之为索引字段,我们通过修改倒排信息,定期重建倒排索引,来使它生效。

  两个角色接入配置中心和注册中心。注册中心接入主要是为了updateNode对于queryNode的调度,其中包括健康检查,索引更新通知等等。比如流量突然增大,需要加机器,我们只需要在新机器上启动queryNode,它就会自动注册到服务中心,此时updateNode便可感知到它,开始对它开始进行调度,从而使得提供广告检索服务,从而做到平滑扩容。整体的架构如下图所示:  

广告引擎平台化演进之路

  索引服务v1除了将索引功能抽取出来之外,为了更好地支撑广告业务高速发展,还提供了统一增量更新、数据备份、广告追踪等功能,通过索引服务能力的建设,使品牌和效果产品线的总控服务得到了简化,索引服务提供的能力可以同时支撑多条业务线,节省了开发资源。但经过业务的迭代发现了v1版本的几个问题:

  数据同步、索引建立都是和业务强耦合,接入新业务时候工作量很大;

  查询功能不够灵活;

  效果广告需要支撑海量数据场景,当数据膨胀后v1版本的弊端逐渐暴露;

  所以,为了解决上述的问题,我们进一步继续索引服务v2的研发。

  2.1.2 索引服务v2 :为海量检索场景提供技术服务能力  

广告引擎平台化演进之路

  索引服务v2 主要包括queryNode集群和es集群,v2版本主要有以下3个特点:

  技术引入:为了支持当时的业务规划进行海量的素材数据检索,并且需要快速落地,引入了es集群。通过调研,es支持海量存储、分布式、索引的分片和副本,这样的架构能解决1.0单机内存瓶颈的问题。自带持久化功能,可以让我们摆脱使用数据库备份数据的困扰。

  开源扩展:es并不是拿来就能使用的,在某些地方达不到我们的要求。对此,我们做了些针对性优化,并且根据现有业务场景,在es上层完成了一系列模块扩展。

  业务隔离:V2另外一个重点就是将业务从索引服务中剥离出来,把业务相关的逻辑拆分成一个单独的同步服务,这样整个索引服务通用性更强,业务接入的效率更高。

  索引V2服务的改进主要体现在数据同步、层级索引、检索优化:

  数据同步:在v1中全量同步任务效率是比较低的,因为是串行地从业务端拉取所有的数据到内存,如果数据量很大的情况下,耗时会很长。在V2版本中,对此进行了优化,将整个同步任务大致分为任务触发,数据导入,任务完成,三个阶段。在数据导入阶段,通过并行的方式,完成广告数据导入。实现方式是使用多个线程按照分片规则,同时从存储中读取广告数据,传输给queryNode节点,然后写入进es,所有分片数据写入成功之后,才会算是任务结束。耗时从2分钟缩短到30秒以内。全量同步改成批量导入之后,虽然性能提高了,但是任务流程的增加,会带来一些不稳定的因素,为此我们研发了--同步任务管理功能,保证分布式任务的高可用。  

广告引擎平台化演进之路

  层级索引:在数据查询方面,在V2.0中为了保证检索性能做了很多工作,效果最为明显的就是层级索引和检索流程的优化。通过层级索引解决了多类文档关联查询性能的问题,在数据同步的时候,根据配置信息,为每条广告数据生成文档id和父文档id,让不同广告实体之间建立父子关系,从而能提前建立层级索引,这样在查询的时候,只需要将查询条件组装好,一次就能将数据都查询出来,降低了检索耗时。  

广告引擎平台化演进之路

  检索优化:为了进一步缩短检索耗时,我们简化了es索引流程。原生检索流程是,数据写入es之后,会生成两份数据,一份source_存储原始文档,一份倒排索引。查询的时候,查询倒排索引获取文档id,通过文档id去原始文档中查询字段信息并返回。索引架构2.0中,queryNode需要对每个请求都透传es返回的信息,不仅增加没有必要的解析工作,还因为传输数据变大,提高耗时。为此我们对这块进行如下优化:

  禁用了source原始文档:当禁用source原始文档之后,es中只有倒排信息,缺少原始文档信息。为了解决这个问题,我们在建立索引之前对每条记录,构造出一个fulldata字段存储原始文档,并且将其缓存到queryNode中。

  通过queryNode实现正排检索:通过文档id查找文档字段信息。es返回的数据只需要将文档id返回给queryNode,然后queryNode会根据配置信息,组装文档信息。大幅度降低了耗时。 这样还有个好处就是,正排数据查询只需要访问queryNode,不用再访问es集群,在降低正排查询耗时的同时,也减少了es集群的压力,一举两得。  

广告引擎平台化演进之路

  索引服务V2上线之后,在可用性、扩展性和性能各个方面都有所提升,目前检索平均耗时在10ms左右。下一步,我们会继续研发3.0版本,结合网易广告自身的特点,为了进一步节省资源,自研分布式检索内核替换es,更好地支撑业务发展。

  2.2 用户标签服务

  用户标签服务(UTS)是为了广告请求时实时获取用户的兴趣标签的服务,以实现广告的精准投放,从业务角度考虑UTS服务其实很简单,就是通过各种数据源的用户信息组成用户画像,但是从技术角度来看,用户标签服务的构建是一个典型的单体架构到服务架构的应用。现阶段用户数据主要分为三大类:

  dmp数据:主要包括用户的信息:性别、年龄、兴趣、设备类型等等。

  人群包:根据业务策略生成人群包,完成目标人群的投放。

  广告标签:是广告平台内部生成标签信息,主要是根据用户的广告行为生成的偏好信息,比如用户的长期偏好、短期偏好等等。

  在平台化之前,每条业务线都要自己单独维护自己对于用户标签数据的写入、存储和读取功能,这样会有以下这些问题:

  数据种类多:由于每种数据种类,使用规则和格式都不一样,当业务发展到较大规模时,这部分功能的接入和维护都消耗了非常大的人力和时间。

  数据与业务耦合:如果遇到底层数据服务升级、数据迁移或者协议升级,那么每条业务线也会受到很大的影响。

  重复开发:由于引擎对接了三条业务线,有的数据在其中一条业务线落地之后,在其他业务线使用时需要进行再次开发,造成了数据对接功能重复开发。

  资源浪费:在存储资源上没有统一的规划和管理,存在资源浪费的现象。  

广告引擎平台化演进之路

  所以在平台升级过程中,对于各条业务的相关用户信息的业务功能进行了重新梳理,目的是降低模块耦合,减少重复建设,将用户标签的管理和查询功能单独抽离出来,成为一个服务。所有的业务端(易品、易投、易效)脱离了和数据源的耦合,所有对于数据源的管理都由UTS统一负责接入和管理,另外UTS服务还负责标签的路由、选取策略实现、标签查询。所有的数据源都是通过配置实现的,各个业务线可以配置所需数据源,由UTS统一负责数据的组织、加工、查询。UTS服务的架构如下图;  

广告引擎平台化演进之路

  该系统主要分为三模块:策略模块,标签查询,标签推荐。为了解决海量用户数存储的问题,统一将所有标签数据都存成pb格式,利用pb性能高、兼容性、压缩比例大的优势,节省了约30%的空间。同时UTS还接入了配置中心、AB实验平台,实现流量切分,对于不同的流量资源,使用不同的数据策略,挑选合适的用户标签。策略现在主要分为两类:

  常规策略:UTS收到上游广告请求时,从配置中心拉取该业务线的数据查询规则,通过规则组合在UTS内部进行数据查询,通过DMP或者人群包数据源来丰富用户的标签信息。

  推荐策略:根据用户历史广告行为(点击、浏览、激活)的数据,通过协同过滤的方式找到标签相似度高的兴趣标签,优化广告的检索逻辑,实现效果优先。

  2.3 计费服务

  索引解决广告召回问题,UTS解决用户识别问题,计费服务则主要负责解决广告的消耗能力问题。计费服务的核心问题是:减少超投,平滑消耗。所以计费服务有对外提供两个功能用以完成控量和计费功能,分别是预算过滤和效果回收。  

广告引擎平台化演进之路

  预算过滤:将请求中不符合预算限制的广告过滤掉。这里预算限制的策略包括:预算平均策略、平台预算限制策略、广告位预算分配策略、尾量控制等等,这些策略的目的就是将预算合理的分配到合适的流量上去,使得广告主和媒体方都能收益最大化。

  效果回收:主要完成用户观看广告之后产生的行为监测收集功能,需要记录用户的每一次请求、广告出价、曝光、点击、转化等等行为数据。效果回收服务每天需要承接亿级别的请求,并根据行为进行用户频控、计费等。为了保证相关业务的可靠性,需要将用户行为以日志的形式存下,并且实时更新用户的效果数据。最终通过离线+实时的方式将日志采集到HDFS和Druid中。

  该服务中过滤模块、效果模块和预算计算模块都是以集群方式部署,保证无单点,而且计费数据是非常重要的数据,使用多重数据备份方案,并且接入了详细的实时监控和自动恢复机制,一定要保证服务的极高可用性。除此外,预算过滤也是广告请求流程中的一环,对性能也有较高要求。为此,我们在实现中使用无锁设计、多级缓存等机制等等来保证耗时,目前平均耗时在1ms。

   3、服务保障与监控

  广告投放业务是涉及到收入的核心业务,这对我们整个平台的健壮性和监控能力提出了非常高的要求。为了保证广告平台上所有的广告服务的正常运行,我们后期也对广告平台上的所有监控服务做出了梳理和分类,搭建了scout监控服务。目前广告平台上的监控主要分为三大类:系统监控,指标监控,业务监控。

  系统监控:主要包括每个服务下各个模块耗时、机器节点的CPU、内存等一些系统级别的监控信息。

  指标监控:主要包括各个业务线、重点位置、重点平台等维度的广告请求、返回、胜出和收入等广告业务中的一些核心指标数据。实时监控这些数据的变动,如果发现波动较大的情况,能立马用邮件、电话、popo的方式通知相关人员。

  业务监控:主要包括对于上层业务流程的监控,比如广告日志字段监控,广告投放追踪监控等等。本类监控和指标监控的区别在于两点。第一,相比指标监控,业务监控更加关注过程,而指标数据更加关注结果。我们在分析投放效果的时候,光有结果数据是没有办法去做投放效果的优化和提高,需要过程数据的支撑,而且当系统出现问题的时候,一般过程数据要比结果数据更快的表现出来,从而能更加减少对于业务的损失。第二,本类监控更加贴近上层业务,会去根据不同的广告业务模型去做跨系统的监控和分析,针对性地分析不同业务的核心产品的广告数据,并且做好定制化的报警。让投放过程的每一步都有据可查。  

广告引擎平台化演进之路

  对于系统监控,我们直接使用集团的哨兵系统。

  对于指标监控和业务监控构建了scout监控服务。该服务以jar的方式接入到平台上各条业务线,将采集数据发送到kafka。之后会有集群会去消费kafka中的数据,进行过滤、聚合等操作,将总控、索引、频次、计费、排序等各个服务采集数据串联起来,进行存储和预警。此外,我们也会从Druid中拉取各类指标数据进行环比、同比的相关规则的检查和报警。并且按照指标的不同重要性,使用不同的通知方式去报警。

   4、未来的发展

  永恒不变的就是变化,广告业务在不停地发展,这就需要我们主动去做技术升级和架构演进,拥抱变化,时刻感知上层业务。这样才能实实在在地解决各种的核心用户、场景问题,而不是让技术与业务脱节。我们最开始做服务拆分的主要目标之一,就想让每个服务可以各自迭代,深入研究,达到“又精又专”。到目前我们也是如此在做,接下来每个服务进入了新版的迭代研发中。

  索引服务3.0 ,主要解决2.0强依赖es的问题。此外,进一步收集广告业务其他的大数据查询、存储的场景,准备自研索引服务框架,提供统一的索引解决方案。

  计费服务2.0 ,目标通过调研国外论文和行业方案,引入“参竞率”参数,完成预算释放的自适应调节,优化计费主体流程,使得预算更加平滑,进一步减少超投。

  语言升级,考虑到广告业务大部分都是高并发的场景,java的gc对于某些基础服务的影响还是比较明显。为了进一步地提高服务性能,未来我们可能会分批次,分服务地进行语言升级。对于业务敏感的服务继续使用java,对于性能要求高的、变化不大的基础服务,替换成go。

  最后,广告技术团队仍然任重而道远,希望发扬务实、重积累、重技术的团队文化,最终通过团队技术能力支撑业务的快速发展。