分享嘉宾:刘桐仁 百度

编辑整理:梁尔舒 美团

出品平台:DataFunTalk

导读: 百度搜索中台在线承接了数十亿的天级流量,内容计算架构为在线提供了数十亿的异构且有丰富特征和信号的原材料。历史上这些原材料是由数量庞大的各式脚本产生的,这些脚本复用度不够,难以维护和接手。我们在2020年以Serverless理念为指引,构建了新一代的业务框架vs-lambda,业务聚焦在函数的编写上,让业务开发效率从周级别提升到小时级,并降低了维护成本;同时借助智能调度实现典型场景下90%以上的成本节省,极大的减轻了业务的机器成本和组织的成本负担。

“Serverless"这个概念最近两年特别火,有人甚至说"2020年是Serverless技术元年”,它给我们带来的更多的是一种新的思考方式,不仅仅是服务容器的极致化的自动伸缩,更是一种云原生下全新的研发范式。希望通过这篇文章,给大家带来更多的思考。本文内容包括以下几大方面:

  • 搜索中台内容架构的特点&技术挑战
  • 从脚本战国时代到业务框架时代
  • Why Serverless & 技术挑战
  • Serverless的技术实践和业务应用
  • 收益&方法论总结

01 搜索中台内容架构的特点&技术挑战

首先什么是搜索中台呢?大家平时在百度APP搜索出来的个性化的卡片结果,都是搜索中台负责出的,比如搜索天气,会出来“天气预报”的卡片,这是从用户视角直观的对搜索中台的感受。从技术角度来理解,搜索中台是一个支持多行业垂类,多业务从0到N可持续发展的一套搜索系统。

从数据流向来看,大致分为两个部分,一部分就是业务方提供的内容原始数据通过内容生效架构生效到检索系统里面。另一部分,用户无论是从手百还是PC发起的搜索请求通过我们的检索架构,最终能获取到检索结果。

百度垂搜业务特点如下:

  • 生效周期要求高:很多类似天气、股票的业务,对生效周期的要求要在秒级。
  • 生效吞吐大:有些类似B2B、医疗、商品类型的数据能达到PB级别的数据,且需要在小时级别生效。
  • 稳定性要求高:内容数据生效失败会直接影响检索结果,比如天气数据没有更新生效,用户就会无法查询到正常的结果。
  • 业务复杂度高:目前支持了公司内十多个业务部门,二三十条业务线,业务种类多,接入方式、处理逻辑千差万别,甚至生效要求都有较大差别。

再来看搜索内容生效架构的特点:

  • 规模增长:随着垂类业务的深耕,流量从几千万到几亿,再到目前几十亿的级别,对于历史架构的稳定性,时效性提出了诸多挑战
  • 业务需求:业务初期,业务接入时个性化的需求相对较少,随着业务的发展,个性定制化的需求越来越强,原始架构无法满足
  • 架构维护:服务架构经过长时间的发展,历史包袱很重,架构难以维护
  • 资源成本:随着业务需求和规模的增长,公司的资源成本也在指数级增长

02 从脚本战国时代到业务框架时代

从架构演进的过程来看,可以分为三个阶段,脚本战国时代、业务框架时代、Serverless时代。

1. 脚本战国时代

脚本战国时代的特点是聚焦于高质量高权威的数据,业务方要求简单快速的生效,此时仅有少量个性化接入需求。上图中黄色部分就是为了解决用户个性化接入需求的数据加工通路。业务方会将他们业务系统里面的数据推送到系统数据代理模块, 接着推送到用户通用需求处理模块,对数据进行比较统一的初期的简单加工,接着再推送到统一数据加工模块,业务方会在自定义的拓扑图中实现特定算子的handler代码,业务方数据在加工完成之后,数据就会被推送到下游的建库模块,先后经过数据校验、数据解析,生成索引到各个机房。

2. 业务框架时代

为什么要进入业务框架时代?

跟脚本战国时代相比,出现了以下两方面问题:

  • 首先,业务方定制化需求越来越多,只通过简单的脚本加工已难以支持;
  • 另外,业务流量指数级增长,老系统已经扛不住。

先来看第一个问题。

业务方的脚本个数,从原来的几个脚本,增长到了近百个,导致统一加工模块维护困难,代码行数也从最初的数百行,增长到了数十万行,代码耦合程度高,隔离性差,单个业务异常会影响到其他业务。

我们的方案是引入了一个通用的业务框架,业务方在业务框架内开发业务代码,开发完成后提交到任务平台,当数据到来之后,会通过统一的数据网关分发到业务各自的app中去处理。

