推荐系统是移动互联网时代非常成功的人工智能技术落地场景之一。
本文我们将从架构设计的角度回顾和讨论推荐系统的一些核心算法模块,重点从离线层、近线层和在线层三个架构层面讨论这些算法。
本文不会讲解一些具体推荐模块的架构设计,但无论什么推荐模块,其逻辑经过拆解后都可以映射到本文的架构体系中,做到触类旁通,举一反三。
本文选自《从零开始构建企业级推荐系统》一书,在庞杂的领域知识网格中点亮关键节点,为你的商业化落地开辟泛化通道。
1 架构设计概述
架构设计是一个很大的话题,本文这里只讨论和推荐系统相关的部分。更具体地说,我们主要关注的是算法以及其他相关逻辑在时间和空间上的关系——这样一种逻辑上的架构关系。
下面介绍的是一些经过实践检验的架构层面的最佳实践,以及对这些最佳实践在不同应用场景下的分析。除此之外,还希望能够通过把各种推荐算法放在架构的视角和场景下重新审视,让读者大家对算法间的关系有更深入的理解,从全局的角度看待推荐系统,而不是只看到一个个孤立的算法。
架构设计的本质之一是平衡和妥协。一个推荐系统在不同的时期、不同的数据环境、不同的应用场景下会选择不同的架构,在选择时本质上是在平衡一些重要的点。下面介绍几个常用的平衡点。
▊ 个性化 vs 复杂度
个性化是推荐系统作为一个智能信息过滤系统的安身立命之本,从最早的热榜,到后来的公式规则,再到著名的协同过滤算法,最后到今天的大量使用机器学习算法,其主线之一就是为用户提供个性化程度越来越高的体验,让每个人看到的东西都尽量差异化,并且符合个人的喜好。为了达到这一目的,系统的整体复杂度越来越高,具体表现为使用的算法越来越多、算法使用的数据量和数据维度越来越多、机器学习模型使用的特征越来越多,等等。同时,为了更好地支持这些高复杂度算法的开发、迭代和调试,又衍生出了一系列对应的配套系统,进一步增加了整个系统的复杂度。可以说整个推荐逻辑链条上的每一步都被不断地细化分析和优化,这些不同维度的优化横纵交织,构造出了一个整体复杂度非常高的系统。从机器学习理论的角度来类比,如果把推荐系统整体看作一个巨大的以区分用户为目标的机器学习模型,则可以认为复杂度的增加对应着模型中特征维度的增加,这使得模型的VC维不断升高,对应着可分的用户数不断增加,进而提高了整个空间中用户的个性化程度。这条通过不断提高系统复杂度来提升用户个性化体验的路线,也是近年来推荐系统发展的主线之一。
▊ 时效性 vs 计算量
推荐系统中的时效性概念体现在实时服务的响应速度、实时数据的处理速度以及离线作业的运行速度等几个方面。这几个速度从时效性角度影响着推荐系统的效果,整体上讲,运行速度越快,耗时越少,得到的效果越好。这是因为响应速度越快,意味着对用户行为、物品信息变化的感知越快,感知后的处理速度越快,处理后结果的反馈就越快,最终体现到用户体验上,就是系统更懂用户,更快地对用户行为做出了反应,从而产生了更好的用户体验。但这些时效性的优化,带来的是更大的计算量,计算量又对应着复杂的实现逻辑和更多的计算资源。在设计得当的前提下,这样的付出通常是值得的。
时效性优化是推荐系统中非常重要的一类优化方法和优化思路,但由此带来的计算压力和系统设计的复杂度也是必须要面对的。
▊ 时间 vs 空间
时间和空间之间的平衡关系可以说是计算机系统中最为本质的关系之一,在推荐系统中也不例外。时间和空间这一对矛盾关系在推荐系统中的典型表现,主要体现在对缓存的使用上。缓存通常用来存储一些计算代价较高以及相对静态变化较少的数据,例如用户的一些画像标签以及离线计算的相关性结果等。但是随着越来越多的实时计算的引入,缓存的使用也越来越广泛,常常在生产者和消费者之间起到缓冲的作用,使得二者可以解耦,各自异步进行。例如实时用户兴趣计算这一逻辑,如果没有将之前计算的兴趣缓存起来,那么在每次需要用户兴趣时都要实时计算一次,并要求在较短的时间内返回结果,这对计算性能提出了较高的要求。但如果中间有一层缓存作为缓冲,则需求方可以直接从缓存中取来结果使用。这在结果的实时性和新鲜度上虽然做了一定的妥协,但却能给性能提升带来极大的帮助。这样就将生产和消费隔离开来,生产者可以根据具体情况选择生产的方式和速度。当然,仍然可以努力提高生产速度,生产速度越快,缓存给时效性带来的损失就越小,消费者不做任何改动就可以享受到这一提升效果。所以说,这种利用缓存来解耦系统,带来性能上的提升以及开发的便利,也是在推荐系统架构设计中需要掌握的一种通用的思路。
上面介绍的一些基本性原则贯穿着推荐系统架构设计的方方面面,是一些具有较高通用性的思路,掌握这些思路,可以产生出很多具体的设计和方法;反过来,每一种设计技巧或方法,也都可以映射到一个或几个这样的高层次抽象原则上来。这种自顶向下的思维学习方法对于推荐系统的架构设计是非常重要的,并且可以推广到很多其他系统的设计中。
2 系统边界和外部依赖
架构设计的第一步是确定系统的边界。
所谓边界,就是区分什么是这个系统要负责的,也就是边界内的部分,以及什么是这个模型要依赖的,也就是边界外的部分。划分清楚边界,意味着确定了功能的边界以及团队的边界,能够让后期的工作都专注于核心功能的设计和实现。反之,如果系统边界没有清晰的定义,可能会在开发过程中无意识地侵入其他系统中,形成冗余甚至矛盾,或者默认某些功能别人会开发而将其忽略掉。无论哪种情况,都会影响系统的开发乃至最终的运转。
系统边界的确定,简单来说,就是在输入方面确定需要别人给我提供什么,而在输出方面确定我要给别人提供什么。
在输入方面,就是判断什么输入是需要别人提供给我的,要把握的主要原则包括:
这个数据或服务是否与我的业务强相关
在推荐业务中用到的每个东西,并不是都与推荐业务强相关,例如电商推荐系统中的商品信息,只有与推荐业务强相关的服务才应该被纳入推荐系统的边界中。
这个数据或服务除了我的业务在使用,是否还有其他业务也在使用
例如上面说到的商品信息服务,除了推荐系统在使用,其他子系统也在广泛使用,那么显然它应该是一个外部依赖。也有例外情况,例如推荐系统要用到一些其他系统都用不到的商品信息,这时候,虽然理论上应该升级商品信息服务来支持推荐系统,但由于其他地方都用不到这些信息,因此很多时候可能需要推荐系统的负责团队来实现这样一个定制化服务。
依照此原则,下图展示了推荐系统的主要外部依赖。
▊ 1、 数据依赖
推荐系统作为一个典型的数据算法系统,数据是其最重要的依赖。这里面主要包括用户行为数据和物品数据两大类,前面介绍的各种算法几乎都是以这两种数据作为输入进行计算的。这些数据除了为推荐系统所用,它们也是搜索、展示等其他重要系统的输入数据,所以作为通用的公共数据和服务,显然不应该在推荐系统的边界内部,而应该是外部依赖。需要特别指出的是,虽然有专门的团队负责行为数据的收集,但是收集到的数据是否符合推荐系统的期望却不是一件可以想当然的事情。例如,对于结果展示的定义,数据收集团队认为前端请求到了结果就是展示,但对于推荐系统来说,只有用户真正看见了才是真实的展示。其中的原因在于数据收集团队并不直接使用数据,那么他们就无法保证数据的正确性,这时就需要具体使用数据的业务方,在这里是推荐团队,来和他们一起确认数据收集的逻辑是正确的。如果数据收集的逻辑不正确,后面的算法逻辑就是在做无用功。花在确保数据正确上的精力和资源,几乎总是有收益的。
▊ 2、平台工具依赖
推荐系统是一个计算密集型的系统,需要对各种形态的数据做各种计算处理,在此过程中,需要一整套计算平台工具的支持,典型的如机器学习平台、实时计算平台、离线计算平台、其他平台工具等。在一个较为理想的环境中,这些平台工具都是由专门的团队来构建和维护的。而在一些场景下,推荐系统可能是整个组织中最早使用这些技术的系统,推荐业务也还没有重要和庞大到需要老板专门配备一个平台团队为之服务的程度,在这种情况下,其中的一些平台工具就需要推荐系统的团队自己负责来构建和维护了。为了简化逻辑,下面我们假设这些平台工具都是独立于推荐系统存在的,属于推荐系统的外部依赖。
在对外输出方面,系统边界的划定会根据公司组织的不同有所差异。例如,在一些公司中,推荐团队负责的是与推荐相关的整个系统,在输出方面的体现就是从算法逻辑到结果展示,这时候系统的边界就要延伸到最终的结果展示。而在另外一些公司中,前端展示是由一个大团队统一负责的,这时候推荐系统只需要给出要展示的物品ID和相关展示信息即可,前端团队会负责统一展示这些物品信息。这两种模式没有绝对的好坏之分,重要的是要与整个技术团队的规划和架构相统一。在本书中,为了叙述简便,我们不讨论前端展示涉及的内容,只专注于推荐结果的生产逻辑。
推荐系统的效果和性能在一定程度上取决于这些依赖系统,所以在寻求推荐系统的优化目标时,目光不能只看到推荐系统本身,很多时候这些依赖系统也是重要的效果提升来源。例如,物品信息的变更如果能被更快地通知到推荐系统,那么推荐系统的时效性就会更好,给到用户的结果也就会更好;再如,用户行为数据收集的准确性能有所提高的话,对应的相关性算法的准确性也会随之提高。在有些情况下,外部系统升级会比优化算法有更大的效果提升。当然,推荐系统的问题也可能来自这些外部的依赖系统。例如,前端渲染展示速度的延迟会导致用户点击率的显著下降,因为这会让用户失去耐心。所以,当推荐系统指标出现下降时,不光要从内部找问题,也要把思路拓展到系统外部,从全局的角度去找问题。综合来讲,外部依赖的存在启发我们要从全链条、全系统的角度来看问题,找问题,以及设计优化方法。
3 离线层、在线层和近线层架构
架构设计有很多不同的切入方式,最简单也是最常用的一种方式就是先决定某个模块或逻辑是运行在离线层、在线层还是近线层。这三层的对比如下。
任何使用非实时数据、提供非实时服务的逻辑模块,都可以被定义为离线模块。其典型代表是离线的协同过滤算法,以及一些离线的标签挖掘类算法。离线层通常用来进行大数据量的计算,由于计算是离线进行的,因此用到的数据也都是非实时数据,最终会产出一份非实时的离线数据,供下游进一步处理使用。与离线层相对的是在线层,也常被称为服务层,这一层的核心功能是对外提供服务,实时处理调用方的请求。这一层的典型代表是推荐系统的对外服务接口,接受实时调用并返回结果。在线层提供的服务是实时的,但用到的数据却不一定局限于实时数据,也可以使用离线计算好的各种数据,例如相关性数据或标签数据等,但前提是这些数据已经以对实时友好的形态被存储起来。
近线层则处于离线层和在线层的中间位置,是一个比较奇妙的层。这一层的典型特点就是:使用实时数据(也会使用非实时数据),但不提供实时服务,而是提供一种近实时的服务。所谓近实时指的是越快越好,但并不强求像在线层一样在几十毫秒内给出结果,因为通常在近线层计算的结果会写入缓存系统,供在线层读取,做了一层隔离,因此对时效性无强要求。其典型代表是我们前面讲过的实时协同过滤算法,该算法通过用户的实时行为计算最新的相关性结果,但这些计算结果并不是实时提供给用户的,而是要等到用户发起请求时才会把最新的结果提供给他使用。
下面详细介绍每一层的特点、案例和具体分析。
4 离线层架构
离线层是推荐系统中承担最大计算量的一个部分,很大一部分的相关性计算、标签挖掘以及用户画像挖掘工作都是在这一层进行的。这一层的任务具有的普遍特点是使用大量数据以及较为复杂的算法进行计算和挖掘。所谓大量数据,通常指的是可以使用较长时间段的用户行为数据和全量的物品数据;而在算法方面,可以使用较为复杂的模型或算法,对性能的压力相对较小。对应地,离线层的任务也有缺点,就是在时间上存在滞后性。由于离线任务通常是按天级别运行的,用户行为或物品信息的变更也要等一天甚至更久才能够被反映到计算结果中。在离线层虽然进行的是离线作业,但其生产出来的数据通常是被实时使用的,因此离线数据在生产出来之后还需要同步到方便在线层读取的地方,例如数据库、在线缓存等。
在具体实践中,经常放在离线层执行的任务主要包括:协同过滤等行为类相关性算法计算、用户标签挖掘、物品标签挖掘、用户长期兴趣挖掘、机器学习模型排序等。仔细分析这些任务,会发现它们都符合上面提到的特点。这些任务的具体流程各不相同,但大体上都遵循一个共同的逻辑流程。
在这个逻辑架构图中,离线算法的数据来源主要有两大类:一类是HDFS/Hive这样的分布式文件系统,通常用来存储收集到的用户行为日志以及其他服务器日志;另一类是RDBMS这样的关系数据库,通常用来存储商品等物品信息。离线算法会从输入数据源获取原始数据并进行预处理,例如,协同过滤算法会先把数据处理成两个倒排表,LDA算法会先对物品文本做分词处理,等等,我们将预处理后的数据统一称为训练数据(虽然有些离线算法并不是机器学习算法)。预处理这一步值得单独拿出来讲,这是因为很多算法用到的预处理是高度类似的,例如,文本标签类算法需要先对原始文本进行分词或词性标注,行为类相关性算法需要先将行为数据按用户聚合,点击率模型需要先将数据按照点击/展示进行聚合整理,等等。所以在设计离线挖掘的整体架构时,有必要有针对性地将数据预处理流程单独提炼出来,以方便后面的流程使用,做到更好的可扩展性和可复用性。下一步是各种推荐算法或机器学习模型基于各自的训练数据进行挖掘计算,得到挖掘结果。离线计算用到的工具通常包括Hadoop、Spark等,结果可能是一份协同过滤相关性数据,可能是物品的文本主题特征,也可能是结果排序模型。接下来,为了让挖掘结果能够被后面的流程所使用,需要将挖掘结果同步到不同的存储系统中。一般来说,如果挖掘结果要被用作下游离线流程的输入,是一份中间结果,那么通常它会被再次同步到Hive或HDFS这样的分布式文件系统中;如果挖掘结果要被最终的推荐服务在线实时使用,那么它就需要被同步到Redis或RDBMS这样对实时访问更为友好的存储系统中。至此,一个完整的离线挖掘流程就完成了。
上面讲到离线任务通常以天为单位来执行,但是在很多情况下,提高作业的运行频率以及对应的数据同步频率,例如从一天一次提升到一天多次,都会对推荐系统的效果有提升作用,因为这些都可以被理解为在做时效性方面的优化。一种极限的思想是,当我们把作业的运行频率提高到极致时,例如每分钟甚至每几秒钟运行一次作业,离线任务就变成了近线任务。当然,在这种情况下就需要对离线算法做相应的修改以适应近线计算的要求,例如前面介绍过的实时协同过滤算法就是对原始协同过滤算法的修改,以及将机器学习的模型训练过程从离线改为在线。
所以,虽然我们会把某些任务放到离线层来执行,但并不代表这些任务就只能是离线任务。我们要深入理解为什么将这些任务放在离线层来执行,在什么情况下可以提高其运行频率,甚至变为近线任务,以及这样做的好处和代价是什么。只有做到这一点,才能够做到融会贯通,不被当前的表象迷住眼睛。一种典型的情况是,当实时计算或流计算平台资源不足,或者开发人力资源不足时,我们倾向于把更多的任务放到离线层来执行,因为离线计算对时效性要求较低,出错之后影响也较小。综合来说,就是容错度较高,适合在整体资源受限的情况下优先选择。而随着平台的不断完善,以及人力资源的不断补充,就可以把一些对时效敏感的任务放到近线层来执行,以获得更好的收益。
5 近线层架构
有了上面的铺垫,近线层的存在理由和价值就比较明确了,从生产力发展的角度来看,可以认为它是实时计算平台工具发展到一定程度对离线计算的自然改造;而从推荐系统需求的角度来看,它是各种推荐算法追求实时化效果提升的一种自然选择。
近线层和离线层最大的差异在于,它可以获取到实时数据,并有能力对实时数据进行实时或近实时的计算。也正是由于这个特点,近线层适合用来执行对时效比较敏感的计算任务,例如实时的数据统计等,以及实时执行能够获得较大效果提升的任务,例如一些实时的相关性算法计算或标签提取算法计算。近线层在计算时可使用实时数据,也可使用离线生成的数据,在提供服务时,由于无须直接响应用户请求,因此也不用提供实时服务,而是通常会将数据写入对实时服务友好的在线缓存中,方便实时服务读取,同时也会同步到离线端做备份使用。
通常放在近线层执行的任务包括实时指标统计、用户的实时兴趣计算、实时相关性算法计算、物品的实时标签挖掘、推荐结果的去重、机器学习模型统计类特征的实时更新、机器学习模型的在线更新等,这些任务通常会以如下两种方式进行计算。
个体实时:所谓个体实时,指的是每个实时数据点到来时都会触发一次计算,做到真正意义上的实时。典型的工具代表是Storm和Flink。
批量实时:很多时候并不需要到来一个实时数据点就计算一次,因为这会带来大量的计算和I/O,而是可以将一定的时间窗口或一定数量的数据收集起来,以小批次为单位进行计算,这可以有效减少I/O量。这种妥协对于很多应用来说,只要时间窗口不太大,就不会带来效果的显著下降。典型的工具代表是Spark Streaming。
下面展示了典型的近线层计算架构图。
从数据源接入的角度来看,近线层主要使用实时数据进行计算,这就引出了近线层和离线层的一个主要区别:近线层的计算通常是事件触发的,而离线层的计算通常是时间触发的。事件触发意味着对计算拥有更多的主动权和选择权,但时间触发则无法主动做出选择。事件触发意味着每个事件发生之后都会得到通知,但是否要计算以及计算什么是可以自己选择的。例如,可以选择只捕捉满足某种条件的事件,或者等事件累积到一定程度时再计算,等等。所以,当某个任务的触发条件是某个事件发生之后进行计算,那么这个任务就很适合放在近线层来执行。例如推荐结果的去重,需要在用户浏览过该物品之后将其加入一个去重集合中,这就是一个典型的事件触发的计算任务。此外,近线层的计算是可以使用离线数据的,但前提是需要提前将这些数据同步到对实时计算友好的存储系统中。
在近线层中执行的典型任务包括但不限于:
特征的实时更新。例如,根据用户的实时点击行为实时更新各维度的点击率特征。
用户实时兴趣的计算。根据用户实时的喜欢和不喜欢行为计算其当下实时兴趣的变化。
物品实时标签的计算。例如,在第6章用户画像系统中介绍过的实时提取标签的流程。
算法模型的在线更新。通过实时消息队列接收和拼接实时样本,采用FTRL等在线更新算法来更新模型,并将更新后的模型推送到线上。
推荐结果的去重。用户两次请求之间是有时间间隔的,所以无须在处理实时请求时进行去重,而是可以将这个信息通过消息队列发送给一个专门的服务,在近线层中处理。
实时相关性算法计算。典型的如实时协同过滤算法,按照其原理,也可以把随机游走等行为类算法改写为实时计算,放到近线层中执行。
总结起来,凡是可以和实时请求解耦,但需要实时或近实时计算结果的任务,都可以放到近线层中执行。
近线层的实时计算虽然没有响应时间的要求,但却存在数据堆积的压力。具体来说,近线层计算用到的数据大部分是通过Kafka这样的消息队列实时发送过来的,在接收到每一个消息或消息窗口之后,如果对消息或消息窗口的计算速度不够快,就会导致后面的消息堆积。这就像大家都在排队办理业务,如果一个业务办理得太慢,那么排的队就会越来越长,长到一定程度就会出问题。所以,近线层的计算逻辑不宜过于复杂,而且近线层读取的外部数据,例如离线同步好的Redis中的数据,也不宜过多,还有I/O次数不宜过多。这就要求近线层的计算逻辑和用到的数据结构都要经过精心的设计,共同保证近线层的计算效率,以免造成数据堆积。
除了纯数据统计类型的任务,以及结果去重这样的无数据产出的任务,近线层的大多数任务在离线层都有对应的部分,二者有着明显的优势和劣势,因此应该结合起来使用。典型的如实时协同过滤算法,由于引入了实时性,使得它在一些新物品和新用户上的效果比原始的协同过滤算法的效果好;但由于它只使用实时数据,所以在稀疏性和不稳定性方面的问题也是比较大的,要使用离线版本的协同过滤算法作为补充,才能形成更全面的覆盖。再比如在近线层执行的用户实时兴趣预测,能够捕捉到用户最新鲜的兴趣,准确率会比较高;但由于短期兴趣易受展示等各种因素影响发生较大的波动,如果完全根据短期兴趣来进行推荐的话,则很有可能会陷入局部的信息茧房,产生高度同质的结果,影响用户的整体体验。而如果将离线计算的长期兴趣和短期兴趣相结合,就可以有效避免这个问题,既能利用实时数据取得高相关性,又能利用长期数据取得稳定性和多样性。从这些例子可以看出,离线层和近线层之间并没有不可逾越的鸿沟,二者更多的是在效率、效果、稳定性、稀疏性等多个因素之间进行权衡得到的不同选择,一个优秀的工程师应该做到“码中有层,心中无层”,才算是对算法和架构做到了融会贯通。
上面讲到离线层的任务在一定条件下可以放到近线层来执行,那么类似地,近线层的任务是否可以放到在线层来执行呢?这个问题其实涉及离线层、近线层这两层作为整体和在线层的关系。如果把推荐系统比作一支打仗的军队,那么在线层就是在前方冲锋陷阵的士兵,直接面对敌人的攻击,而离线层和近线层就是提供支持的支援部门,离线层就像是生产粮食和军火的大后方,近线层就像是搭桥修路的前方支援部门,二者的本质都是让前线士兵能够最高效、最猛烈地打击敌人,但其业务本质导致它们无法到前线去杀敌。离线层和近线层是推荐系统的生产者,在线层是推荐系统的消费者(也会承担一定的生产责任),它们有着截然不同的分工和定位,是无法互换的。
6 在线层架构
在线层与离线层、近线层最大的差异在于,它是直接面对用户的,所有的用户请求都会发送到在线层,而在线层需要快速给出结果。如果抽离掉其他所有细节,这就是在线层最本质的东西。在线层最本质的东西并不是在线计算部分,因为在极端情况下,在接收到用户请求之后,在线层可以直接从缓存或数据库中取出结果,返回给用户,而不做任何额外计算。而事实上,早年还没有引入机器学习等复杂的算法技术时,绝大多数计算都是在离线层进行的,在线层就起到一个数据传递的作用,很多推荐系统基本都是这么做的,甚至时至今日,这种做法仍然是一种极端情况下的降级方案。
推荐系统发展到现在,尤其是各种机器学习算法的引入,使得我们可以使用的信息越来越多,可用的算法也越来越复杂,给用户的推荐结果通常是融合了多种召回策略,并且又加了重排序之后的结果,而融合和重排序现在通常是在在线层做的。那么问题来了:这些复杂计算一定要放到在线层做吗?为了回答这个问题,不妨假设:如果将所有计算都放在离线层做,在线层只负责按照用户ID查询返回结果,是否可行?如果将所有计算都放在离线层做,由于不知道明天会有哪些用户来访问系统,所以就需要为每个用户都计算出推荐结果,这要求我们计算出全平台所有用户的推荐结果,而对于那些明天没有来访问系统的用户,今天的计算就浪费掉了。但这仍然不够,因为明天还会有新来的用户,这些用户的信息在当前计算时是拿不到的,所以,即使今天离线计算出了所有当前用户的推荐结果,明天也还会有大量覆盖不到的用户。这就是将上面提到的复杂计算一定要放在在线层做的第一个主要原因:只有按需实时计算才能覆盖到所有用户,并且不会产生计算的浪费。从另一个角度来看,如果今天就把用户的推荐结果完全计算出来,若用户明天的实时行为表达出来的兴趣和今天的不相符,或者机器学习模型中一些关键特征的取值发生了变化,那么推荐结果就会不准确,并且无法及时调整。例如,用户昨天看的是手机,今天打算买衣服,但我们昨天计算出的推荐结果是以手机为主的,那么用户今天的需求是无法满足的。这就是需要在在线层做复杂计算的第二个主要原因:只有在线实时计算,才能够充分利用用户的实时信息,包括实时兴趣、实时特征以及其他近线层计算的结果等。除此以外,还有其他原因,比如实时处理可以快速应对实时发生的业务请求等。以上这些原因共同决定了在线层存在的意义。
从目前的趋势来看,在线层承担的工作越来越多,因为大家希望利用的信息越来越多地来自实时计算结果。如果说离线层和近线层是厨房里的小工,负责一切食材和配料的前期准备工作,那么在线层就是最后掌勺的大厨,它需要将大家准备好的材料进行组合装配,最终形成一盘菜。
在线层的典型形态是一个RESTful API,对外提供服务。调用方传入的参数在不同公司的设计中差异较大,但基本都会包含访问用户的ID标识和推荐场景这两个核心信息,其他信息推荐系统都可以通过这两个信息从其他地方获取到。在线层接收到请求后会启动一套流程,将离线层和近线层生成的数据进行串联,在毫秒级响应时间内返回给调用方。这套流程的典型步骤包括:
AB实验分流
根据用户ID或请求ID,决定当前用户要执行的策略版本。
获取用户画像
根据传入的用户ID信息和场景信息,从Redis等缓存中获取用户的画像信息,用在后面的流程中。
相关性候选集召回
包括行为相关性、内容相关性、上下文相关性、冷启动物品等多维度候选集的召回。
候选集融合排序
将上面流程得到的候选集进行融合,再进一步进行机器学习模型排序,最后得到在算法上效果最优的结果列表。在当今推荐系统大量使用机器学习算法的背景下,这一部分的逻辑通常会比较复杂。而为了将机器学习模型预测这一越来越通用的逻辑和推荐主逻辑相剥离,通常也会为机器学习专门搭建一套在线系统,用来提供预测功能,包括对推荐结果的点击、转化预测。这样做的好处是机器学习模型的升级改造不会干扰到推荐系统本身,有利于模块化维护。
业务逻辑干预
在完成算法逻辑之前或之后,还需要加入一些业务逻辑,例如去除或减少某些类别的物品,或者出于业务考虑插入一些在算法上非最优的结果,等等。
拼接展示信息
在一些推荐系统中,推荐服务要负责将展示所需的所有信息集成到一起,这样调用方拿到结果后就可以直接展示了,而不需要再去获取其他内容。这看起来是一个负担,但从某些角度来看也是好事,因为我们可以做一些展示层面的个性化,典型的如根据不同的用户展示不同的图片或标题,要知道展示层对于用户是否对物品感兴趣是起着非常重要的作用的,毕竟这是一个处处看脸的时代。Netflix就做过剧集封面个性化的尝试,相比给所有人展示同样的封面,个性化封面使得在用户点击方面获得了显著的提升。
在这套流程中,本书前面介绍过的相关性算法的结果、用户画像的结果、用户兴趣模型的结果等都会被串联起来。
这套流程对应的在线层服务架构图如下。
在上图中不仅呈现了在线服务层的流程架构,而且还把它所依赖的数据和服务也一并呈现出来,这样可以最直接地体现在线层“主厨”的串联作用。最上面一层在线服务层的流程体现了上面介绍的在线层的典型计算流程。下面所依赖的数据平台,包含了推荐服务用到的所有数据,如相关性数据、用户画像数据、用户兴趣数据,以及与机器学习相关的模型和特征数据等。这些数据又是通过下面的计算平台这一层生成的,包括离线层的计算平台和近线层的计算平台。这些计算平台所使用的数据构成了整个推荐系统的数据源,主要包括:物品数据源、行为数据源和外部数据源。
这个架构图从数据和计算的角度对推荐系统做了分割,跟之前讲的离线层和近线层的分割方法是两种不同的视角,相互正交。经常从不同的视角去抽象、剥离一个系统,有助于我们更全面、更深刻地认识系统。在复杂系统面前,我们的认识过程就像盲人摸象,需要不断地从新的视角去看待理解它,才能得到更全面的认识。
7 架构层级对比
在介绍完离线层、近线层和在线层的架构之后,我们通过下表对它们进行更全面的对比。
上表基本上列出了推荐系统的所有主要模块在架构中的位置,建议读者从架构的视角对其算法进行回顾,以加深对它们的理解。
希望工程师在设计和实现算法时,脑子里除了有算法和数据,还应多一个架构的维度,能够从架构工程的角度来考虑算法,做到心中有系统,而不只是一些零散推荐算法的实现,这样才能构建好一个推荐系统。
(完)
《从零开始构建企业级推荐系统》是一本面向实践的企业级推荐系统开发指南,可以帮助开发者逐步构建一个完整的推荐系统,并提供了持续优化的系统性思路。
《从零开始构建企业级推荐系统》
张相於 著
举一纲而万目张,解一卷而众篇明
本书旨在与庞杂的领域知识网格中点亮关键节点,为读者的商业化落地开辟泛化通道。
注重零启动、全流程、成系统、通用化
用整体、全局思维将商业级推荐系统涉及的算法、模块、架构与相关性链条全线贯通。
国内首批实践商业化推荐的老江湖
从当当、转转到阿里,十年知名电商企业主导推荐系统研发和带领相关算法团队经验。
产品逻辑、数据、算法、架构四大体系
用真实案例覆盖电商、新闻、短视频等主流场景和技术生态,面向工程师与产品经理。