内容来源: 2018 年 5 月 5 日,小米HBase研发工程师吴国泉在“ACMUG & CRUG 2018 成都站”进行《大数据时代系统体系架构和对比:存储与计算》演讲分享。
摘要
大数据时代,各种分布式框架层出不穷,存储方面有: HDFS, ES, HBase… 计算方面有:MR, Spark, Flink等等。如何根据业务选取合适的技术方案,相信一定是大家都比较关心的问题,这次的分享就简单谈一谈我对现在比较主流的分布式框架的理解,希望能和大家一起学习进步。欢迎大家加入架构华山论剑:836442475 本群提供免费的学习指导 架构资料 以及免费的解答 不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导 进群修改群备注:开发年限-地区-经验 方便架构师解答问题
MySQL、HBase、Elastcisearch的特点和区别
MySQL、HBase、Elastcisearch是目前比较流行的存储方式。Mysql广泛应用于OLTP业务,支持事务,提供二级索引。HBase面向海量数据存储,有良好的写性能,读性能稍差,不支持事务和二级索引。ES适用于复杂查询和全文检索,不支持事务。接下来我们将通过存储方式和读写方式这两个方面来分析他们的特点。
存储方式
常见的存储方式有行存和列存两种。行存的形式如上图,一条一条记录连续存放,这种方式比较适合于线上,比如一次性读取检索到的数据的全部信息。列存储适合于一些数据分析的业务,这种情况下不需要全部信息,只需特定字段下的相关数据。
与前两种方式不同,ES存储的是倒排索引,适用于全文检索的业务。如图所示原始文档的内容在存储的时候首先会进行分词,然后这些分词会被组合成字典,每个字典后有对应的链表,链表保存的就是该分词所在的文档ID。这样就可以通过一些关键字快速的定位到文档信息。
读写方式
Mysql的读写方式是典型的1+4,其特点在于所有的读写都有可能是随机IO。而HBase的每张表都是由很多Region组成,写模式下数据首先会被写入内存,当内存到达某个阈值之后会进行刷盘生成一个小文件,任何的更新、插入、删除操作都被当做写操作,都是顺序写内存然后刷到盘中。读的时候是通过组件定位到指定Region,然后遍历Region上的所有小文件。这相当于牺牲了读性能来提高写性能。ES的写入类似于HBase,同样是先写内存然后刷盘,不过性能上不如HBase,因为ES在创建倒排索引的时候不仅要做分组,还有评分、压缩之类的操作。虽然ES写入性能较差,但正因为在写入的时候做了这些复杂的计算,所以获得了很强的检索功能。
上图对MySQL、HBase、ES之间的特点进行了详细的总结。关于一致性的问题,这里需要提一下。ES写入数据的时候会创建索引,这个操作会耗费一定的时间,因此ES中数据从写入到可以检索到默认的时间间隔为1s。
计算
解决了数据存储问题之后,接下来就是发现数据价值,这就要利用到计算框架。对此我们主要探讨两方面,离线计算和实时计算。
离线计算
移动计算优于移动数据是MapReduce的早期思想,因此当Map任务在HDFS节点启动的时候,数据不用迁移就可以直接在数据中跑计算任务,当然Reduce阶段还是要做汇总。需要注意的是即使内存足够,Map阶段的数据也还是会落盘。
对于上图中的 ,相信大家一眼就能求出解。而计算机求解的时候首先会将该方程转换成下面的形式,然后假设一个初始值代入方程右边获得新的x值,接着进行不断的迭代,当上一个x值和当前值的差值小于规定阈值的时候迭代结束。在AI和数据分析领域其实都会涉及到这样的解方程形式以及迭代计算。如果用MapReduce来实现的话,每一次迭代都会启动一个MapReduce任务,相对来说代价还是很大的。
针对以上两点问题,Spark提供了一种新的RDD数据结构,如果数据可以存放在内存中就不会进行落盘。另外它还提供了更好的API,不仅是任务调度,在程序编写方面也更加友好。
实时计算
在讲实时计算之前需要明确一点,离线计算不等于批量计算,实时计算也不等于流式计算。离线和实时指的是数据处理的延时,批量和流式则是数据处理的方式。其实流式计算是可以完成批量计算的工作的,之所以还有批量计算框架,是因为流式计算的设计难度远高于批量计算。google的流式计算负责人有过这样的观点——一个设计良好的流式系统可以完全取代批量系统。
目前实时计算有这样几个难点,分别是吞吐、exactly once、数据乱序。下面会分别介绍Storm、Spark、Flink针对这三点提出的解决方案。
Storm
上图是Storm统计词群的过程,首先由spout从输入源中读取一条数据,然后上游bolt接收数据进行分词,接着下游bolt根据key值接收数据并将数据入库,最终得到统计结果。
如果中间的分词系统挂了,storm会提供一个acker任务,每个bolt在计算完之后都会向acker发送一个ack信息用来声明任务执行成功,当整个流程中所有的ack信息都发送给acker之后,acker就认为这条信息处理成功并返回成功消息给输入源。这种场景下ack信息会随着数据量增长,因此特别影响storm的性能,这也是早期我们认为流式计算的吞吐量不如批量计算的一个重要原因。
如果在处理的过程中某个计算节点挂了,而另外的节点却入库成功,这时acker会认为该条记录已处理失败进而重发,导致DB中的部分数据会重复累加。
Spark streaming
Spark streaming针对以上两个问题进行了优化。首先是关于吞吐,不再是一条一条处理而是小批量的处理,默认间隔为1秒,这1秒内所接收到的数据会被生成为一个batch然后向下游发送,也就是通过扩大粒度来提高吞吐。
我们都知道离线计算本来就是精准计算架构,Spark streaming内部是利用spark的逻辑来保证exactly once。
Flink
Flink不再是一条一条数据做ack,而是在每段数据之间打上checkpoint,然后针对每段数据进行确认,如果任务挂掉就会在上一次成功的checkpoint点重新恢复数据。通过这种方式将流式计算和容灾较好的结合起来。
流式计算会有一个窗口的概念,比如上图中就有3个5秒窗口,方框中的编号代表事件发生的时间。可以看到第3秒的时候有两条访问事件,由于网络的延迟问题很有可能这3秒的数据会被分到第二个5秒窗口中,导致数据不正确。造成这样结果的原因是早期的流式框架在处理数据的时候,将接收数据的时间认为是数据产生的时间。这里延伸出里了两个概念,event time——数据真正产生的时间,process time——系统处理该数据的时间。对此最直观的解决方案就是让数据携带自身产生时的时间戳,流式系统以该时间戳为基准。