Author: Mark.wei
2016年底,facebook 统计报告显示全球已经有33亿人接入互联网,如我们算上通信用户,那么全球有超过三分之二的人已经通过某种方式接入互联网,每一天,人类的活动都将产生海量的数据,并且量级呈指数级上升。
正是海量数据的快速生成,对于海量数据的存储和处理的技术也相对应的快速发展,由于大数据领域的快速发展,统计分析已经无法把数据的优势完美发挥,从而促使机器学习的第三次爆发。
几乎是每隔几个月,我们都会看到到一些新名词的诞生,Hive,Storm,Spark,Gemfire,Beam,Nifi,Kylin,Apex...(我已经数不过来了)
回头看,Hadoop诞生已有12年之久了,在当前场景的实际应用中,hadoop如一位蹒跚的老妇人,无法应对快速数据的处理,而流式数据处理(如 Strom, Spark-streaming, kafka-Streams)把 in time 变成 real time,极大的满足了用户对实时性的要求。
说到流式数据,大家肯定会想到storm 和spark 这两位大侠,spark-streaming虽然缓解了spark在流式上的缺陷,但我今天要说的是号称比spark快10-100倍的apex。
Apahce Strom 是hadoop生态第一个处理流式数据(streaming data)的项目, 现在除了Storm, Flink, Spark-streaming, Kafka Streams 之外又多了一种选择--Apex。
源自DataTorrent的Apache Apex, 诞生于2015年6月,可谓横空出世,现已荣升为apache的顶级项目。
无界数据和持续处理
数据集可分为有界和无界两类。有界数据是有限的,数据的有固定的时间区间,而无界数据是不断增长的,本质上说是无限数据集。但是数据集的有界或无界分类并不依赖于数据如何产生。通常来说,无界数据使用流式处理, 有界数据使用批处理(离线处理)。
股市或者汇市的交易数据既有有界数据也有无界数据,炒股的朋友肯定比较熟悉,交易数据每时每刻都在变化,通常来说股市里的技术派,所使用的日线MACD,KDJ,RSI等指标,就是典型的离线分析,是滞后的,对于短线交易者或中长线交易者有一定的参考价值(注意是参考,能不能赚钱,看你自己的造化), 而交易市场必然存在一些日内交易或高频交易者,捕捉瞬时的交易信号,利用量能,平多力量寻找投资机会,差异在毫秒之间,流式处理就可以满足这些弄潮儿的需求。
流式处理
流式处理以为处理不间断的事件,当事件发生后,输入数据进入时,可以立即处理,没有人工参与导致的延迟(这个和批处理不同)。信息的低延时处理和快速的结果反馈,对实时使用场景是非常重要的。当然,流式处理并不仅限于处理实时数据,他也可以用来处理历史数据。
假设我们的数据保存在一份文件里,首先需要一行一行的读取数据,在读取数据的同时,我们分析处理数据,这样就如同一条水管,数据在水管中流动,随着数据的流动,程序不间断的处理数据。
流式应用可以快速的处理数据,流式处理可以完成求和,取均值,取最大值等操作,为了完成这些操作,需要把流式数据分割成临时的边界(通常叫做窗口)
从上图可以看到,连续的数据可以通过两种方式分割,翻滚窗口(Tumbling Window)和滑动窗口(Sliding Window), 在窗口的结尾,输出计算结果。
流式处理系统
大数据生态中,第一个开源的流式数据处理框架叫做storm,后来各种更优秀的流式处理框架诞生(如 Flink,Apex ,Heron),新的流式框架不仅具有更强的能力和适应性,而且有状态管理,处理保护,容错,伸缩性和高性能。
Twitter 列举的Storm罪状
我们来看看Twitter某篇论文中列举的Storm几宗罪
Worker进程: Storm中每个Worker进程有多个Executor线程组成,每个Executor线程又可以包含多个Task(Spout或者Bolt),这种复杂的多层的嵌套关系会让系统调试和资源高效分配变得比较困难。
问题一,Worker进程内部可以运行不同Task,由于这些Task在Worker进程内部并没有实现资源隔离,所以很难单独分析具体某一个Task的性能,而且如果Task在运行时发生异常导致Worker进程崩溃,则Worker进程内的其他Task也会被Kill掉,从而导致Topology的整体性能下降。
问题二,资源的不合理分配,假如有一个拓扑包括三个Spout和一个Bolt,两个Worker进程,Spout需要5G内存,Bolt需要10G内存,由于Storm中每个Worker的资源配置是相同的,而且其中一个Worker要运行一个Spout和一个Bolt,所有就要为每个Worker预分配15G内存,两个Worker共30G内存,而实际情况只需分配25G内存就可以了,导致5G内存的浪费。
问题三,Worker进程内部线程设计不合理。熟悉Storm的同学们应该知道,每个Worker进程有个全局的接收线程,负责接收上游Socket数据,并路由到指定的Executor线程,也有个全局的发送线程,负责将该进程内所有Executor产生的数据发送到下游的Worker中。在Executor内部,也包含两个线程,一个是用户逻辑线程,负责执行具体的Spout/Bolt工作,另一个是本地发送线程,负责将Executor产生的数据发送到Worker全局的数据发送队列中。这样一来,Tuple从进入Worker到从Worker出来,一共会经过四个线程,线程间的切换开销较大,同时多个Executor都在往一个全局的发送队列发送数据,也会产生队列竞争问题。
Nimbus负担过重: 在Storm设计中,Nimbus节点承担了多个任务,例如所有Topology的任务调度,监控以及JAR包的分发,当集群运行的Topology非常多时,Nimbus则会成为一个性能瓶颈。
问题一,Nimbus在任务调度时,不支持细粒度的资源分配和资源隔离。运行在同一台机器上的不同Topology的Worker可能会相互影响,从而会出现一些很难定位的性能问题。
问题二,Zookeeper性能瓶颈,Topology的状态信息均保存在Zookeeper集群中,所有Worker以及Supervisor的心跳信息都由Zookeeper来管理,Zookeeper内部数据是写入到磁盘的,所以当Topology和Worker的数量规模很大时,Zookeeper则会成为一个性能瓶颈。
问题三,Nimbus单点失败。
缺少反压机制:反压的意思是说当流系统下游的组件处理速度变慢时,通知上游的组件降低数据发送速度。通俗的讲,是这样,嘿,哥们,我已经处理不过来了,你先停停,别着急往我这发。
处理效率: 这里Twitter结合生产环境实际使用情况,列举了一些降低系统性能的主要原因:
问题一,非最优的Replay机制。在Storm中,如果在Tuple在Tuple Tree中任何一个环节发生错误,就会触发Tuple Failure,然后进行Replay,当整个拓扑的扇出特别高时,则会经常性发生Tuple Replay,从而降低系统的处理效率。
问题二,较长的垃圾回收周期。比如说Worker进程消耗了大量内存,在进行垃圾回收时,花费的时间较长,然后则会导致一系列的其他问题,如Nimbus有可能会认为该Worker进程已经挂掉了,或者Tuple延迟过高触发Tuple Replay。
问题三,队列竞争,这个问题之前提到过了,Worker进程内部多个Executor都在往一个全局的发送队列发送数据,引起队列竞争。
Storm也已经进化到2.0版本,据说,上述问题也解决了不少。流式框架的发展,为我们提供了越来越易用的工具,降低了学习的门槛。