关于推荐系统:解密游戏推荐系统的建设之路

14次阅读

共计 6360 个字符,预计需要花费 16 分钟才能阅读完成。

作者:vivo 互联网服务器团队 - Ke Jiachen、Wei Ling

本文从零开始介绍了游戏举荐我的项目的倒退历程,论述了大型项目建设中遇到的业务与架构问题以及开发工程师们的解决方案,描述了游戏举荐我的项目的特点以及业务倒退方向,有着较好的参考与借鉴意义。

一、游戏举荐的背景与意义

从信息获取的角度来看,搜寻和举荐是用户获取信息的两种次要伎俩,也是无效帮忙产品变现的两种形式,搜寻是一个十分被动的行为,并且用户的需要十分明确,在搜索引擎提供的后果里,用户也能通过浏览和点击来明确的判断是否满足了用户需要。

然而,举荐零碎承受信息是被动的,需要也都是含糊而不明确的。

举荐零碎的 作用 就是建设更加有效率的连贯,更有效率地连贯用户与内容和服务,节约大量的工夫和老本。以此背景,游戏举荐零碎由此诞生。

游戏举荐零碎从设计之初就作为游戏散发的平台,向公司内所有次要流量入口(游戏核心、利用商店、浏览器、jovi 等)散发游戏,零碎通过各种举荐算法及举荐策略,为用户举荐下载付费志愿较高且兼顾商业价值的游戏,从而为公司带来支出。倒退至明天,该零碎还具备类游戏内容与素材的举荐性能。

二、游戏举荐的初期模型

游戏举荐的目标是推出用户想要且兼顾商业价值的游戏,以此来进步业务的支出指标。此处的商业价值是由经营侧通过策略规定去把控的,而用户动向游戏则是通过算法排序失去的,算法排序所须要的特色数据,以及举荐成果的反馈数据则由埋点信息上报以供计算剖析。

因而咱们的模型能够分成四大块:

  1. 经营举荐规定配置
  2. 算法模型训练
  3. 举荐策略失效
  4.  数据埋点上报

模块间的 交互如下:在策略失效前,经营会先在配置核心生成对应的配置规定,这些规定会以缓存的模式存储以供举荐高并接口调用。当用户拜访 app 利用某些特定页面时,其后盾会带着对应的场景信息来申请游戏举荐后盾,举荐后盾依据场景信息映射相干配置(召回,标签,过期,算法等 ……….)调用算法服务并进行资源排序,最终将举荐的后果反馈给 app 利用。

app 利用在展现举荐页面的同时,也将用户相应的行为数据以及举荐数据的相干埋点进行上报。

三、业务增长与架构演进

随着接入零碎带来的正向收益的晋升,越来越多的业务抉择接入游戏举荐零碎,这使得咱们反对的性能日益丰盛。

目前游戏举荐笼罩的场景有分类、专题、榜单、首页、搜寻等;蕴含的策略类型有干涉、打散、资源配比、保量;反对的举荐类型更是丰盛:联运游戏、小游戏、内容素材、举荐理由。

这些丰盛的应用场景使得业务的复杂度老本增长,令咱们在性能,扩展性,可用性上面临着新的挑战,也推动着咱们架构改革。

3.1 熵增环境下的通用组合策略

在 0 到 1 的过程中,游戏举荐聚焦于进步散发量,这时候思考得更多的是怎么把游戏推出去,在代码实现上应用分层架构来划分执行的业务。

然而在 1 到 2 的过程中,咱们游戏举荐不仅仅举荐游戏,也举荐内容和素材;同时在策略调用上也更加灵便,不同场景其调用的策略是不同的,执行程序也是不同的;更重要的是退出了很多用户个性化业务与动静规定,这些都使得现有业务代码急剧收缩,扩大起来顾此失彼,无从下手。因而咱们急需一个高复用,易扩大,低代码的策略框架去解决这些问题。

如图所示,通用组合策略负责流转的角色有两个 acceptor 和 executor,通信媒介是举荐上下文 context。负责执行逻辑的角色有三个 matcher,listener 和 process,它们都有多个不同逻辑的实现类。当申请游戏举荐零碎时,acceptor 会先从配置中动静查问策略模板进行匹配,接着 listener 组件会执行相应的预处理逻辑。解决后 acceptor 通过上下文 context 将工作流转给 executor 处理器。executor 再依据配置,将 process 依据前置条件进行筛选并排列组合,最初埋点返回。

