美团DSP广告策略实践

近年来,在线广告在整个广告行业的比重越来越高。在线广告中实时竞价的广告由于其良好的转化效果,占有的比重逐年升高。DSP(Demand-Side Platform)作为需求方平台,通过广告交易平台(AdExchange)对每次曝光进行竞价尝试。对于AdExchange的每次竞价请求,DSP根据Cookie Mapping或者设备信息,尝试把正在浏览媒体网站、App的用户映射到DSP能够识别的用户,然后根据DSP从用户历史行为中挖掘的用户画像,进行流量筛选、点击率/转化率预估等,致力于ROI的最大化。美团点评的用户量越来越大,积累了大量的用户在站内的行为信息,我们基于这些行为构造了精准的用户画像,并在此基础上针对美团App和网站的用户搭建了美团DSP平台,致力于获取站外优质的流量,为公司带来效益。本文从策略角度描述一下在搭建DSP过程中的考虑、权衡及对未来的思考。在DSP实时竞价过程中,策略端都在哪些步骤起作用;对每一个步骤的尝试和优化方向做出详细介绍;总结DSP如何通过AB测试、用户行为反馈收集、模型迭代、指导出价/排序等步骤来打通整个DSP实时竞价广告闭环。美团DSP在一次完整的竞价展示过程中可能涉及到两个大的步骤:对AdExchange的竞价请求实时竞价;竞价成功之后用户点击进入二跳页、浏览、点击、最后转化。我们分别看一下这两个步骤中策略的支持。上图给出了每一次竞价广告的粗略示意图,竞价Gateway在收到竞价请求之后,会识别出美团点评用户的流量,根据网站历史CTR、网站品类属性等因素进行简单的流量过滤,把流量分发到后端的AdServer。AdServer作为后端广告的总控模块,首先向RecServer(定向召回服务)获取站外展示广告召回结果,然后根据获取的广告结果向PredictorServer(CTR/点击价值预测服务)请求每个广告的站外点击率和点击价值。最后AdServer根据获取的点击价值v和ctr,根据v∗ctrt进行排序,从而挑选出top的广告进行展示。上图给出了竞价成功后,用户从站外展示的广告点击后,所经历的流程示意图。用户点击站外广告后,到达二跳页Gateway,二跳页Gateway向AdServer请求广告列表。AdServer从RecServer获取站内二跳页广告召回结果,然后根据获取的广告结果向PredictorServer请求每个广告的二跳页点击率并进行排序。排序后的结果返回给二跳页Gateway进行广告填充。在上述两个步骤中,美团DSP策略端的支持由RecServer和PredictorServer提供,在图1和图2分别用红色的箭头和AdServer交互。其中RecServer主要负责站外广告和二跳页的广告召回策略,而PredictorServer主要负责站外流量的CTR预估,点击价值预估和二跳页内的CTR预估。整个策略的闭环如下图:接下来详细介绍下美团DSP的召回、CTR预估、点击价值预估相关的策略。定向召回服务定向召回服务分别在实时竞价过程中提供了站外广告的召回服务,在竞价完成之后提供了二跳页的广告召回服务。站外召回和站内二跳页召回没有本质的区别,比较常见的做法是二跳页会根据用户点击商品的品类进行品类过滤。下面我们具体看一下目前定向召回相关的具体策略。一、基于实时行为召回通过实时日志流平台准确的跟踪用户的实时点击浏览/收藏/购买行为,对于相应的用户重新投放用户近一段时间内发生过浏览/收藏/购买行为的商品。需要注意的是这个策略需要考虑召回概率按时间进行衰减,用户的实时行为能够比较强反映用户的近期兴趣,距离当前时间比较长的用户行为对于用户近期兴趣的定向偏弱。二、基于位置召回O2O的业务特点与传统的电商有明显的区别,传统电商是在线上达成交易意向,然后通过快递送货的方式完成交易。O2O业务绝大部分消费者是在线上买入电子券,然后要到店进行消费,所以用户的位置信息在广告召回中起着举足轻重的作用。我们在基于位置的广告召回中尝试了以下三种策略:实时地理位置召回根据用户所在的实时地理位置召回距离比较近的广告。 对于移动端的广告流量,可以比较准确的获得用户的实时地理位置,从而进行比较精准的投放; 对于PC端的流量,地理位置是通过用户访问的IP地址进行推算的,所以地理位置是有偏移的,但是考虑到PC端浏览广告的流量用户位置一般都比较固定,比如用户一般是在上班或者在家休息,我们仍然使用了这个策略。实时商圈热单召回根据用户所在的实时地理位置推断出用户目前所在商圈,给用户投放当前商圈的热门消费单。商圈的范围一般在几公里范围之内,对于用户到店消费是一个合理的距离范围,所以我们离线挖掘出每一个商圈的热门消费单,作为用户召回的候选。偏好商圈热单召回通过离线分析用户历史的浏览/点击/购买行为,分析出用户的历史商圈偏好,召回用户偏好的商圈消费热单作为广告候选集。这个策略需要用户的userid,仅对于能够识别并能映射到userid的用户适用。可以看到策略1和策略2是不需要userid的,所以这两个策略也是我们在识别不到userid的时候一个比较好的冷启动召回策略。三、基于协同过滤召回基于协同过滤的召回策略我们融合了user-based和item-based两种。四、基于item-based的协同过滤我们首先通过用户的购买行为计算item之间的相似度,比如通过计算发现item A和item B之间的相似度比较高,我们把item A作为候选推荐给购买item B的用户,作为item B的用户的召回候选集之一;同样也把item B作为候选推荐给购买item A的用户,作为购买item A的用户的召回候选集之一。因为item-based协同过滤的特征,这一部分召回基本能够把热门爆款单都拉到候选集中。五、基于user-based的协同过滤我们同样需要先计算用户之间的相似度。计算用户相似度时,除了考虑用户购买的商品,还可以把用户所消费过的商家及商家所在的商圈作为相似度权重考虑进来。这么做是因为,很多商品是在全国多个城市都可以购买的,如果只采用用户购买的商品来计算相似度,可能把两个不同城市用户的相似度计算的比较高,加入商家和商圈的权重,可以大大降低这种情况的可能性。六、基于矩阵分解的场景化召回对于O2O消费的某些场景,比如美食和外卖,用户是否发生购买与用户目前所处的场景有很大关系,这里的场景包含时间、地点、季节、天气等。举个例子来说,工作日的中午,如果还在下雨,这个时候外卖的购买概率一般是比其他商品高的。基于此,我们开发了基于矩阵分解的场景化召回策略。我们采用了FM模型来进行建模,建模的特征包括季节、时间(工作日/周末,一天之内的时段)、地点、天气等。这个策略的目的是希望召回用户实时的基于场景化的需求。上文提到在实时竞价阶段,AdServer会跟PredictorServer请求每个广告的站外点击率和点击价值,最后AdServer根据获取的点击价值v和ctr,根据$v∗ctr^t$进行站外广告排序,挑选top的广告。最终的报价公式如下:$$a∗∑i=1kvi∗ctrti+b(1)$$k是本次竞价要展示的广告数,t,a,b都是根据实际流量情况进行调整。其中t为挤压因子,为了控制ctr在排序和报价中起作用的比重,t越大,ctr在排序和报价中的比重越高;a,b需要根据DSP需要获取的流量和需要达到的ROI之间的权衡进行调整,a,b越大,出价越高,获取的流量越多,成本越高,ROI就减少。公式1中CTR直接作为一个引子进行出价计算,所以这里的CTR必须是一个真实的点击率。因为在站外广告点击日志中,正样本是非常稀疏的,为了保证模型的准确度,我们一般都会采用负样本抽样。这样模型估计出来的CTR相对大小是没有问题的,可以作为排序依据,但是用来计算出价的时候,必须把负样本采样过程还原回去,我们在下面的小节中详细解释。站外CTR预估该模型目标是,对于RecServer召回的广告,预测出广告的相对点击率和真实点击率,相对点击率用于排序, 真实点击率用于流量报价。对于每个流量,AdExchange会下发给多个DSP,报价最高的DSP会胜出,获取在这个流量上展示广告的机会。为了能够引入更多的优质流量,减少流量成本,提高ROI、CTR预估模型需要充分考虑站点、广告、用户等维度的信息。广告的点击与转化主要与用户、广告、媒体(user,ad,publisher)这三个因素相关。我们的特征也主要从这三个方向去构建,并衍生出一些特征。特征选择用户特征用户浏览,购买的品类,用户画像,浏览器,操作系统等特征。广告特征广告deal的属性特征,如商家、品类、价格、创意类型等特征。广告deal的统计特征,如历史CTR、CVR、PV、UV、订单量、评分等。媒体特征网站类别,网站域名,广告位,尺寸等特征。匹配特征(主要是用户与广告维度的匹配)用户浏览、购买的品类与广告品类的match, 商家的match。用户浏览广告的不同时间粒度的频次特征,比如用户浏览当前广告的次数、用户上次点击广告距离当前的时间差。组合特征在LR+人工特征的实现过程中,需要人工构造一些组合特征,比如,网站+广告、用户消费水平+价格、广告主+广告品类等,对于FM和FFM能都自动进行特征的组合。环境特征广告的效果往往与用户所处的外部环境相关。比如 时段、工作日/节假日、移动端的经纬度等。特征处理最后再看我们具体如何构建模型。模型选择由于站外的站点数量巨大、广告位较多、广告的品类较多,造成训练样本的特征数较大,需要选择合适的模型来处理,这里我们选用了LR+人工特征的方式,确保训练的性能。特征降维点击率模型需要考虑用户维度的数据,由于美团的用户量巨大,如果直接用用户id作为特征会造成特征数急剧增大,而且one-hot encoding后的样本会非常稀疏,从而影响模型的性能和效果。所以我们这里采用了用户的行为和画像数据来表征一个用户,从而降低用户维度的大小。负样本选择对于站外广告,有很多广告位比较靠近页面的下方,没有被用户看到,这样的广告作为负样本是不合理的。我们在负样本选择的时候需要考虑广告的位置信息,由于我们作为DSP无法获取广告是否真实被用户看到的信息。这里通过适当减少点击率较低的展位负样本数量,来减轻不合理的负样本的情况。对于二跳页广告,只取点击的位置之前的负样本,而未点击的则只取top20的广告作为负样本。负样本采样由于广告点击的正负样本分布极其不均,站外广告的点击率普遍较低,绝大多数样本是负样本,为了保证模型对正样本的召回,需要对负样本按照一定比例抽样。真实CTR校准由于负样本抽样后,会造成点击率偏高的假象,需要将预测值还原成真实的值。调整的公式如下:$$q=p(p+1−pw)(2)$$q: 调整后的实际点击率。 p: 负样本抽样下预估的点击率。 w: 负样本抽样的比例。二跳页CTR预估当用户点击了广告后,会跳转到广告中间页,因为站外流量转化非常不容易,所以对于吸引进来的流量,我们希望通过比较精细化的排序给用户投放尽可能感兴趣的广告。由于进入二跳页的流量大概比站外流量少两个数量级,我们可以使用比较复杂的模型,同时因为使用比较多的用户/广告特征,所以这里我们选择了效果比较好的FFM模型。特征和样本处理方面的流程基本类似CTR预估模块中的样本处理流程。差别在于广告在展示列表中的位置,对广告的点击概率和下单概率是有非常大影响的,排名越靠前的广告,越容易被点击和下单,这就是position bias的含义。在抽取特征和训练模型的时候,就需要很好去除这种position bias。我们在两个地方做这种处理: 在计算广告的历史CTR和历史CVR的时候,首先要计算出每个位置的历史平均点击率ctr_p,和历史平均下单率cvr_p,然后再计算i广告的每次点击和下单的时候,都根据这个item被展示的位置,计算为$ctr_0/ctr_p及cvr_0/ctr_p$。 * 在产生训练样本的时候,把展示位置作为特征放在样本里面,并且在使用模型的时候,把展示位置特征统一置为0。上文提到广告是根据v∗ctrt进行排序,并通过公式1进行报价。这里面的v就是点击价值(点击价值是指用户发生一次点击之后会带来的转化价值)。广告业务的根本在于提高展示广告的eCPM,eCPM的公式可以写为v∗ctr∗1000,准确的预估点击价值是为了准确预估当前流量对于每一个广告的eCPM。刘鹏在《计算广告》中提到,只要准确的估计出点击价值,通过点击价值计算和CTR计算得到的eCPM进行报价,就始终会有利润,这是因为AdExchange是按照广义第二出价进行收费的。在实际投放过程中,出价公式可以随着业务目标的不同进行适当的调整,比如我们的出价公式中包含了挤压因子t,和a ,b两个参数。出价越高带回来的流量越大,可能带来质量参差不齐的流量,一般在一段时间之内会引起CTR的降低,这样会带来CPC点击成本的提高,所以ROI会降低。反之出价比较低的情况下,带来的流量越少,经过比较细致的流量过滤,CTR能长期保持在一个较高的水平,点击成本CPC比较低,ROI就会比较高。美团DSP在点击价值预估上经历了两个阶段:第一阶段是站外广告的落地页是广告的详情页面时,广告的点击价值预估比较简单,只需要预估出站外流量到达广告详情页之后的CVR即可。正负样本的选择也比较简单,采集转化样本为正样本,采集浏览未转化样本作为负样本。我们也进行了适当的负样本采样和真实CVR校准,这里采用的方法跟上一节类似,不再赘述。 模型方面,在控制特征复杂度的基础上,我们选择了效果不错的二次模型FFM,复杂度和性能都能够满足线上的性能。 特征方面,我们使用了站外实时特征+部分离线挖掘特征,由于FFM预测复杂度是(knn),k是隐向量长度,n是特征的个数,特征的选择上需要挑选贡献度比较大的特征。第二阶段,经过统计,详情页的用户流失率非常高,为了降低流失率,我们开发了广告二跳页,在二跳页里面,用户在站外点击的广告排在第一位,剩下的是根据我们的召回策略和排序策略决定的。根据公式1,点击价值是由二跳页的k个广告共同决定的。但是在站外广告排序和报价的过程中,我们无法获取中间页的召回结果,所以在实际情况中是无法适用的。目前我们的策略是直接对当前用户和当前商品的特征建立一个回归模型,使用用户在二跳页上成交的金额作为label进行训练,模型分别尝试了GBDT和FM,最终采用了效果稍好些的GBDT模型。离线评估业内常用的量化指标是AUC,就是ROC曲线下的面积。AUC数值越大,模型的分别能力越强。Facebook提出了NE(Normalized Entropy)来衡量模型,NE越小,模型越好。$$NE=−1N∑ni=1(1+yi2log(pi)+1−yi2log(1−pi))−(p∗log(p)+(1−p)∗log(1−p))(3)$$N:训练的样本的数量。 yi:第i个样本的lable,点击为+1, 未点击为-1。 pi:第i个样本预估的点击率。 P:所有样本的实际点击率。离线我们主要使用的是AUC和NE的评估方法。在线AB测试通过在线ABtest,确保每次上线的效果都是正向的,多次迭代后,站外CTR提升30%,广告二跳页CTR提升13%,二跳页CVR提升10%。在线监控在线AUC监控在线预估的CTR和CVR,建立小时级流程,计算每个小时的在线AUC。发现AUC异常的情况,会报警,确保模型在线应用是正常的。在线预估均值监控在线预估的值会计算出平均值,确保均值在合理的范围之内。均值过高会导致报价偏高,获取流量的成本增加。均值过低,造成报价偏低,获取的流量就偏少,对于估值异常的情况能及时响应。本文介绍了美团DSP在站外投放过程中的策略实践。很多细节都是在业务摸索过程中摸索出来的。后续有些工作还可以更细致深入下去:流量筛选流量筛选目前还是比较粗暴的根据网站历史的CTR等直接进行过滤,后续会基于用户的站内外的行为,对流量进行精细化的筛选,提升有效流量,提高转换。动态调整报价在DSP的报价环节,点击率预估模型会对每一个流量预估出一个CTR,为了适应adx市场的需要,会加上指数和系数项进行调整。但是通过这种报价方式获取的流量,由于外部竞争环境的变化,流量天然在不同时段的差异,经常会出现CPC不稳定。该报价的系数对于所有的媒体都是一致的,而一般的优质媒体都是有底价的,且不同媒体的底价不一致,造成该报价方式无法适用所有的媒体,出现部分优质媒体无法获取足够的流量。我们的目标是在CPC一定的情况下,在优质媒体、优质时段尽可能多的获取流量,这里我们需要根据实时的反馈和期望稳定的CPC来动态调整线上的报价。从而在竞价环境、时段、媒体变化时,CPC保持稳定,进一步保证我们的收益最大化(同样的营销费用,获取的流量最多)。位置召回基于位置的召回策略中,我们对用户的商圈属性没有作区分,比较粗粒度的统一召回,这样其实容易把用户当前时间/位置真正有兴趣的商品拍的比较靠后;比较好的办法是通过精准的用户画像和用户消费时间/位置上下文挖掘,根据用户竞价时的位置和时间,分析出用户转化率高的商圈,从而进行更加精准的投放。在业务上,美团DSP会逐步接入市场上主流的AdExchange和自有媒体的流量。技术上,会持续探索机器学习、深度学习在DSP业务上的应用,从而提升美团DSP的效果。欢迎订阅「K叔区块链」 - 专注于区块链技术学习 博客地址:http://www.jouypub.com简书主页:https://www.jianshu.com/u/756c9c8ae984segmentfault主页:https://segmentfault.com/blog/jouypub腾讯云主页:https://cloud.tencent.com/developer/column/72548

