轻量级大数据计算
沉重的大数据计算
现在的大数据平台有个明显的问题就是越来越沉重。一方面单机性能不再被关注,而是转向集群规模的堆砌,硬件方面则尽可能的避开外存计算困难,指望巨大的内存。另一方面框架的体系越发庞大复杂,试图包罗万象。
轻量级计算需求
从技术角度来看大数据的核心主要是快速的计算以及性能的提高。这两点其实并不一定只在大数据量的情况下才有需求,即使是几G,几十G这样的规模在高并发的情况下也需要快速的计算和高性能。而且对于临时性的数据处理也不适合建设大数据平台。
大数据计算开发难度大
目前很多大数据平台都将努力的方向转向了SQL,因为它是性能比拼的主要性能。一般我们在学习的时候使用的SQL大多在3、5行之内,而实际开发过程中碰到的可能更多是3、5百行的SQL语句,优化SQL性能其实几乎无助于降低这种SQL的开发难度。这时就会导致仍需大量底层的编码,要经常编写UDF。其实提高性能本质上是降低开发难度,如果复杂运算的自动化优化靠不住,那么就需要快速编写高性能的算法。
如果你对大数据开发感兴趣,想系统学习大数据的话,可以加入大数据技术学习交流扣扣群458345782,私信管理员即可免费领取开发工具以及入门学习资料
集群透明化
现在的大数据平台也在努力实现集群的透明化,让单机和集群有更高的一致性,提高代码的兼容性,一定程度上降低开发难度。但是在集群不是很大的时候并不一定能获得最优的性能,因为高性能的计算方案因场景而异,其中有些可能是相互矛盾的。且透明化只采取最保险的方法,一般是这些方法中性能较差的那个。
轻量级计算的技术特征
轻量级计算主要有这样一些特征。首先它是面向过程计算,强调可集成性;其次数据源是开放的,并不一定都是在数据库中;另外它更注重单机优化,会尽可能的压榨单个机器的计算能力,然后才会考虑集群。最后使用的时候要在集群透明和高性能之间进行权衡,比如节点文件存储,不用网络文件系统;多个单机运算,不用统一集群框架。
漏斗运算举例
问题解释
电商漏斗分析能够帮助运营人员分析多步骤过程中每一步的转化和流失情况,上图展示了依次触发登录、搜索商品、查看商品、生成订单事件的人群转化流式情况。
关键点
关于上述的功能实现有几个关键点。第一是时间过滤,一次性抽取所有阶段的数据是没有必要的,我们所关心的往往是某一个时间段内的事件数据。第二是时间窗口,即目标事件得在规定时间内发生才算有效。第三目标事件要具备有序性,如先浏览商品后放入购物车,反之则不算。最后事件属性也需要过滤,比如我们目前只统计某一品牌的商品。
以上关键隐藏着几个难点。首先是事件窗口跨天,比如2017年1月1日12点至2017年1月6日12点也被当做5天。其次是目标事件的顺序不确定,因为是由参数决定目标事件的顺序,因此可能会出现“浏览商品”->“放入购物车”,也可能出现“放入购物车”->“浏览商品”。另外是事件属性不确定,浏览商品时间有brand属性,登录事件可能啥属性都没有,而属性又是以json串的形式存在。
传统计算方案
针对以上这些问题,显然采用SQL是无法解决的,有序计算是SQL的软肋,因此不适合写这类过程计算。常规的做法是使用UDF,毫无疑问这种方式理论上可以提高性能,但是开发工作量太大了,且缺乏通用性,后续的维护非常麻烦。
聚合算法
聚合算法可以算是一个比较好的解决方案,上图为聚合算法(单个用户)的相关参数定义。M表示的是在规定时间窗口也就是T内,所触发的最大事件的序号,当M的值等于K,即等于目标事件个数时,就算完成了一个流程。因为整个流程中可能会很多的序列,且还有可能重复,比如出现12123这样的形式,明显12的事件触发了两次,于是我们就用A来记录子序列的最大事件序号,这是分别是2和3。S记录的是当前事件的时间戳,如果S超出了T规定的时间窗口,那么其所在的序列将被判定为无效。
上图通过集算器编写的聚合算法的实际代码。
优化
执行聚合算法需要对原始数据进行一些整理,比如获取事件和用户的总数。简单的做法就是直接使用SQL语句的group_by,因为要同时查询用户码表和事件码表,所以我们需要group_by全部数据两次。众所周知大数据运算的性能瓶颈经常在于硬盘,而在关系型数据库中进行两次group_by,即使是针对同一个表理论上还是要遍历两次,CPU的计算时间可以忽略不计,但是涉及硬盘的时间则不能忽略。
为此我们在集算器中设计了一种管道机制。数据在进入游标进行group的时候,同时会有一种机制将数据压入到管道中,在管道中继续进行group,这样一次运算就能够输入两份结果。
这就是实际的代码和效果对比,很明显时间缩短了将近一半。
事件ID
整个过程中查询事件必不可少,原先事件的ID类似于100007、10004、10010这样的形式,事件顺序的判断依据这些ID编号,这种方式对在数组查找成员相对来说还是很慢的。
我们的做法是直接将事件ID序号化,预处理阶段的时候原先的ID号被转换为了简单的id序号,实际计算阶段就可以直接根据id来查找到相关的事件。同样的因为原先的用户ID也是一长串的数字,所以也应该进行序列化以节省查询时间。
事件属性
事件属性主要存在两个问题,一是每个事件的属性项不同,二是属性是以json串的形式保存。做过调优的朋友应该都知道文本分析的性能都非常差,不仅如此,字符串的比对也很慢,且难以实现复杂的过滤需求,转成json对象或序表又浪费存储空间。
所以我们将原先的json串形式转成了数组,造事件码表的时候把事件属性整理好,事先规定好属性顺序,这样连属性名也省了,大量节约存储空间,也加快了读取速度。
这是关于用户ID,事件ID序号化,事件属性数组化的所有代码。
多线程和多进程
解决完以上问题之后,再来看下如何对单机性能进行优化。我们一开始采用的是多线程的方案,因为相对来说比较简单,但是经过测试发现当多线程并行数达到一定数量后性能却不升反降。在改用多进程之后,并行数一直升到机器核数,性能始终呈上升态势。
由于我们使用的是java语言,造成这种情况的原因可能是由于多进程的内存事先已经分配好了,之后不会产生冲突。而多线程在分配内存的时候有可能会加锁、抢资源,其他的线程就需要等待,当内存足够大时冲突还不会显现,一旦内存较小就会造成冲突。
单机和多机
接下来我们就开始搭建集群。最初做的是4台机器的集群,每台机器16个核,采用多进程的方式也就是有64个进程,通过主程序指挥,这里主机要和64个进程进行通信,相对来说成本有些高。
后来我们又重新构建了个三层结构,让每个机器都有一个子主程序,由子主程序和上端的子程序通讯,这样机器间的通讯就只剩4次。
总结体会
通过对上述案例的总结,我们有这样几个体会。首先过程计算实际上是一个普遍问题,虽然大数据平台都在努力优化SQL,但却无助于此类问题的解决,而过度依赖UDF又会使平台本身失去意义。另外性能优化是个不断试错迭代的过程,需要敏捷的开发工具快速的做出原先测试,由于开发太慢UDF就变的不太适合了。
网友评论