通过这套通用的策略,咱们在实现个别业务的时候,只有扩大具体 matcher 和 process,并在配置核心将场景和解决优先级绑定起来,就能实现大部分的场景开发,这样研发者能够更聚焦于某个逻辑流程的开发,而不必疲于梳理代码,并进行扩大设计。

3.2 多级缓存与近实时策略

游戏举荐零碎服务于手机游戏用户,处于整个零碎链路的上游,峰值流量在 3W TPS 左右,是个读远多于写的零碎。“读”流量来自于用户在各种举荐场景,列表、搜寻、下载钱下载后、榜单等,写数据次要来源于经营相干策略的变更,所以咱们面临的一个重大挑战就是如何在保障可用性的前提下应答高频的读申请。

为了保证系统的读性能,咱们采纳了 redis + 本地缓存的设计。配置更新后先写 mysql,写胜利后再写 redis。本地缓存定时生效,应用懒加载的形式从 redis 中读取相干数据。这种设计能保障最终一致性,软状态时服务集群数据存在短暂不统一的状况,晚期对业务影响不大,能够认为是一个逐渐放量的过程。

晚期原先部署节点较少,整个零碎达到最终一致性的工夫较短,但随着节点减少到数百台,这个工夫就变得不是那么谐和了。

同时随着业务复杂度的减少,经常是多个配置策略决定这一个举荐后果,此时本地缓存的状态极大影响了测试和点检的便当,如果配置更改不能做到立马更新本地缓存,那就要期待漫长的一段时间能力开始验证逻辑。因而,咱们对缓存构造做出了如下的调整:

与先前不同的是,咱们退出音讯队列并通过配置版本号的比对来实现策略的实时更新同步,获得了很好的成果。

3.3 高并服务的垃圾回收解决

任何一个 java 服务都逃离不了 FGC 的魔咒,高并服务更是如此。很多服务每天两位数的 FGC 更是粗茶淡饭,显然这对业务的稳定性和服务性能影响是微小的。游戏举荐这边通过一直实际总结了一套较为通用的办法很好地解决了这个问题:

能够看到起初 jvm 配置较为惯例:1G 的年老代,2G 的老年代以及一些其余常见的多线程回收的配置,其后果就是每天 10 次的 FGC,YGC 单次耗时在 100ms,FGC 耗时在 350 – 400ms。咱们晓得线上接口容忍的范畴个别是 200ms 以内,不超过 300ms,这样显然是不达标的。

通过剖析,咱们发现高并服务的高频 FGC 来源于这几个方面:

  • 大量的本地缓存(堆内)占据了老年代的空间,大大增加了老年代叠满的频率。
  • 高并申请导致了对象的急速生成,年老代空间不足以包容这剧增的对象,导致其未达到存活阈值(15 次)就降职至老年代。
  • 引入的监控组件为了性能,经常提早 1 – 2 min 再将数据上报服务端,导致这部分数据也无奈在年老代被回收。

当然这还不是问题的全副,FGC 还有个致命问题就是 stop the world,这会导致业务长时间无奈响应,造成经济损失。反过来,就算 FGC 频繁,stop the world 只有 1ms,也是不会对业务造成影响的,因而不能单单以 FGC 的频率来判断 jvm 服务的 gc 性能的好坏。通过下面的探讨,咱们在实践中失去了如下的解决方案:

  • 不常变动的缓存(小时级别)移到堆外,以此缩小老年代叠满的根底阈值。
  • 变动不那么频繁的缓存(分钟级别)更新的时候进行值比照,如果值一样则不更新,以此缩小老年代的沉积。
  • 应用 G1 回收器:-XX:+UseG1GC

    -XX:MaxGCPauseMillis=200

    -XX:InitiatingHeapOccupancyPercent=25

    -XX:MaxNewSize=3072M -Xms4608M -Xmx4608M -XX:MetaspaceSize=512M

    -XX:MaxMetaspaceSize=512M 

其成果如上所示,调整后各项指标都有很大的提高:因为年老代中的复制算法使其垃圾清理速度较快,所以调大其容量使对象尽量在其中回收,同时设置每次清理的工夫,使得 mix gc 管制在 200ms 以内。

