Hadoop是当前重要的大数据计算平台,它试图摒弃传统数据库的理念,重新构建一套新的大数据体系。但是,这并不是件很容易的事,在Hadoop的设计和实现中能看到一些先天不足的地方,其中一点就是把理论问题和工程问题给搞拧了。
所谓理论方法,是指试图解决问题的一般情况,设计通用的算法能适应尽量多的情况,并努力使算法的复杂度降低。在研究问题时不会考虑具体环境下某个具体动作是否可以执行以及该动作消耗的资源,而会假想一个理想的环境;工程方法则相反,不需要考虑普遍的一般情况,而是针对某个具体问题,充分利用当前环境提供的便利,并充分考虑每个具体动作消耗的资源(包括时间),尽量高效率低成本地实现目标。原则上,在设计体系模型时要理论化,而在编码实现时则要工程化,这样才能做出一个即能解决足够多问题且效率也可接受的产品。
但是,Hadoop在不少地方把这个事情搞反了。本该由理论解决的一般性问题被用工程方法简化成特定问题,而具体实现时又把本该在工程层面优化的问题想得过于理论化。导致的结果就是能方便解决的问题太少,而解决问题时效率又太低。一句话说就是理论问题工程化,工程问题理论化。
MapReduce是Hadoop中关键的并行机制,经过多轮封装改进,这个模型现在仍然是Hadoop的算法核心。这个方法认为可以将一个大任务拆成一些无关的小任务,分别处理(Map)后再按某个键值归拢到一起再做处理(Reduce)。Hadoop实现好这个框架后,程序员就只要写Map和Reduce动作即可了,这样就编写并行程序就会简单很多。
MapReduce用来处理求和、计数等简单问题也确实有效,但世界可没有这么简单!
琢磨一下用MapReduce去实现JOIN运算的麻烦度就知道了(具体做法可以从网上找到,这里不细说了),而这种有关联的运算在现实需求中相当常见,其它一些次序有关的运算用MapReduce实现也很困难。也就是说,MapReduce算法能够方便描述的运算太少了,这个模型的适应面太窄。基于这个理论实现的产品,面对复杂运算时基本上起不到减少工作量的目标,甚至根本无法应用。本该在理论上研究出更通用的算法,却被工程化成简单情况。
然而,Hadoop在实现MapReduce框架时,却又缺乏对具体情况的考虑,而是假想了一个理想环境。Hadoop认为集群节点机可以无穷多,任何性能问题都只要增加节点就可以解决,这样就没兴趣去关注单机性能了,肆意地跑出多个进程而很少采用经常效率更高(但不总是)的多线程手段;Hadoop不考虑外存读写的巨大延迟,Map过程 的中间结果要写盘暂存,理论上复杂度其实也不算很高(读写次数基本是确定的),但实际运行效率之低令人难以容忍,很难想象有工程经验的程序员敢采用这种机制。实测情况是大概四五个节点的hadoop集群才能赶得上被充分工程优化后的单台机器性能。
Spark也有类似的问题。Spark前的Hadoop在工程上对内存利用不足(如前述的MapReduce总是要落盘),而Spark又走向另一个极端,从理论模型上就只考虑内存计算了。我们知道,外存计算和内存计算是很不一样的,外存计算要复杂困难得多,而大数据常常面临外存计算,原则上我们应当改进当前的外存计算算法体系,努力降低复杂度并扩大适应范围,而不是简单避开这个困难,这就是刻意简化问题了,适应面会大折折扣。
退一步讲,就算只考虑内存计算,能做好也是有相当意义的。但是,Spark在实现时却又走入了理论化的窠臼,它把那个RDD搞成immutable,这个机制有很强的理论色彩,和数学运算中的抽象数据类型一致,用来描述算法确实很美。可是,实际运行时将造成大量没有必要的内存复制,本来是打算提高性能的,却得不偿失,然后又要再用其它手段去绕。
类似的情况,细纠下去还有。发明新理论确实很难,为降低难度而简化问题还可以理解(其实传统数据库领域已经积累了不少这理论,只是学会并不算很难),但在工程实现时又搞得理论化就难以理解了,这样怎么会有高性能?