百度技术|品牌广告投放平台的中台化应用与实践
导读:随着业务和团队规模的增长,原来单体应用的技术架构无论从研发效率还是系统性能来看,都出现了瓶颈。微服务化的技术架构改造(遵循领域模型将现有单体应用按照业务边界拆分为多个微服务)提上议程。本文主要分两块,首先介绍品牌广告投放平台的架构演进;其次,结合实际业务,介绍在践行中台化理念和微服务化改造过程中碰到的问题和解决方案。
投放平台的架构演进
品牌广告投放平台(前身是锦囊平台)于2011年诞生,至今已有9个年头了,期间技术架构主要经历了3次大的迭代演进。1.0版本基于的是单体应用架构,各产品线的投放均集成在同一个应用中。2.0版本根据产品线对投放平台进行了更细粒度的拆分,不同的产品线都有独立的投放平台支持,同时抽象并下沉公共能力,构建了部门级的基础库。3.0版本针对2.0中的各投放平台,从业务模式出发,以业务模型为边界,定义了7大业务中台(资源中心、报价中心、竞拍中心、订单中心、投放中心、数据中心、用户中心),覆盖品牌广告业务的全流程,包括售前、售中和售后。技术架构上采用微服务化理念,并依托商业平台大部门的基础设施,高效支持了10+产品的售卖和投放。
下面从技术架构视角详细介绍平台的3次迭代演进,以及背后的驱动因素。
01 1.0版本
1.0版本的诞生是在2011年,为了支持品牌专区的售卖和投放,系统取名为锦囊。随着业务的发展,品牌广告产品形态逐渐丰富起来,统一通过锦囊平台进行广告投放,模块图如下所示:
不难发现,1.0版本的一个最大问题是:各产品线的投放都由同一个平台管理,导致系统的开发、测试、上线会影响所有产品线。对于A产品的一个微小改动可能会引发B产品的一个bug,存在牵一发而动全身的风险,同时产品线的互相耦合导致研发效能的降低,随着开发人数的增长,项目管理的难度也成级数上涨。于是,为了解决这些痛点,2.0版本应运而生。
02 2.0版本
如上图所示,和1.0版本最大的区别是,各产品线通过独立平台进行投放,开发、测试、上线互不影响,即做到产品线维度的隔离,同时将可能复用的功能最大程度沉淀到团队的基础工具库中,避免各业务团队重复开发。但是,随着产品线的增多,发现有很多相似的业务流程(比如售前流程中的询量、询价、词包管理等)并未完全统一,基础工具库的存在仅仅是代码层面的复用,最多也只是是模块层面的复用。对于新接入的产品线,相似的业务流程仍旧需要重复开发一遍,费时费力。针对此问题,借鉴中台化的理念,将品牌广告从售前到售后的全流程进行了拆解,定义了全局的业务模型,并以此为边界抽象出7大业务中台,即3.0版本,希望可以实现服务复用,甚至是产品复用。
「复用的不同层次」
依据可复用结构图的复杂性,从低到高依次划分为:代码复用、模块复用、服务复用、产品复用。
1、代码复用,在此处窄化为函数、方法、或类的复用,代码是复用的最低层次,也是服务质量的基石。复用的代码通常无法脱离自身服务提供独立完整的功能,不具有模块的规模
2、模块复用,也叫组件复用,模块是基于功能划分的单位。模块复用比代码复用高一层级,是因为它通常可以提供较为独立的功能。
3、服务复用,这里的服务复用可以分为两种:3.1 将2中可复用的模块直接抽离成微服务,单独提供功能。3.2 将被多场景用到的业务能力,抽离为微服务,做为底层业务能力。
4、产品复用,产品是对外交付的最终的成果,产品复用是项目体系内最大程度的复用。较为实际的一种产品复用是指:可以直接设计出可组装的产品模块,通过对模块的组装生成不同形态的产品。
03 3.0版本
基于2.0版本中的抽离思路,品牌广告投放平台拆分为 一个业务前台 + 7大业务中台(独立提供服务,即达到服务复用层次),如下图所示:
对7大业务中台做个简单的介绍。
1、「用户中心」,沉淀用户信息,提供不同角色的鉴权能力,不同账户之间的主子关系。主要的业务实体是:账户和角色。
2、「资源中心」,统一管理售卖资源,包括产品、频道、页面、广告位的管理。同时覆盖售前流程中的询量阶段,提供资源的询量和锁量能力,库存的查询和冲突校验能力以及词包的管理。主要的业务实体是:资源和流量。
3、「报价中心」,面向内部人员的统一售前管理平台。负责报价的审批流程和资源的价格计算。主要业务实体是:报价单。
4、「竞拍中心」,提供标的的管理和竞价能力。底层沉淀了多种竞拍策略(有明拍、暗拍;GFP、GSP;单胜者和多胜者等),满足不同产品线的需求。主要实体是:标的、竞价轮和出价。
5、「订单中心」,提供自建合同管理能力(包括合同创建到导入投放的流程框架、合同状态流转全托管)和资金操作能力(为业务方屏蔽底层资金包的操作细节)。主要实体是:合同和订单。
6、「投放中心」,提供两大类能力,一类是投放层级的管理,包括推广、单元和创意;另一类是物料管理,包括物料的增删改查和内容样式审核。同时对接检索端,将投放层级信息实时同步至检索端。主要业务实体是:推广、单元和创意。
7、「数据中心」,对接部门数据中台,为各产品线提供统一的、多维度的数据查询接口。主要实体是:数据表。
需要注意的是,以上7大业务中心仅仅是从业务视角进行定义和划分,并不代表每个中心就只有一个服务。基于技术架构和业务职责,每个业务中心可以继续划分为多个模块,例如投放中心又被划分为 检索适配服务、物料审核服务、实验投放服务等。
04 微服务化改造
简单总结下1.0-->2.0-->3.0架构演进的驱动因素。1.0到2.0最主要的驱动因素是品牌广告产品形态的丰富(售卖投放逻辑互有差异),为了高效支持产品的售卖,从大而全的单个投放系统拆分为以产品线为粒度的多个独立投放平台。2.0到3.0最主要的的驱动因素是践行公司中台化的治理思想,最大程度的实现服务级能力的复用,支持产品创新的快速落地。
「中台化和微服务的关系」
中台是一种企业治理思想和方法论,微服务是技术架构方式。中台化的落地,需要使用微服务架构。微服务化是践行公司中台理念的一种技术实现手段。
05 单体应用的缺点
在介绍微服务化改造之前,先来看下单体应用的缺点。我们从研发效率、技术架构、组织结构三个角度来讨论。
5.1 研发效率
研发效率可以从3个方面来看,系统复杂度、开发并发度以及沟通成本。
- 「系统复杂度」
在团队创建初期,项目刚起步阶段,"单体优先"的方法会更加适用。对于小型团队来说,没必要一上来就使用微服务架构,徒增复杂度。但是,随着业务的增长,系统的功能会愈加复杂,一个最直观的点就是业务逻辑。所以如果不及时对系统进行拆分的话,研发人员就要忍受在一个日渐臃肿庞大的单体应用中进行日常迭代开发,一个微小的改动甚至会触发陈年老坑。这样的恶性循环就会导致每次预估开发排期的时候研发人员会特别谨慎,因为他不知道可能的影响面,需要花时间进行评估,研发效率低下可想而知。
- 「开发并发度」
当研发人员集中在一个单体应用中进行迭代开发的时候,很难做到较高的开发并发度。由于各功能模块耦合在同一个系统中,所以不可避免的会存在串行开发,当然你也可以并行开发,但是需要花更多的时间去解决各ci或者不同分支间的代码冲突,记住:在解决代码冲突的时候要谨慎,小心引入bug。所以,过渡耦合的技术架构,会降低研发效率,这也是为什么很多紧急项目不是光靠增加研发人员就能缩短交付周期的一个原因。
- 「沟通成本」
对于小型团队来说,沟通成本很低,很容易就能做到让团队所有成员了解掌握系统,这个时候单体应用的方式运转效果会非常好,这也是为什么几乎每个系统都是这样开始的(锦囊系统)。但是随着团队的增长,沟通和协调成本会不断加剧,为了让团队内的众多研发人员信息共享,就需要额外组织一些会议来做信息对齐,沟通成本的增加间接降低了研发效率。
5.2 技术架构
技术架构也从3个方面来看,系统的高并发(吞吐量)、系统的高可用(稳定性)、系统的可运维。
- 「高并发」
高并发指的是系统的吞吐量,即处理性能。比如询量、锁量、询价、竞拍等业务场景都有对系统吞吐量的要求。但是通常情况下,业务场景对于系统中的不同功能模块有着不同的性能要求,各功能模块需要应付的qps也有高有低,处理的业务逻辑有简有繁。如果是单体应用,那么服务的水平扩展必然是整个系统级别的,会造成不必要的资源浪费,甚至对于某些模块不必要的扩展,会导致外部服务的压力增大(比如对于数据库的连接数),系统整体的吞吐量提升可能不符合预期。因此,要想提高整体系统的吞吐量,首要前提是将整个系统按功能职责进行服务拆分,然后对有性能瓶颈的服务做针对性的优化和水平扩展,补齐系统中的性能短板,和木桶理论有相似之处。
- 「高可用」
高可用也就是系统的稳定性,对于单体应用来说,集成了所有的功能模块,系统的整体稳定性受各功能模块的稳定性共同决定。在特殊情况下,无法按照服务的重要性做服务级别的降级处理。
- 「可运维」
考虑系统的运维成本,主要指系统部署成本、线上问题排查成本。一个系统中的不同功能模块迭代的频率肯定是不同的,有些模块相对稳定(越底层的模块越稳定,离业务越近模块变更越频繁),每次迭代升级只需要上线有变更的服务,如果没有拆分微服务,这是无法做到的,你只能接受每次上线整个系统的事实。另外线上问题排查、报警接收、问题修复等场景,微服务化的技术架构成本更小,风险可控性更强。
5.3 组织结构
一般来说,很少会出现多个团队共同负责一个系统的情况(沟通成本高,ownership不明确),当然一个团队是可能负责多个系统的,即通常情况下,团队和系统的对应关系是一对多,多对多的情况很少发生。结合品牌广告的实际场景,每个系统都是一个独立的广告产品投放端,即每个产品会由独立的团队负责,组织结构上是按照业务线进行垂直划分的。当然,这也不一定不好,在产品孵化初期,需要快速实现落地,验证想法,这类组织结构的划分是非常合理的。但是随着团队业务的不断稳定,团队间的业务壁垒会加深,如果没有按业务能力(微服务)划分的横向团队,很多业务线间的相似业务组件甚至服务是很难抽象下沉,做到复用的。最终的结果是越来越多山头林立,各山头之间某些业务流程又非常相似,再拆分、再融合的成本将会随着时间的推移呈指数上涨。
06 微服务化的难点
正如上节所述,随着团队规模和业务范畴的扩大,对各系统进行横向微服务的拆分势在必行,即技术架构3.0,沉淀了7大业务中台。在整个微服务化的落地中,遇到了不少的难点,概括来说:
6.1 团队组织架构,每个微服的owner是否明确
著名的康威定律「设计系统的组织,其产生的设计和架构等价于组织间的沟通结构」阐述了架构和组织结构的关系。组织和系统架构之间有一个映射关系(一对多),两者不对齐就会出现各种各样的问题。可见,每个微服务都需要有明确的团队来负责,这点需要从上到下来执行。目前大商业通过团队组织架构的调整,每个服务的ownership都非常明确。
6.2 微服务拆分粒度
这个难点在品牌广告的微服务实践中解决的比较好,团队在 cpt、cpm、cpm_gd 等合约类广告售卖模式上深耕多年,对相关领域的业务模型有深刻的理解。因此,微服务的拆分按照投前、投中、投后划分为7大业务中台,各业务中心所承担的职责按照业务模型进行边界划分,不同服务负责业务实体的不同生命周期,职责明确、边界清晰。
6.3 自动化构建部署以及可测性
微服务化后,服务数量比原来单系统要多的多,在rd联调和qa测试中,不同环境下的多服务部署非常令人头疼。比如要测试某次迭代,需要完整部署七大业务中台和一个业务系统,大约涉及10+模块。同一时间若有其他业务迭代也需要测试,那么同样需要部署一套10+模块的完整环境,且不同产品的测试环境需要互相隔离(例如消息、db等),如果没有自动化构建部署的工具,手动逐个模块部署将耗费大量人力。依托公司的基础设施可以快速搭建一套完整的拓扑环境,支持不同场景的测试需求。
6.4 微服务治理
微服务治理包括服务的变更(包括:发布、升级与版本管理)、服务的发现、服务的健康检查(包括:日志、监控与报警)、服务的故障处理(包括:)等,正如前文所述,整个微服务的治理均托管至部门级的基础设施,在此就不做赘述了。
6.5 微服务和分布式能力紧耦合
上图是微服务体系的逻辑架构,由两部分组成(紫色部分是集成在微服务中的提供分布式能力的共享类库和网络客户端):
- 内层架构(图中浅蓝色部分),是每个微服务的实现架构;
- 外层架构(图中黄色部分),是构建强大微服务架构所需要的各种能力,即分布式能力,如服务注册发现、消息中间件、redis等key-value存储、数据库、日志监控追总系统等。
微服务和分布式能力的强耦合会增加运维的复杂性(如提供分布式能力的各客户端版本)和服务本身的体积(某些微服务本身可能规模很小,但是需要引入的外层分布式能力的客户端相比之下就规模惊人了)。由于微服务的解决方案采用的是部门级的基础设施,目前一部分已经做到了无侵入,比如使用凤睛实现了业务无侵入的微服务监控。
6.6 分布式事务
从单体应用改造成微服务化后,势必会引入分布式事务的问题。针对不同的业务场景,主要落地了2种解决方案。
同步方案
如图所示,品牌广告下单过程中的典型业务场景,冻款 + 锁库存,在rpc的调用过程中可能会遇到如下问题:
冻款成功,锁库存失败
冻款成功,锁库存下游成功(黄色检索端服务),上游超时(蓝色资源中心)
冻款和锁库存均成功,业务方失败
冻款+锁库存的操作主要发生在客户下单的时候,从业务方的视角来看是同步调用服务,其结果会影响到主业务服务的抉择。针对此类执行时间确定且较短的业务场景,采用了通用型TCC的思想,落地了分布式解决方案BTCC。下图是BTCC的架构图:
异步方案
如图所示,业务场景是客户在公示期交付标的的保证金(T+1 0点截止),获取竞拍资格,然后T+1的10点可以参与此标的的竞拍。对于业务方来说,需要调用两个服务的rpc接口,冻款 + 确认竞拍资格。但此类业务场景对时间上并无实时要求,对于客户来说T+1的10点才能感知到竞拍资格,因此可以采用异步化的解决方案,本地消息表。同时配上定时任务,监控未被成功处理的消息,重试发送,起到了兜底方案。
6.7 RPC调用替代进程内方法调用
微服务架构改造过程中,在遵循领域模型将现有单体应用按照业务边界拆分为多个微服务时,往往选择用 REST 或者 RPC 等远程调用方式简单替代原有的进程内方法调用。
- 在微服务之前:应用程序由多个耦合在一起的模块组成,这些模块通过内存空间进行方法调用;
- 在微服务之后:应用程序由多个耦合在一起的微服务组成,这些微服务通过网络进行远程调用。
抛开调用方式的差异来看微服务化前后的系统架构,会发现两者几乎完全一样。如下图所示:
而微服务版本在某些情况下可能表现的更糟糕:因为调用方式更脆弱(网络远比内存不可靠)。而我们将网络当成 “胶水” 来使用,试图把分散的业务逻辑模块(已经拆分为微服务)按照单体时代的同样方式简单粘在一起,这当然比单体在同一个进程内直接方法调用更加的不可靠。也就是说,简单用远程调用替代进程内方法调用,服务间的强耦合并未得到改善,反而让调用方式变得更加脆弱。
解决方案是引入「事件」(Event),解除不必要的强耦合。结合领域模型的设计理念,将Event 理解为领域中已经发生的事情:通常意味着有行为(Action)已经发生,有状态(Status)已经改变。RPC的调用方式更加像「命令」(Command),用于传递一个要求执行某个动作(Action)的请求,代表将要发生的事情。
下面举一个使用 event 机制的实际原理,同时对比command方式,优势显而易见。
下图是使用RPC调用的方式(以进入竞拍期为例),核心业务流程:创建标的 --> 标的审核 --> 标的报价 --> 标的进入公示期 --> 标的进入竞拍期 --> 标的竞拍中 --> 标的竞拍结束 --> 下单
以标的为核心业务实体,标的生命周期的变更做为事件触发源,采用消息中间件实现事件的广播和订阅,服务间的交互图如下所示:
和RPC调用方式相比,服务之间解耦了,不再有循环依赖和调用的情况,做到了「高内聚,低耦合」的设计理念。同时,基于实体生命周期的监控更加自然,任何环节有问题或者延迟,都会在第一时间发现,服务的稳定性和线上运维效率大幅提升。
07 业务组件的沉淀
介绍完微服务化改造后,再来聊聊中台化实践中,对于某个服务而言,如果做到更好的复用性。这里提出「业务组件」的概念,业务组件是指一类能够被复用的独立业务功能,从粒度上看,可以比功能模块更细。比如竞拍中心比价服务中的标的排序策略;资源中心的询量、锁量功能;订单中心的冻款、解冻功能等。下图解释了业务组件的使用,概括来说,业务组件本身不和产品线直接关联,从组件功能本身来定义边界,即做到高内聚,业务组件定义的越抽象,未来复用的可能性就越大。然后在应用层,针对不同产品线装配组件,这个步骤可以抽象为配置化。
对于业务中台来说,需要支持多类产品线的需求,各产品线之间有相似的逻辑也有自己定制化的,而且对于业务模块来说,唯一不变的就是变化。那么在这种多变且需求不可预知的场景下,如何预知哪些业务能力未来可能被复用,需要抽象为业务组件呢?个人认为在实际开发中,不强求一次就能设计出通用的业务组件,而是采用实用主义的态度,即只对那些需要在多个场景中被复用的能力进行抽象下沉,而不需要复用的,就暂时放在各产品线独有的处理逻辑中。具体如下图所示(不同的use case可以看作是不同的产品线逻辑):
按产品线隔离和抽象业务组件各有利弊。前者可以最大程度降低产品线之间的耦合,各产品线的升级迭代相互独立,降低风险。但是没有深入挖掘产品线之间的共性,和中台化的理念背道而驰。业务组件的方式更加贴近中台化理念,最大化通用能力的复用性。但同时风险也更大,一个业务组件的功能升级会影响所有产品线,所以对业务组件进行抽象和定义的合理化程度直接决定了升级代价和风险。从这个意义上来讲,定期的局部模块重构显得尤为重要。
08 总结
本文首先介绍了品牌广告投放平台技术架构的历史变迁,从1.0到3.0版本的演进过程和背后的驱动因素。接着结合实际业务,重点介绍了微服务化改造中碰到的难点和应对方式。最后,在践行中台化理念的过程中,探讨了通用能力下沉的时机和方法,同时结合业务组件的概念,使各产品线在流程独立和能力复用中找到平衡。