April 16, 2019 · 1 min · jiezi

LruCacahe在美团DSP系统中的应用演进

背景DSP系统是互联网广告需求方平台,用于承接媒体流量,投放广告。业务特点是并发度高,平均响应低(百毫秒)。为了能够有效提高DSP系统的性能,美团平台引入了一种带有清退机制的缓存结构LruCache(Least Recently Used Cache),在目前的DSP系统中,使用LruCache + 键值存储数据库的机制将远端数据变为本地缓存数据,不仅能够降低平均获取信息的耗时,而且通过一定的清退机制,也可以维持服务内存占用在安全区间。本文将会结合实际应用场景,阐述引入LruCache的原因,并会在高QPS下的挑战与解决方案等方面做详细深入的介绍,希望能对DSP感兴趣的同学有所启发。LruCache简介LruCache采用的缓存算法为LRU(Least Recently Used),即最近最少使用算法。这一算法的核心思想是当缓存数据达到预设上限后,会优先淘汰近期最少使用的缓存对象。LruCache内部维护一个双向链表和一个映射表。链表按照使用顺序存储缓存数据,越早使用的数据越靠近链表尾部,越晚使用的数据越靠近链表头部;映射表通过Key-Value结构,提供高效的查找操作,通过键值可以判断某一数据是否缓存,如果缓存直接获取缓存数据所属的链表节点,进一步获取缓存数据。LruCache结构图如下所示,上半部分是双向链表,下半部分是映射表(不一定有序)。双向链表中value_1所处位置为链表头部,value_N所处位置为链表尾部。LruCache读操作,通过键值在映射表中查找缓存数据是否存在。如果数据存在,则将缓存数据所处节点从链表中当前位置取出,移动到链表头部;如果不存在,则返回查找失败,等待新数据写入。下图为通过LruCache查找key_2后LruCache结构的变化。LruCache没有达到预设上限情况下的写操作,直接将缓存数据加入到链表头部,同时将缓存数据键值与缓存数据所处的双链表节点作为键值对插入到映射表中。下图是LruCache预设上限大于N时,将数据M写入后的数据结构。LruCache达到预设上限情况下的写操作,首先将链表尾部的缓存数据在映射表中的键值对删除,并删除链表尾部数据,再将新的数据正常写入到缓存中。下图是LruCache预设上限为N时,将数据M写入后的数据结构。线程安全的LruCache在读写操作中,全部使用锁做临界区保护,确保缓存使用是线程安全的。LruCache在美团DSP系统的应用场景在美团DSP系统中广泛应用键值存储数据库,例如使用Redis存储广告信息,服务可以通过广告ID获取广告信息。每次请求都从远端的键值存储数据库中获取广告信息,请求耗时非常长。随着业务发展,QPS呈现巨大的增长趋势,在这种高并发的应用场景下,将广告信息从远端键值存储数据库中迁移到本地以减少查询耗时是常见解决方案。另外服务本身的内存占用要稳定在一个安全的区间内。面对持续增长的广告信息,引入LruCache + 键值存储数据库的机制来达到提高系统性能,维持内存占用安全、稳定的目标。LruCache + Redis机制的应用演进在实际应用中,LruCache + Redis机制实践分别经历了引入LruCache、LruCache增加时效清退机制、HashLruCache满足高QPS应用场景以及零拷贝机制四个阶段。各阶段的测试机器是16核16G机器。演进一:引入LruCache提高美团DSP系统性能在较低QPS环境下,直接请求Redis获取广告信息,可以满足场景需求。但是随着单机QPS的增加,直接请求Redis获取广告信息,耗时也会增加,无法满足业务场景的需求。引入LruCache,将远端存放于Redis的信息本地化存储。LruCache可以预设缓存上限,这个上限可以根据服务所在机器内存与服务本身内存占用来确定,确保增加LruCache后,服务本身内存占用在安全范围内;同时可以根据查询操作统计缓存数据在实际使用中的命中率。下图是增加LruCache结构前后,且增加LruCache后命中率高于95%的情况下,针对持续增长的QPS得出的数据获取平均耗时(ms)对比图:根据平均耗时图显示可以得出结论:QPS高于250后,直接请求Redis获取数据的平均耗时达到10ms以上,完全无法满足使用的需求。增加LruCache结构后,耗时下降一个量级。从平均耗时角度看,QPS不高于500的情况下,耗时低于2ms。下图是增加LruCache结构前后,且增加LruCache后命中率高于95%的情况下,针对持续增长的QPS得出的数据获取Top999耗时(ms)对比图:根据Top999耗时图可以得出以下结论:增加LruCache结构后,Top999耗时比平均耗时增长一个数量级。即使是较低的QPS下,使用LruCache结构的Top999耗时也是比较高的。引入LruCache结构,在实际使用中,在一定的QPS范围内,确实可以有效减少数据获取的耗时。但是QPS超出一定范围后,平均耗时和Top999耗时都很高。所以LruCache在更高的QPS下性能还需要进一步优化。演进二:LruCache增加时效清退机制在业务场景中,Redis中的广告数据有可能做修改。服务本身作为数据的使用方,无法感知到数据源的变化。当缓存的命中率较高或者部分数据在较长时间内多次命中,可能出现数据失效的情况。即数据源发生了变化,但服务无法及时更新数据。针对这一业务场景,增加了时效清退机制。时效清退机制的组成部分有三点:设置缓存数据过期时间,缓存数据单元增加时间戳以及查询中的时效性判断。缓存数据单元将数据进入LruCache的时间戳与数据一起缓存下来。缓存过期时间表示缓存单元缓存的时间上限。查询中的时效性判断表示查询时的时间戳与缓存时间戳的差值超过缓存过期时间,则强制将此数据清空,重新请求Redis获取数据做缓存。在查询中做时效性判断可以最低程度的减少时效判断对服务的中断。当LruCache预设上限较低时,定期做全量数据清理对于服务本身影响较小。但如果LruCache的预设上限非常高,则一次全量数据清理耗时可能达到秒级甚至分钟级,将严重阻断服务本身的运行。所以将时效性判断加入到查询中,只对单一的缓存单元做时效性判断,在服务性能和数据有效性之间做了折中,满足业务需求。演进三:高QPS下HashLruCache的应用LruCache引入美团DSP系统后,在一段时间内较好地支持了业务的发展。随着业务的迭代,单机QPS持续上升。在更高QPS下,LruCache的查询耗时有了明显的提高,逐渐无法适应低平响的业务场景。在这种情况下,引入了HashLruCache机制以解决这个问题。LruCache在高QPS下的耗时增加原因分析:线程安全的LruCache中有锁的存在。每次读写操作之前都有加锁操作,完成读写操作之后还有解锁操作。在低QPS下,锁竞争的耗时基本可以忽略;但是在高QPS下,大量的时间消耗在了等待锁的操作上,导致耗时增长。HashLruCache适应高QPS场景:针对大量的同步等待操作导致耗时增加的情况,解决方案就是尽量减小临界区。引入Hash机制,对全量数据做分片处理,在原有LruCache的基础上形成HashLruCache,以降低查询耗时。HashLruCache引入某种哈希算法,将缓存数据分散到N个LruCache上。最简单的哈希算法即使用取模算法,将广告信息按照其ID取模,分散到N个LruCache上。查询时也按照相同的哈希算法,先获取数据可能存在的分片,然后再去对应的分片上查询数据。这样可以增加LruCache的读写操作的并行度,减小同步等待的耗时。下图是使用16分片的HashLruCache结构前后,且命中率高于95%的情况下,针对持续增长的QPS得出的数据获取平均耗时(ms)对比图:根据平均耗时图可以得出以下结论:使用HashLruCache后,平均耗时减少将近一半,效果比较明显。对比不使用HashLruCache的平均耗时可以发现,使用HashLruCache的平均耗时对QPS的增长不敏感,没有明显增长。下图是使用16分片的HashLruCache结构前后,且命中率高于95%的情况下,针对持续增长的QPS得出的数据获取Top999耗时(ms)对比图:根据Top999耗时图可以得出以下结论:使用HashLruCache后,Top999耗时减少为未使用时的三分之一左右,效果非常明显。使用HashLruCache的Top999耗时随QPS增长明显比不使用的情况慢,相对来说对QPS的增长敏感度更低。引入HashLruCache结构后,在实际使用中,平均耗时和Top999耗时都有非常明显的下降,效果非常显著。HashLruCache分片数量确定:根据以上分析,进一步提高HashLruCache性能的一个方法是确定最合理的分片数量,增加足够的并行度,减少同步等待消耗。所以分片数量可以与CPU数量一致。由于超线程技术的使用,可以将分片数量进一步提高,增加并行性。下图是使用HashLruCache机制后,命中率高于95%,不同分片数量在不同QPS下得出的数据获取平均耗时(ms)对比图:平均耗时图显示,在较高的QPS下,平均耗时并没有随着分片数量的增加而有明显的减少,基本维持稳定的状态。下图是使用HashLruCache机制后,命中率高于95%,不同分片数量在不同QPS下得出的数据获取Top999耗时(ms)对比图:Top999耗时图显示,QPS为750时,分片数量从8增长到16再增长到24时,Top999耗时有一定的下降,并不显著;QPS为1000时,分片数量从8增长到16有明显下降,但是从16增长到24时,基本维持了稳定状态。明显与实际使用的机器CPU数量有较强的相关性。HashLruCache机制在实际使用中,可以根据机器性能并结合实际场景的QPS来调节分片数量,以达到最好的性能。演进四:零拷贝机制线程安全的LruCache内部维护一套数据。对外提供数据时,将对应的数据完整拷贝一份提供给调用方使用。如果存放结构简单的数据,拷贝操作的代价非常小,这一机制不会成为性能瓶颈。但是美团DSP系统的应用场景中,LruCache中存放的数据结构非常复杂,单次的拷贝操作代价很大,导致这一机制变成了性能瓶颈。理想的情况是LruCache对外仅仅提供数据地址,即数据指针。使用方在业务需要使用的地方通过数据指针获取数据。这样可以将复杂的数据拷贝操作变为简单的地址拷贝,大量减少拷贝操作的性能消耗,即数据的零拷贝机制。直接的零拷贝机制存在安全隐患,即由于LruCache中的时效清退机制,可能会出现某一数据已经过期被删除,但是使用方仍然通过持有失效的数据指针来获取该数据。进一步分析可以确定,以上问题的核心是存放于LruCache的数据生命周期对于使用方不透明。解决这一问题的方案是为LruCache中存放的数据添加原子变量的引用计数。使用原子变量不仅确保了引用计数的线程安全,使得各个线程读取的引用计数一致,同时保证了并发状态最小的同步性能开销。不论是LruCache中还是使用方,每次获取数据指针时,即将引用计数加1;同理,不再持有数据指针时,引用计数减1。当引用计数为0时,说明数据没有被任何使用方使用,且数据已经过期从LruCache中被删除。这时删除数据的操作是安全的。下图是使零拷贝机制后,命中率高于95%,不同QPS下得出的数据获取平均耗时(ms)对比图:平均耗时图显示,使用零拷贝机制后,平均耗时下降幅度超过60%,效果非常显著。下图是使零拷贝机制后,命中率高于95%,不同QPS下得出的数据获取Top999耗时(ms)对比图:根据Top999耗时图可以得出以下结论:使用零拷贝后,Top999耗时降幅将近50%,效果非常明显。在高QPS下,使用零拷贝机制的Top999耗时随QPS增长明显比不使用的情况慢,相对来说对QPS的增长敏感度更低。引入零拷贝机制后,通过拷贝指针替换拷贝数据,大量降低了获取复杂业务数据的耗时,同时将临界区减小到最小。线程安全的原子变量自增与自减操作,目前在多个基础库中都有实现,例如C++11就提供了内置的整型原子变量,实现线程安全的自增与自减操作。在HashLruCache中引入零拷贝机制,可以进一步有效降低平均耗时和Top999耗时,且在高QPS下对于稳定Top999耗时有非常好的效果。总结下图是一系列优化措施前后,命中率高于95%,不同QPS下得出的数据获取平均耗时(ms)对比图:平均耗时图显示,优化后的平均耗时仅为优化前的20%以内,性能提升非常明显。优化后平均耗时对于QPS的增长敏感度更低,更好的支持了高QPS的业务场景。下图是一系列优化措施前后,命中率高于95%,不同QPS下得出的数据获取Top999耗时(ms)对比图:Top999耗时图显示,优化后的Top999耗时仅为优化前的20%以内,对于长尾请求的耗时有非常明显的降低。LruCache是一个非常常见的数据结构。在美团DSP的高QPS业务场景下,发挥了重要的作用。为了符合业务需要,在原本的清退机制外,补充了时效性强制清退机制。随着业务的发展,针对更高QPS的业务场景,使用HashLruCache机制,降低缓存的查询耗时。针对不同的具体场景,在不同的QPS下,不断尝试更合理的分片数量,不断提高HashLruCache的查询性能。通过引用计数的方案,在HashLruCache中引入零拷贝机制,进一步大幅降低平均耗时和Top999耗时,更好的服务于业务场景的发展。作者简介王粲,2018年11月加入美团,任职美团高级工程师,负责美团DSP系统后端基础架构的研发工作。崔涛,2015年6月加入美团,任职资深广告技术专家,期间一手指导并从0到1搭建美团DSP投放平台,具备丰富的大规模计算引擎的开发和性能优化经验。霜霜,2015年6月加入美团,任职美团高级工程师,美团DSP系统后端基础架构与机器学习架构负责人,全面负责DSP业务广告召回和排序服务的架构设计与优化。招聘美团在线营销DSP团队诚招工程、算法、数据等各方向精英,发送简历至cuitao@meituan.com,共同支持百亿级流量的高可靠系统研发与优化。

December 24, 2018 · 1 min · jiezi