3.4 限流降级与兜底策略

为了保障业务的可用性,大部分业务都会引入 hystrix, sentinel, resilience4j 这类熔断限流组件,但这些组件也不能解决全副的问题。

对于游戏举荐来说,一台节点往往承载着不同的业务举荐,有些业务非常外围,有些不是那么重要,限流降级的时候不是简略的哪个服务限流多少问题,而是在权衡利弊的状况下,将无限的资源向哪些业务歪斜的问题,对此咱们在分层限流高低足了功夫。

同时对于个性化业务来说,仅仅返回通用的兜底会使举荐同质化,因而咱们的策略是将用户的历史数据存储下来,并在下次兜底的时候作为举荐列表进行返回。

四、精细化经营模式的摸索

在经验过了 0 到 1 的开疆拓土 与 1 到 2 的高速增长后,游戏的举荐架构曾经趋于稳定。这时候咱们更加关注效力的进步与老本的降落,因而咱们开始着手于 零碎经营的精细化设计,这对举荐零碎的良性倒退是意义重大的。

精细化经营不仅能进步尾量游戏的支出,进步经营人员的工作效率,还能实时疾速反馈算法在线成果并立马做出调整,做到一个业务上的闭环。首先就不得不提到游戏举荐零碎的分层正交试验平台,这是咱们做精细化经营的根底。

4.1 多层 hash 正交试验平台

游戏举荐的要害就一个 ” 准 ” 字,这就须要通过精细化策略迭代来晋升效率和准确度,从而不断扩大规模劣势,实现正向循环。然而策略的扭转并不是通过“头脑风暴”空想的,而是一种建设在数据反馈上的机制,以带来预期内的正向变动。这就须要咱们分隔对照组来做 A /Btest。

个别线上业务常见的 A /B test 是通过物理形式对流量进行隔离,这种办法常见于 H5 页面的分流试验,但面对简单业务时却存在着部署较慢,埋点解析艰难等问题,其典型的架构形式如下:

对于游戏举荐来说,其实现一次举荐申请的流程比较复杂,波及到多组策略,为了保障线上流量的效率与互斥,就不能采纳简略的物理调配流量的形式。

因而在业务层咱们建设了一套多层 hash 正交试验规定来满足咱们 A /B test 的要求。

其流量走势如下图所示,

与物理隔离流量,部署多套环境的形式不同,分层模型在分流算法中引入层级编号因子(A)来解决流量饥饿和流量正交问题。每一试验层能够划分为多个实验田,当流量通过每一层试验时,会先通过 Function(Hash(A)) 来计算其调配的实验田,这样就能保障层与层之间的流量随机且互相独立。

以上就是举荐业务和个别业务试验流量隔离的不同之处,在实验设计上咱们又将一个残缺的试验周期分为以下几个阶段。在准备阶段须要跟依据业务指标的需要,提出试验假如,划分好基线和实验田的流量比例,并上线配置(放量)。

在试验阶段,线上流量进入后,服务会依据流量号段的匹配响应的策略进行执行,并将试验数据上报。放量一段时候后,咱们会依据上报的埋点数据进行数据分析,以确定此次策略的好坏。

和试验阶段划分绝对应地,咱们将试验平台划分为试验配置,埋点上报和试验后果剖析三个模块,在试验配置模块,咱们依据试验需要来实现分流配置和业务场景的映射关系。

并在 hash 试验治理中将业务层级划分,以便流量的流通。

在埋点上报模块中,咱们通过 sdk 的形式植入业务代码中,当流量进入该实验田时就会进行剖析和埋点上报,咱们将上报的埋点分为游戏和申请维度,节俭上报流量的同时以满足不同的剖析需要:

游戏维度:{
    "code": 0,
    "data": [
        {
            "score": 0.016114970572330977,
            "data": {
                "gameId": 53154,
                "appId": 1364982,
                "recommendReason": null,
            },
            "gameps": "埋点信息",
        }
    ],
    "reqId": "20200810174423TBSIowaU52fjwjjz"
}
申请维度:{
    "reqId":"20200810142134No5UkCibMdAvopoh",
    "scene": "appstore.idx",                       
    "imei": "869868031396914",
    "experimentInfo": [
        {
            "experimentId": "RECOMMENDATION_SCENE",
            "salt":"RECOMMENDATION_SCENE",         
            "imei": "3995823625",                  
            "sinfo": "策略信息"                               
        },
        {
            "experimentId": "AUTO_RECOMMENDATION_REASON",
            "salt":"RECOMMENDATION_SCENE",
            "imei": "1140225751",
            "sid": "3,4,5"
        }
    ]
}

