用户行为分析是数据分析中非常偅要的一项内容在统计活跃用户,分析留存和转化率改进产品体验、推动用户增长等领域有重要作用。美团点评每天收集的用户行为ㄖ志达到数百亿条如何在海量数据集上实现对用户行为的快速灵活分析,成为一个巨大的挑战为此,我们提出并实现了一套面向海量數据的用户行为分析解决方案将单次分析的耗时从小时级降低到秒级,极大的改善了分析体验提升了分析人员的工作效率。
本文以有序漏斗的需求为例详细介绍了问题分析和思路设计,以及工程实现和优化的全过程本文根据2017年12月ArchSummit北京站演讲整理而成,略有删改
下图描述了转化率分析中一个常见场景,对访问路径“首页-搜索-菜品-下单-支付”做分析统计按照顺序访问每层节点的用户数,得箌访问过程的转化率
统计上有一些维度约束,比如日期时间窗口(整个访问过程在规定时间内完成,否则统计无效)城市或操作系統等,因此这也是一个典型的OLAP分析需求此外,每个访问节点可能还有埋点属性比如搜索页上的关键词属性,支付页的价格属性等从結果上看,用户数是逐层收敛的在可视化上构成了一个漏斗的形状,因此这一类需求又称之为“有序漏斗”
这类分析通常是基于用户荇为的日志表上进行的,其中每行数据记录了某个用户的一次事件的相关信息包括发生时间、用户ID、事件类型以及相关属性和维度信息等。现在业界流行的通常有两种解决思路
对于第一种解法,最大的问题是需要做大量join操作而且关联条件除了ID的等值连接之外,还有时間戳的非等值连接当数据规模不大时,这种用法没有什么问题但随着数据规模越来越大,在几百亿的数据集上做join操作的代价非常高甚至已经不可行。
第二种解法有了改进通过聚合的方式避免了join操作,改为对聚合后的数据通过UDAF做数据匹配这种解法的问题是没有足够嘚筛选手段,这意味着几亿用户对应的几亿条数据都需要遍历筛选在性能上也难以接受。
那么这个问题的难点在哪里为什么上述两个解法在实际应用中变得越来越不可行?主要问题有这么几点
1. 事件匹配有序列关系。如果没有序列关系就非常容易通过集合的交集并集運算即可。
2. 时间窗口约束这意味着事件匹配的时候还有最大长度的约束,所以匹配算法的复杂度会进一步提升
3. 属性和维度的需求。埋點SDK提供给各个业务线每个页面具体埋什么内容,完全由业务决定而且取值是完全开放的,因此目前属性基数已经达到了百万量级同時还有几十个维度用于筛选,有些维度的基数也很高
4. 数据规模。目前每天收集到的用户行为日志有几百亿条对资源和效率都是很大的挑战。
基于上述难点和实际需求的分析可以总结出几个实际困难,称之为“坏消息”
1. 漏斗定义完全随机。不同分析需求对应的漏斗定義完全不同包括具体包含哪些事件,这些事件的顺序等这意味着完全的预计算是不可能的。
2. 附加OLAP需求除了路径匹配之外,还需要满足属性和维度上一些OLAP的上卷下钻的需求
3. 规模和性能的矛盾。一方面有几百亿条数据的超大规模另一方面又追求秒级响应的交互式分析效率,这是一个非常激烈的矛盾冲突
另一方面,还是能够从问题的分析中得到一些“好消息”, 这些也是在设计和优化中可以利用的点
1. 計算需求非常单一。这个需求最终需要的就是去重计数的结果这意味着不需要一个大而全的数据引擎,在设计上有很大的优化空间
2. 并發需求不高。漏斗分析这类需求一般由运营或者产品同学手动提交查询结果用于辅助决策,因此并发度不会很高这样可以在一次查询時充分调动整个集群的资源。
3. 数据不可变所谓日志即事实,用户行为的日志一旦收集进来除非bug等原因一般不会再更新,基于此可以考慮一些索引类的手段来加速查询
4. 实际业务特点。最后是对实际业务观察得出的结论整个漏斗收敛非常快,比如首页是几千万甚至上亿嘚结果到了最下层节点可能只有几千,因此可以考虑一些快速过滤的方法来降低查询计算和数据IO的压力
如果用一句话总结这个问题的核心本质,那就是“多维分析和序列匹配基础上的去重计数”具体来说,最终结果就是每层节点符合条件的UUID有多少个也就是去重后的計数值。这里UUID要符合两个条件一是符合维度的筛选,二是事件序列能匹配漏斗的定义去重计数是相对好解的问题,那么问题的重点就昰如果快速有效的做维度筛选和序列匹配
下图是部分行为日志的数据,前面已经提到直接在这样的数据上做维度筛选和序列匹配都是很困难的,因此考虑如何对数据做预处理以提高执行效率。
很自然的想法是基于UUID做聚合根据时间排序,这也是前面提到的UDAF的思路如下图所示。这里的问题是没有过滤的手段每个UUID都需要遍历,成本很高
再进一步,为了更快更方便的做过滤考虑把维度和属性抽出来构成Key,把对应的UUID和时间戳组织起来构成value如果有搜索引擎经验的话,很容易看出来这非常像倒排的思路
这个数据结构还是存在問题。比如说要拿到某个Key对应的UUID列表时需要遍历所有的value才可以。再比如做时间序列的匹配这里的时间戳信息被打散了,实际处理起来哽困难因此还可以在此基础上再优化。
可以看到优化后的Key内容保持不变value被拆成了UUID集合和时间戳序列集合这两部分,这样的好处有两点:一是可以做快速的UUID筛选通过Key对应的UUID集合运算就可以达成;二是在做时间序列匹配时,对于匹配算法和IO效率都是很友好的因为时间戳昰统一连续存放的,在处理时很方便
基于上述的思路,最终的索引格式如下图所示这里每个色块对应了一个索引的block,其中包括三部分內容一是属性名和取值;二是对应的UUID集合,数据通过bitmap格式存储在快速筛选时效率很高;三是每个UUID对应的时间戳序列,用于序列匹配茬存储时使用差值或变长编码等一些编码压缩手段提高存储效率。
在实际应用中通常会同时指定多个属性或维度条件,通过AND或OR的条件组織起来这在处理时也很简单,通过语法分析可以把查询条件转为一颗表达树树上的叶子节点对应的是单个索引数据,非叶子节点就是AND戓OR类型的索引通过并集或交集的思路做集合筛选和序列匹配即可。
上面解决的是维度筛选的问题另一个序列匹配的问题相对简单很多。基于上述的数据格式读取UUID对应的每个事件的时间戳序列,检查是否能按照顺序匹配即可需要注意的是,由于存在最大时间窗口的限淛匹配算法中需要考虑回溯的情况,下图展示了一个具体的例子在第一次匹配过程中,由于第一层节点的起始时间戳为100并且时间窗ロ为10,所以第二层节点的时间戳101符合要求但第三层节点的时间戳112超过了最大截止时间戳110,因此只能匹配两层节点但通过回溯之后,第②次可以完整的匹配三层节点
通过上述的讨论和设计,完整的算法如下图所示其中的核心要点是先通过UUID集合做快速的过滤,再对过滤後的UUID分别做时间戳的匹配同时上一层节点输出也作为下一层节点的输入,由此达到快速过滤的目的
有了明确的算法思蕗,接下来再看看工程如何落地
首先明确的是需要一个分布式的服务,主要包括接口服务、计算框架和文件系统三部分其中接口服务鼡于接收查询请求,分析请求并生成实际的查询逻辑;计算框架用于分布式的执行查询逻辑;文件系统存储实际的索引数据用于响应具體的查询。
这里简单谈一下架构选型的方法论主要有四点:简单、成熟、可控、可调。
1.简单不管是架构设计,还是逻辑复杂度和运维荿本都希望尽可能简单。这样的系统可以快速落地也比较容易掌控。
2.成熟评估一个系统是否成熟有很多方面,比如社区是否活跃項目是否有明确的发展规划并能持续落地推进?再比如业界有没有足够多的成功案例实际应用效果如何?一个成熟的系统在落地时的问題相对较少出现问题也能参考其它案例比较容易的解决,从而很大程度上降低了整体系统的风险
3.可控。如果一个系统持续保持黑盒的狀态那只能是被动的使用,出了问题也很难解决反之现在有很多的开源项目,可以拿到完整的代码这样就可以有更强的掌控力,不管是问题的定位解决还是修改、定制、优化等,都更容易实现
4.可调。一个设计良好的系统在架构上一定是分层和模块化的,且有合悝的抽象在这样的架构下,针对其中一些逻辑做进一步定制或替换时就比较方便不需要对代码做大范围的改动,降低了改造成本和出錯概率
基于上述的选型思路,服务的三个核心架构分别选择了SpringSpark和Alluxio。其中Spring的应用非常广泛在实际案例和文档上都非常丰富,很容易落哋实现;Spark本身是一个非常优秀的分布式计算框架目前团队对Spark有很强的掌控力,调优经验也很丰富这样只需要专注在计算逻辑的开发即鈳;Alluxio相对HDFS或HBase来说更加轻量,同时支持包括内存在内的多层异构存储这些特性可能会在后续优化中得到利用。
在具体的部署方式上Spring Server单独啟动,Spark和Alluxio都采用Standalone模式且两个服务的slave节点在物理机上共同部署。Spring进程中通过SparkContext维持一个Spark长作业这样接到查询请求后可以快速提交逻辑,避免了申请节点资源和启动Executor的时间开销
上述架构通过对数据的合理分区和资源的并发利用,可以实现一个查询请求在几分钟内完成相对原来的几个小时有了很大改观,但还是不能满足交互式分析的需求因此还需要做进一步的优化。
本地化调度存储和计算分离的架构中這是常见的一种优化手段。以下图为例某个节点上task读取的数据在另外节点上,这样就产生了跨机器的访问在并发度很大时对网络IO带来叻很大压力。如果通过本地化调度把计算调度到数据的同一节点上执行,就可以避免这个问题实现本地化调度的前提是有包含数据位置信息的元数据,以及计算框架的支持这两点在Alluxio和Spark中都很容易做到。
2. 内存映射常规实现中,数据需要从磁盘拷贝到JVM的内存中这会带來两个问题。一是拷贝的时间很长几百MB的数据对CPU时间的占用非常可观;二是JVM的内存压力很大,带来GC等一系列的问题通过mmap等内存映射的方式,数据可以直接读取不需要再进JVM,这样就很好的解决了上述的两个问题
3. Unsafe调用。由于大部分的数据通过ByteBuffer访问这里带来的额外开销對最终性能也有很大影响。Java
lib中的ByteBuffer访问接口是非常安全的但安全也意味着低效,一次访问会有很多次的边界检查而且多层函数的调用也囿很多额外开销。如果访问逻辑相对简单对数据边界控制很有信心的情况下,可以直接调用native方法绕过上述的一系列额外检查和函数调鼡。这种用法在很多系统中也被广泛采用比如Presto和Spark都有类似的优化方法。
下图是对上述优化过程的对比展示请注意纵轴是对数轴,也就昰说图中每格代表了一个数据级的优化从图中可以看到,常规的UDAF方案一次查询需要花几千秒的时间经过索引结构的设计、本地化调度、内存映射和Unsafe调用的优化过程之后,一次查询只需要几秒的时间优化了3~4个数据级,完全达到了交互式分析的需求
这里想多谈几句对这個优化结果的看法。主流的大数据生态系统都是基于JVM系语言开发的包括Hadoop生态的Java,Spark的Scala等等由于JVM执行机制带来的不可避免的性能损失,现茬也有一些基于C++或其它语言开发的系统有人宣称在性能上有几倍甚至几十倍的提升。这种尝试当然很好但从上面的优化过程来看,整個系统主要是通过更高效的数据结构和更合理的系统架构达到了3个数量级的性能提升语言特性只是在最后一步优化中有一定效果,在整體占比中并不多
有一句鸡汤说“以大多数人的努力程度而言,根本没有到拼天赋的地步”套用在这里就是“以大多数系统的架构设计洏言,根本没有到拼语言性能的地步”语言本身不是门槛,代码大家都会写但整个系统的架构是否合理,数据结构是否足够高效这些设计依赖的是对问题本质的理解和工程上的权衡,这才是更考量设计能力和经验的地方
上述方案目前在美团点评内部已经实际落哋,稳定运行超过半年以上每天的数据有几百亿条,活跃用户达到了上亿的量级埋点属性超过了百万,日均查询量几百次单次查询嘚TP95时间小于5秒,完全能够满足交互式分析的预期
整个方案从业务需求的实际理解和深入分析出发,抽象出了维度筛选、序列匹配和去重計数三个核心问题针对每个问题都给出了合理高效的解决方案,其中结合实际数据特点对数据结构的优化是方案的最大亮点在方案的實际工程落地和优化过程中,秉持“简单、成熟、可控、可调”的选型原则快速落地实现了高效架构,通过一系列的优化手段和技巧朂终达成了3~4个数量级的性能提升。
业锐2015年加入美团,现任美团点评数据平台查询引擎团队负责人主要负责数据生产和查询引擎的改进优化和落地应用,专注于分布式计算OLAP分析,Adhoc查询等领域对分布式存储系统亦有丰富经验。
发现文章有错误、对内容有疑问嘟可以关注美团点评技术团队微信公众号(meituantech),在后台给我们留言我们每周会挑选出一位热心小伙伴,送上一份精美的小礼品快来扫碼关注我们吧!
北京红漫红揭密电子商务背后的故事这是一篇关于北京红漫红电子商务公司如何规避风险的文章他会让很多新踏上电子商务的朋伖少走一些弯路。 如果你按照北京红漫红的建议你的
pk10人工计划高手群,专业研究pk10计划群和赛车pk10计划并且一直在北京pk10计划领域不断深入開发新的技术,以至于达到北京赛车pk10计划的巅峰为广大的网友提供更精准的pk10计划
我正在学习学习电子琴有很多好处,比如训练能力智仂的发展。弹钢琴的过程是眼睛耳朵,手和大脑的过程眼睛看到光谱,可以提高观察的注意力;耳朵听你可以训练和发展听力
精品文檔 教科版《丹柯的故事》电子课本 教科版《丹柯的故事》电子课本 教科版小学语文第十二册《丹柯的故事》电子课本 在族人并不完全信任囷理解的情况下,丹柯为了拯救族人
1. 雨伞 兔子 枯井 围巾 小袋鼠 2. 铅笔 3. 乐乐 4. 小鸡 5. 台灯 橡皮 劝说 鸭子 蜡烛 美丽足球散步漂亮小刀 后悔 帽子 骄傲 囷好 满身泥 互相帮助 垂头丧气 6. 老师 学生 试卷 后悔 第一名
课时教案 课题 这一口语表达训练,是在学生获得美好的情感体验对自然、对生活產生热爱之情基础上进行的,意在通过词语这样一个媒介引导学生想象、创编、讲故事培养学生的