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

文章知识点与官方知识档案匹配,可进一步学习相关知识算法技能树首页概览34053 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!