收益主要在以下三点:

  • 业务方在业务框架内开发自己的业务代码,复用公共函数库
  • 业务逻辑隔离,独立开发、部署、上线
  • 巨型统一加工模块服务下线,通用逻辑下沉

接着来看第二个问题。

针对系统吞吐不足的问题,我们对系统进行了重构,将系统进行架构分层,大致分成了四个部分:数据接入、数据审核、数据加工和数据建库。这四部分都是统一采用Kafka作为数据总线将数据串联。最终支持了天级数十亿建库,数万QPS,PB级别数据量的规模能力。

03 Why Serverless & 技术挑战

1. 为什么要进入Serverless时代?

回顾业务框架时代的顶层设计,可以发现数据处理层处于核心位置,同时也是内容架构唯一的业务深耕入口,是内容架构中业务更新最频繁、流量种类最复杂的业务主战场。随着各业务不断的深耕,越来越多的业务需要个性化的数据加工。但发现数据加工、接入、维护效率低以及迅速膨胀的资源成本成为制约业务发展的主要因素。具体体现在:

  • 业务接入成本高:接入时需要用户具体关注资源的申请(CPU、内存),同时还需要学习数据加工的框架是如何加工的
  • 服务运维麻烦:用户需要处理上线后自身遇到的各种问题,比如流量暴涨,服务存在异常实例,线上潮汐式业务大量浪费资源,因为很多潮汐式业务存在生效时间要求,资源配置只能按照波峰时最大值要求配置

2. Serverless带来的技术挑战

基于以上问题背景和痛点,如下四个方面的技术挑战,是要在做Serverless之前就要思考清楚的:

  • 用户接入:业务要能够低成本接入,老业务如何快速适配使用Serverless提供的能力
  • 稳定性:包括正常状态下的稳定性以及异常控制
  • 动态调度:资源容器是动态调度的,要确保调度的准确性和实时性
  • 排查效率:由于动态调度容器资源,可能导致部分日志前一分钟还有,下一分钟就没有了

04 Serverless的技术实践和业务应用

1. Serverless实践全景图

上图为Serverless实践的全景图。整体分成了三层:应用层、服务层和控制层。

  • 最上层为应用层, 是业务方能够直观感受到的部分。业务方能够自助接入,还可以在云平台直接开发代码,开发完可以进行在线调试,云调试提供了在线Diff测试的能力。云监控是一个不需要业务方配置即可以直接使用的监控。重点要提到的是,服务管理不仅提供了代码级的回滚,也提供了数据级的回滚能力,比如数据导入错误,可以在平台上操作重灌特定时间点之后的数据。
  • 在服务层,首先是FaaS(业务框架)部分,负责调用业务方自定义的脚本类型的函数,以及保证服务的吞吐。其次是BaaS部分,主要是封装了百度内部一些后端服务,比如图片识别服务、自然语言处理服务、索引计算服务。基于FaaS、BaaS,实现了复杂的函数编排。服务层是业务方会一定程度感知到的。
  • 在控制层,包含两部分功能,一部分是智能调度,包含了自动扩缩容、服务的冷启动、异常实例的迁移等能力。另一部分是智能控制,主要解决的问题是在异常状态下如何快速发现问题、解决问题。

以上,将之前业务方需要感知到的云化服务进化到Serverless,不仅效率极大提升,资源使用成本也有所降低。

2. 典型模块介绍

(1) 应用层:数据接入

数据接入可以分为两类,一类是流式导入,可以通过简单的配置,支持HTTP/RPC/消息队列的方式将数据推送过来,接着流转到一个数据队列里面,最终分发到各业务的app中。还有一类数据本身是存储在业务方自己的数据存储里面,比如Mysql、Hbase,业务方也可以在配置服务器中通过简单的配置创建一个导入任务,后续由任务触发器定时触发任务,任务注册在任务管理器中,任务管理器管理着任务的执行状态,任务执行器拿到具体的任务配置后会进行具体的原始数据导出,并会将导出的批量数据转化成一条条的流式数据,导入到统一数据队列中,最终被分发到各业务方app中。两类导入基本无需开发代码,通过配置即可完成接入。

(2) 应用层:测试维护

内容生效架构中存在着复杂的数据处理算子拓扑关系,在过去,业务方的一些修改上线依赖业务单测,繁琐、易漏侧,容易导致线上数据异常,进而影响业务。并且由于较重的测试成本,降低了上线的人效。

