划分方法-最基本的海量技术思想
-
传统Hash,最基本的划分方法
- 将大数据、流量均分到N台服务器,找到合理的key,hash(key)尽量分布均匀,如hash(key) mod N == 0则将其分到第0台服务器
-
随机划分
-
一致性Hash:支持动态增长,更高级的划分方法
一致性hash:考虑到分布式系统每个节点都有可能失效,并且新的节点很可能动态的增加进来,如何保证当系统的节点数目发生变化时仍然能够对外提供良好的服务,这是值得考虑的,尤其实在设计分布式缓存系统时,如果某台服务器失效,对于整个系统来说如果不采用合适的算法来保证一致性,那么缓存于系统中的所有数据都可能会失效(即由于系统节点数目变少,客户端在请求某一对象时需要重新计算其hash值(通常与系统中的节点数目有关),由于hash值已经改变,所以很可能找不到保存该对象的服务器节点),因此一致性hash就显得至关重要。
假设有4台服务器,地址为ip1,ip2,ip3,ip4
- 计算4台服务器的哈希值hash(ip1)、hash(ip2)、hash(ip3)、hash(ip4),并将其映射到 0 ~ 2 32 0~2^{32} 0~232的一致性hash环上。
- 当用户在客户端进行请求时候,首先根据hash(用户id)计算hash值,映射到hash环上。
- 从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过 2 32 2^{32} 232仍然找不到服务器,就会保存到第一台服务器上
MapReduce是一个用于处理海量数据的分布式计算框架
- 数据分布式存储
- 作业调度
- 容错
- 机器间通信等问题
HDFS:系统可靠性、可扩展性、并发处理
MapReduce分而治之思想:分解 -> 求解 -> 合并
MapReduce映射
- 分:map 把复杂问题分解为若干简单的任务
- 合:reduce
mapreduce的执行流程
输入和拆分
数据的准备阶段
分片操作(split)
split是将源文件的内容分片形成一系列的Inputsplit,每个Inputsplit中存储着对应分片的数据信息(如:文件块信息、起始位置、数据长度、所在节点列表……)并不是将源文件分割成多个小文件,每个分片由一个mapper进行后续处理。
每个分片大小参数是很重要的,splitsize是组成分片规则很重要的一个参数,该参数由三个值来确定:
- minsize:splitsize的最小值(mapred-site.xml配置文件的mapred.min.split.size参数确定)
- maxsize:splitsize的最大值(mapred-site.xml配置文件的mapreduce.jobtracker.split.metainfo.maxsize参数确定)
- blocksize:HDFS中文件块大小(hdfs-site.xml配置文件中的dfs.block.size参数确定)
splitsize的确定规则:splitsize=max{minsize,min{maxsize,blocksize}}
数据格式化操作(Format)
将划分好的Inputsplit格式化成键值对的数据格式。其中key为偏移量,value是每一行的内容。
注意:在map任务执行过程中,不停地执行数据格式化操作,每生成一个键值对就会将其传入map,进行处理。因此,map和数据格式化操作是同时进行的。
map映射
根据用户指定的map过程,mapreduce尝试在数据所在机器上执行该map程序。在HDFS中,文件数据被复制多份,计算时将会选择拥有数据的最空闲的节点。
shuffle派发(核心)
shuffle过程是指mapper产生的直接输出数据结果,经过一系列的处理,成为最终的reducer直接输入数据为止的整个过程。这是mapreduce的核心过程,该过程可以分为两个阶段:
Mapper端的shuffle(数据分区,排序)
由mapper产生的结果并不会直接写入磁盘中,而是先存储在内存缓存区中。当内存中的数据量达到设定的阈值(默认80%)时,一次性写入到本地磁盘中。在写入到磁盘之前,需要进行partition、sort、combiner等操作。其中,partition分区是在内存缓存区对map输出结果指定partition的key值和预定的reduce数量来进行hash分区。sort排序是对同一个partition中的数据按照key值进行排序,combiner是把key值相同的记录进行合并。
reducer端的shuffle(同一分区排序)
由于mapper和reducer通常不在同一节点上运行,所以reduce需要从多个节点上下载mapper的结果数据,并对数据进行处理,然后才能被reduce处理。
reduce缩减
reduce接收数据流形成相应的输出。具体功能可以由用户自定义,最终结果直接写入到hdfs。每个reduce进程会对应一个输出小文件。
Mapreduce工作原理
通过Client、JobTracker和TaskTracker的角度来分析MapReduce的工作原理:
首先客户端(client)启动一个作业(job),向Jobtracker请求一个jobID。将运行作业所需要的资源文件复制到hdfs上,包括MapReduce程序打包的jar文件、配置文件和客户端计算所得的输入划分信息。这些文件都存放在jobtracker专门为该作业创建的文件夹,文件夹名为该作业的Job ID。jar文件默认会有10个副本(mapred.submit.replication属性设置)。输入划分信息告诉jobtracker应该为这个作业启动多少个map任务等信息。
jobtracker接收到作业之后,将其放在一个作业队列里,等待作业调度器对其进行调度。当作业调度器根据调度算法调度到该作业时,会根据输入划分信息为每个划分创建一个map任务,并将map任务分配给tasktracker执行。对于map和reduce任务,tasktracker根据主机核的数量和内存大小有固定数量的map槽和reduce槽。
tasktracker每隔一段时间会给jobtracker发送一个心跳,告诉jobtracker它依然在运行,同时心跳中还携带着很多信息,比如当前map任务完成得进度信息。当jobtracker收到作业的最后一个任务完成信息时,便把作业设置为“success”。当jobclient查询状态时,它将得知任务完成,便显示一条消息给用户。
从map端和reduce端来分析:
Map端流程:
1)每个split由一个map任务来处理,map输出的结果(包含生成的partition值)会存储在一个环形内存缓存区(该缓存区大小默认为100M,由io.sort.mb控制),当该缓存区达到阈值时(默认为缓存区大小的80%,由io.sort.spill.percent属性控制),会在本地文件系统中创建一个溢出文件,将该缓存区中的数据写入到这个文件。
2)在写入到磁盘之前,线程首先根据reduce任务的数量和partition的key值将数据划分成不同的partition。主要目的是为了避免有一些reduce任务分配到大量数据,而有些reduce任务却分到很少数据,甚至没有数据,导致任务分配不均衡。分区是对key值数据进行hash取模的过程。分区之后根据排序的key值进行sort,如果此时设置了combiner,将排序后的结果进行combiner操作,这样做的目的是为了让可能少的数据写入到磁盘。
3)当map任务输出最后一个记录时,可能会有很多的溢出文件,这时需要将这些文件归并排序,合并过程中会不断地进行排序和combiner操作。目的有两个:
- 尽量减少每次写入到磁盘的数据量
- 尽量减少下一次复制时网络传输的数据量
最后合并成一个已分区且已排序的文件。(为了减少网络传输的数据量,可以将数据压缩)
4)分区的数据如何知道它对应的reduce是哪个?其实map任务一直和其父tasktracker保持联系,而tasktracker又一直和jobtracker保持心跳,所以jobtracker保存了整个集群的宏观信息。只要reduce任务向jobtracker获取对应的map输出位置就可以了。
Reduce端流程:
1)reduce会接收不同map任务传来的数据,并且每个map传来的数据都是有序的。如果reduce接收的数据量相当小,则直接存储在内存中,若果数据量超过缓存区的阈值时,则会像map端输出数据到磁盘过程一样,生成溢写文件到磁盘中。
2)随着一些文件的增多,后台线程会将它们合并成一个有序的大文件,为了给后面的合并节省空间。最后一次合并的结果没有写入到磁盘中,直接输入到reduce任务中。
Hadoop1.X的MapReduce
两个重要的进程
- JobTracker
- 主进程,负责接收客户作业提交,调度任务到节点上运行,并提供监控工作,监控节点状态及任务进度等管理功能,一个MR集群有一个JobTracker,一般运行在可靠硬件上。
- tasktracker通过周期性的心跳来通知JobTracker其当前的健康状态,每一次心跳包含了可用的map和reduce任务数量、占用的数目以及运行中的任务详细信息。JobTracker利用一个线程池来同时处理心跳和客户请求。
- TaskTracker
- 由jobtracker分配任务,实例化用户程序,本地执行任务并周期性地向jobtracker汇报状态。每一个节点上只会有一个tasktracker。
- JobTracker一直等待JobClient提交作业
- TaskTracker周期性向JobTracker发送心跳并询问有没有任务可做,如果有任务,让其分配任务给它执行。
MR的各个组件功能
- File:文件存储在HDFS上,每个文件切分成多个(默认64M)的Block(默认3个备份)存储在多个节点上(DataNode)
- InputFormat:MR框架基础类之一,包含数据分割(Data splits)和记录读取器(Record Reader)
- Split:实际上每个split包含后一个Block开头部分的数据(解决记录跨block问题)
- RecordReader:每读取一条记录,调用一次map函数,直到split尾部。
- Shuffle:最核心的部分,包含Partion、Sort、Spill、Meger、Combiner
- Partitioner:决定数据由哪一个Reducer处理,从而分区。比如hash法,有n个Reducer,那么数据key:value中的key对n取模,返回余数,即为数据的分区{partition,key,value}
- MemoryBuffer:内存缓存区,每个map结果和partition处理的key value结果都保存在缓存区。缓存区默认大小为100M,溢写阈值80%
- Spill:内存缓存区达到阈值时,溢写spill线程锁住这80M的缓存区,开始将数据写到本地磁盘中,然后释放内存。每次溢写都生成一个溢写数据文件。这些的数据到磁盘前会先对partition分区,然后对数据key进行sort,以及合并combiner
- Combiner:数据合并,将相同key的数据进行value值合并,减少输出传输量。事实上类似于reducer函数
- reducer的shuffle阶段:和map一样,内存缓存区达到阈值时,通过sort和combiner将数据溢写到磁盘。
- Reduce:多个reduce任务输入的数据属于不同的partition,因此数据的key不会重复。一个reduce生成一个文件。
Map
- map个数一般情况下为split的个数
- 压缩文件不可切分
- 非压缩文件和sequence文件可以切分
- dfs.block.size决定block的大小(默认64M)
- 控制map个数:压缩文件或者修改block大小
Reduce
- reduce个数设置
- mapred.reduce.tasks(默认为1)
- reduce个数太少
- 单次执行慢
- 出错再试成本高
- reduce个数太多
- shuffle开销大
- 输出大量小文件
Streaming
- MapReduce和HDFS底层基于Java实现,默认提供Java编程接口
- Streaming框架允许任何语言来实现在Hadoop MapReduce使用的程序
Streaming优点
- 开发效率高
- 方便移植到Hadoop平台, 只需按照一定的格式从标准输入读取数据、向标准输出写数据
- 原有的单机程序稍加改动可以在Hadoop平台进行分布式处理
- 容易单机调试:cat input|mapper|sort|reducer > output
Streaming缺点
- 程序运行效率低:底层解析(Java->流数据->Java)使得效率变慢
- Streaming默认只能处理文本数据,如果对二进制数据进行处理,比较好的方法是将二进制的key和value进行base64的编码转化为文本即可