在试验后果剖析模块中,咱们将采集的埋点的数据上报只大数据侧,并由其进行剖析计算,其后果领导这咱们对试验策略进行进一步的剖析迭代。对于游戏申请的上报格局,咱们能够间接通过 appId 和 gameps 的信息间接剖析得出该类游戏的举荐后果和用户行为的关系。同时退出申请维度的剖析(蕴含策略信息),能够间接剖析出决策对各项指标的影响。

4.2 召回优化之多路召回

召回在游戏举荐业务中就是利用肯定的规定去圈选一批游戏,这是为了将海量的候选集疾速放大为几百到几千的规模。而召回之后的排序则是对放大后的候选集进行精准排序,最终达到精准举荐的目标。

然而这种单路的召回在业务上却有着很大的 缺点

  1. 通常为了保障计算效率,圈选的数量在几百个左右,因为数量限度其无奈齐全笼罩残缺的指标用户候选集。
  2.   随着业务的复杂度变高,召回策略的品种也开始收缩,其召回规定是剥离的无奈对立,这也意味着在某些业务场景下,在品种上无奈笼罩齐全。

因而,衡量了计算效率和业务覆盖度(召回率)的问题,咱们逐渐上线了多路召回性能。

在业务实现上,多路召回兼容了原有的个性化召回、算法召回、游戏池召回、分类 / 标签 / 专题 / 同开发者)召回等召回门路,通过圈选多个游戏池做为召回策略,通过合并、过滤、补量、截断等策略最终筛选出一批进行算法预估打分的游戏。

实质上,多路召回利用各简略策略保障候选集的疾速召回,从不同角度设计的策略保障召回率靠近现实状态。

4.3 曝光干涉之动静调参

一个举荐零碎的效力如何,除了经营策略之外很大水平上取决于举荐算法的后果,而举荐算法的后果又是以曝光量,下载量,ctr 等作为评估指标的。所以在游戏举荐业务的生命周期中,举荐算法始终致力于优化这些指标。

然而在开发中有个理论问题就是,从算法后果的数据反馈,到代码改良上线这个工夫周期较长,对一些须要疾速响应的业务场景来说是不符合要求的。因而咱们须要一套规定来对线上的算法后果做动静调整,以满足业务的要求,这就是 动静调参

目前游戏业务的营收中,曝光量是个极其重要的指标,而大盘在一段时间内的曝光量是确定的,太多或太少都会重大影响业务,由此举荐算法就会依据线上实时反馈的一些数据对游戏的曝光进行调整。

通过设计, 咱们先将调参游戏划分为多个等级,并将游戏的生命周期划分为几个时间段,同时在每个时间段内以游戏曝光量,评级,数量等因素作为计算因子来计算曝光的调配权重。

接着零碎依据实时采集的游戏曝光信息及所计算的游戏指标曝光对理论曝光进行调整,最终实现游戏曝光的动静调控。

对于正向调控来说,动静调参就是最无效的搀扶机制,减少了游戏曝光的同时晋升了导流能力。对于负向调控,动静调参能对品质和要求不达标的游戏,通过缩小曝光的形式进行打压,晋升用户体验。

五、瞻望之智能化建设

通过多年的摸索实际,游戏举荐零碎成就了一套残缺的举荐体系。

在架构上的演进使得咱们能更好地应答复杂多变的业务需要,在精细化经营上的摸索与建设令咱们能更加敏锐地把握住市场的变动以做出响应,这些建设也很好地反馈的反馈到了业务后果中,晋升了泛滥效力和收益指标,失去了业务方的统一好评。

但当散发效率和支出效益问题解决了之后,咱们在思考本人还能做什么,原先游戏举荐做的比拟多的是接入服务,在单链路上去做闭环提高效益,但这是远远不够的。

在将来咱们会思考如何打造笼罩搜广推 + 智能经营的全栈业务撑持零碎(智能礼券,智能 push,用户反馈智能解决零碎),以晋升平台和渠道的价值。

正文完
 0