通过我们自动测试系统的构建,当业务方要上线一个新版本代码之前,首先会在沙盒环境里创建一个容器,同时运行线上版本和测试版本的服务,并进行结果Diff的比较。Diff流量是从线上Kafka队列中拷贝出来的真实数据,不过业务方也可以配置自己的用于Diff的数据集。整个测试的Diff,不仅仅对比数据的Diff,也会进行性能的Diff。整个过程会花费10分钟,全自动的获取测试结果,业务方根据Diff结果来自行确认是否满足上线标准。

维护辅助系统主要包括三部分:Trace 系统、报表系统、监控系统(GPE)。主要解决的问题是老框架排查依赖日志,周期长、低效。Trace系统包含数据Trace、日志Trace两部分。数据Trace实现中,由于原始数据传递是通过数据队列串联起来的,所以ES-Proxy作为一个低优先级的订阅者,在不影响业务数据生效的条件下订阅队列中的数据,并将订阅到的信息推送到ES集群,信息主要是一些关键的索引信息,内容信息由于较大,并不推送到ES集群。日志Trace实现主要是为了能够使得业务方观察自己的业务日志,在日志SDK实现中通过装饰器模式不仅会将日志落入本地磁盘,也会将日志发送到Kafka中,由于ES-Proxy也订阅了Kafka的数据,最终实现了数据Trace、日志Trace的统一。通过数据分析工具基于ES会产出一些分析报表, 比如异常分析等。业务方可以平台化的接入维护辅助系统,辅助业务低成本线上服务维护。

(3) 服务层:业务框架(FaaS)

搜索业务深耕往往需要复杂的流式处理,这就带来了两个问题需要思考清楚, 首先是数据处理的拓扑如何组织,数据流语义如何保障,比如至少保证at least once的数据流语义。对于at least once的数据流语义的保障,基于公司内部的实时数据流处理框架(StreamingComputer)进行了改造,会对流式数据进行表格存储,保证数据不丢失。其次是计算问题,对于如何提高服务吞吐,框架提供了Batch批量读写和数据压缩的能力。

从具体实现层面,业务框架与代码充分解耦,聚焦函数实现,模块实现中支持多协议,同时兼容老框架协议,并提供工具链,可以使得老服务低成本平滑迁移到新的Serverless架构。由于业务框架是使用C++实现的,框架采用PyBind直接调用业务Python函数。并且框架还通过SDK封装了BaaS业务组件,直接供业务方使用。

(4) 服务层:服务架构(BaaS)

集成公司强大优质的服务能力,简化接口调用,屏蔽后端细节,优化服务质量。

拿图片存储来说,如果业务方自己实现的话,会是一个复杂的异步调用的过程,现在直接提供了现成的BaaS算子,业务方可以直接使用,同时Baas内还提供了流控的功能,进一步优化了图片存储服务的质量。

(5) 服务层:函数编排

在Faas、Baas基础之上,实现了复杂的函数编排。业务方定义的函数拓扑和具体执行时的拓扑逻辑上是完全一样的,但实际的部署实现上是深度抽象和解耦的,深度抽象体现在如果业务方定义了FunA、FunB,FunA执行完后执行FunB,编排执行时可能会认为FunA、FunB是不需要分开执行的(可能并行一起执行)。

另一块是深度复用,深度复用有三个层次,一个是在代码内通过SDK的形式直接使用Baas服务,比如分词服务、黄反识别。第二个是算子复用,比如一些融合算子、冲突算子,业务方直接可以配置使用,不需要自己开发代码。第三个是公共流复用,比如基本上业务都会用到的正排计算、倒排计算服务,之间的数据流都是专门经过优化,业务方数据过来的时候都会传递到这个公共的数据流进行处理。公共流复用主要应用在业务使用比较广泛统一,资源使用比较大,不适合频繁调度的应用场景。

(6) 控制层:智能调度

做一套复杂的服务调度系统,首先明确自己的调度目标,也就是我们需要针对哪些场景完成调度功能,根据业务实际使用过程中遇到的痛点问题,我们主要需要支持以下几大功能:

  • 容器自动扩容:根据当前服务流量的波动情况自动分配出来对应可以满足整体实例消费情况的实例数进行消费。
  • 服务资源回收&冷启动:保证长时间没流量的服务容器,资源被回收完全被回收,不占用任何服务资源,当新流量资源到来的时候,服务接着过去资源的数据消费,保证数据生效稳定性的同时,使得业务完全做到按需使用,这个是服务0->1。
  • 异常实例迁移:主要通过热点实例迁移,长尾实例迁移保证服务全局的正常运行。
  • 容器资源自适应:主要通过检测内存使用状态,对资源容器进行自适应的调整,保证容器资源在不浪费的同时,保证服务不会超限而造成服务的OOM。

我们在实现一个调