HBase简介:
HBase在产品中还包含了Jetty,在HBase启动时采用嵌入式的方式来启动Jetty,因此可以通过web界面对HBase进行管理和查看当前运行的一些状态,非常轻巧。
简单来说,你在HBase中的表创建的可以看做是一张很大的表,而这个表的属性可以根据需求去动态增加,在HBase中没有表与表之间关联查询。
列存储
HStore存储是HBase存储的核心了,其中由两部分组成,一部分是MemStore,一部分是StoreFiles。MemStore是Sorted Memory Buffer,用户写入的数据首先会放入MemStore,当MemStore满了以后会Flush成一个StoreFile(底层实现是HFile),当StoreFile文件数量增长到一定阈值,会触发Compact合并操作,将多个StoreFiles合并成一个StoreFile,合并过程中会进行版本合并和数据删除,因此可以看出HBase其实只有增加数据,所有的更新和删除操作都是在后续的compact过程中进行的,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O的高性能。当StoreFilesCompact后,会逐步形成越来越大的StoreFile,当单个StoreFile大小超过一定阈值后,会触发Split操作,同时把当前Region Split成2个Region,父Region会下线,新Split出的2个孩子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Region的压力得以分流到2个Region上。
HFile里面的每个KeyValue对就是一个简单的byte 数组 。
HRegion是Hbase中分布式存储和负载均衡的最小单元。最小单元就表示不同的Hregion可以分布在不同的HRegion server上。但一个Hregion是不会拆分到多个server上的。
HFile格式:
一、HBase基本概念: 2个主要概念:
a) Rowkey: Hbase 中的记录是按照rowkey来排序的;
b) Column family:(列族)是在系统启动之前预先定义好的;
c) Hbase优缺点:
1.不支持条件查询以及orderby等查询;
2.列可以动态增加,列为空则不存储数据,节省存储空间;
3.会自动切分数据;4.可以提供高并发读写操作的支持;
访问方式: 访问hbasetable中的行,只有三种方式:
1 通过单个row key访问
2 通过row key的range
3 全表扫描
Row key:行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。
存储: HBase以表的形式存储数据。表有行和列组成,存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)
存储类型: TableName 是字符串
RowKey 和 ColumnName 是二进制值(Java 类型 byte[])
Timestamp 是一个 64 位整数(Java 类型 long)
value 是一个字节数组(Java类型 byte[])
存储结构:即HTable按Row key自动排序,每个Row包含任意数量个Columns,Columns之间按Columnkey自动排序,每个Column包含任意数量个Values。理解该存储结构将有助于查询结果的迭代。
(RowKey,List(SortedMap(column,List(value,TimeStamp))))
列簇:hbase表中的每个列,都归属与某个列族。列名都以列族作为前缀。
HBase中的列可以动态新增。
存储单元:HBase中通过row和columns确定的为一个存贮单元称为cell。
每个cell都保存着同一份数据的多个版本。版本通过时间戳来索引。
HBase为null的Column不会被存储,这样既节省了空间又提高了读性能
cell中的数据是没有类型的,全部是字节码形式存贮
两种数据版本回收方式:一是保存数据的最后n个版本
二是保存最近一段时间内的版本(比如最近七天)
用户可以针对每个列族进行设置值value:每个值由4个键唯一索引
tableName+RowKey+ColumnKey+Timestamp=>value
二、搭建HBase环境:
http://hbase.apache.org/book/quickstart.html 和 http://hbase.apache.org/book/notsoquick.html 。 如果你在windows环境下配置cygwin及ssh遇到问题可以参考 http://qa.taobao.com/?p=10633
1. 创建一个Maven工程。
mvn archetype:generate-DgroupId=com.alibaba.webx -DartifactId=tutorial1 -Dversion=1.0-SNAPSHOT -Dpackage=com.alibaba.webx.tutorial1-DarchetypeArtifactId=archetype-webx-quickstart-DarchetypeGroupId=com.alibaba.citrus.sample-DarchetypeVersion=1.0-SNAPSHOT -DinteractiveMode=false cmd进入刚才建立的项目,运行:mvn jetty:run 在浏览器中打开:localhost:8081就可以看到我们新建的webx项目了。具体里面是怎么运行的,可以查看webx用户手册。
2. 加入Hadoop、HBase依赖:
3. 在src/test/resources目录下新建文件conf/hbase-site.xml
文件具体配置为:
配置你工程要使用的zookeeper客户端端口号和zookeeper的地址,这个地址可以向开发索要。
4. 写测试文件:
如果不报错,表示链接已经通过,接下来就可以创建表以及对表的增删改查了。
三、基础知识:
1. 通过HBase shell 与HBase交互:
进入控制台:bin/hbase shell
创建表:create ‘表名’,’列簇名’,’列簇名’
增加记录:put ‘表名’,’Row Key’,’列簇名:列名’,’value’
查询:get ‘表名’,’Row Key’
删除:delete‘表名’,’Row Key’,’列簇名:列名’ (只能删除一列)
delete‘表名’,’Row Key’ (删除RowKey的所有列)
删除表:>disable ‘表名’
>drop ‘表名’
2. 通过Java 的API与HBase交互:
步骤一:
创建一个Maven工程加入依赖:
如果你的Maven库里还没有hbase,还需要配置下repository:
步骤二:
确保HBase环境已启动且能连接到,将HBase环境的hbase-site.xml文件拷贝到上述工程的src/test/resources目录
加载配置->创建表->增加记录->根据RowKey查询->遍历查询与迭代->删除记录->删除表
具体操作可以参考: http://qa.taobao.com/?p=13894
http://www.cnblogs.com/panfeng412/archive/2011/08/14/2137984.html
四、深入理解HBase:
思考:HBase服务器内部由那些主要部件构成?
HBase的内部工作原理是什么?
1. HBase的工作原理:
首先HBase Client端会连接Zookeeper Qurom(从下面的代码也能看出来,例如:HBASE_CONFIG.set("hbase.zookeeper.quorum","192.168.50.216") )。通过Zookeeper组件Client能获知哪个Server管理-ROOT-Region。那么Client就去访问管理-ROOT-的Server,在META中记录了HBase中所有表信息,(你可以使用 scan '.META.' 命令列出你创建的所有表的详细信息),从而获取Region分布的信息。一旦Client获取了这一行的位置信息,比如这一行属于哪个Region,Client将会缓存这个信息并直接访问HRegionServer。久而久之Client缓存的信息渐渐增多,即使不访问.META.表也能知道去访问哪个HRegionServer。HBase中包含两种基本类型的文件,一种用于存储WAL的log,另一种用于存储具体的数据,这些数据都通过DFS Client和分布式的文件系统HDFS进行交互实现存储。
2. Client访问数据过程:
Client访问用户数据之前需要首先访问zookeeper,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过client端会做cache缓存。
-ROOT-表、.META都是存放在哪里??
client访问hbase上数据的过程并不需要master参与(寻址访问zookeeper和region server,数据读写访问region server),master仅仅维护者table和region的元数据信息,负载很低。
3. 在HBase上进行MapReduce操作:
4. HBase系统架构:
HBase Client使用HBase的RPC机制与HMaster和HRegionServer进行通信,对于管理类操作,Client与HMaster进行RPC;对于数据读写类操作,Client与HRegionServer进行RPC
5. Zookeeper:
Zookeeper简单说就是协调和服务于分布式应用程序的服务。
Zookeeper Quorum中除了存储了-ROOT-表的地址和HMaster的地址,HRegionServer也会把自己以Ephemeral方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的健康状态。此外,Zookeeper也避免了HMaster的单点问题。
1 保证任何时候,集群中只有一个master
2存贮所有Region的寻址入口。
3 实时监控RegionServer的状态,将Region server的上线和下线信息实时通知给Master
4 存储Hbase的schema,包括有哪些table,每个table有哪些column family
Zookeeper到底为我们干了什么?
1. 集中配置:可以APP1的配置配置到/APP1 znode下的所有机器。
2. 集群管理:同步:维护活机列表(让集群所有机器得到实时更新),
组服务:从集群中选择Master。
3. …..
参考: http://hi.baidu.com/surendaxiao/blog/item/cb1b42f86b03084e252df233.html
6. HMaster:
HMaster没有单点问题,HBase中可以启动多个HMaster,通过Zookeeper的MasterElection机制保证总有一个Master运行,HMaster在功能上主要负责Table和Region的管理工作:
1. 管理用户对Table的增、删、改、查操作
2. 管理HRegionServer的负载均衡,调整Region分布
3. 在Region Split后,负责新Region的分配
4. 在HRegionServer停机后,负责失效HRegionServer 上的Regions迁移
7. HRegionServer:
HRegionServer主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是HBase中最核心的模块。
HRegionServer内部管理了一系列HRegion对象,每个HRegion对应了Table中的一个Region,HRegion中由多个HStore组成。每个HStore对应了Table中的一个ColumnFamily的存储,可以看出每个Column Family其实就是一个集中的存储单元,因此最好将具备共同IO特性的column放在一个ColumnFamily中,这样最高效。
思考:
8. Hadoop+HBase+Zookeeper三者关系:
1.经过Map、Reduce运算后产生的结果看上去是被写入到HBase了,但是其实HBase中HLog和StoreFile中的文件在进行flush to disk操作时,这两个文件存储到了HDFS的DataNode中,HDFS才是永久存储。
2.ZooKeeper跟HadoopCore、HBase有什么关系呢?ZooKeeper都提供了哪些服务呢?主要有:管理Hadoop集群中的NameNode,HBase中HBaseMaster的选举,Servers之间状态同步等。具体一点,细一点说,单只HBase中ZooKeeper实例负责的工作就有:存储HBase的Schema,实时监控HRegionServer,存储所有Region的寻址入口,当然还有最常见的功能就是保证HBase集群中只有一个Master。
Hadoop、ZooKeeper和HBase之间应该按照顺序启动和关闭:启动Hadoop—>启动ZooKeeper集群—>启动HBase—>停止HBase—>停止ZooKeeper集群—>停止Hadoop。
五:理解Hadoop:
1. MapReduce
(1) MapReduce基础:
一个简单的MapReduce程序需要三样东西
1. 实现Mapper,处理输入的对,输出中间结果
2. 实现Reduce,对中间结果进行运算,输出最终结果
3. 在main方法里定义运行作业,定义一个job,在这里控制job如何运行等。
Mapper接口:是一个泛型,有4个形式的参数类型,分别指定map函数的输入键,输入值,输出键,输出值。
数据类型:Hadoop规定了自己的一套可用于网络序列优化的基本类型,而不是使用内置的java类型,这些都在org.apache.hadoop.io包中定义,上面使用的Text类型相当于java的String类型,IntWritable类型相当于java的Integer类型。
Maper 和Reducer 可以理解为分久必合,合久必分!
Maper是将任务切分成很多个小任务,分配给不同的工作者去完成
Reducer是将哪些工作者做完的工作结果收集起来加以整理汇总成最后结果。
总结:job的配置有着复杂的属性参数,如文件分割策略、排序策略、map输出内存缓冲区的大小、工作线程数量等,深入理解掌握这些参数才能使自己的MapReduce程序在集群环境中运行的最优。
(2) MapReduce基本编程:
Ø 创建一个Maven工程
Ø 加入hadoop依赖
Ø 编写Map类
Ø 编写reduce类
Ø 定义job
(3) 进行Mapreduce测试:
l 用MRUnit做单元测试:
Ø 加入mrunit依赖
Ø 单独测试Map
Ø 单独测试Reduce
Ø 测试MapReduce
参考:叶渡:Hadoop学习笔记_yedu.pdf
疑惑:1. 使用MRUnit,测试代码在.run下通过,在.runTest()失败,原因是什么?两者有什么区别?
l 运行MapReduce Job进行集成测试
流程:预设置(准备输入文件、启动hadoop进程等)->运行作业->输出结果跟预期结果的对比->报告导致失败的原因
l 精简HBaseMapReduce测试:
使用Hadoop/HBaseMini Cluster (iTest-hadoop)
参考文档: http://qa.taobao.com/?p=13939
(不安装Hadoop、HBase环境,只要有JDK搞定MapReduce的Job测试)
2. 本地搭建单机版hadoop环境(win):
Hadoop主要是在Linux 平台下运行的,如果想在 Windows 平台下运行,你需要安装 Cygwin 才能运行, Hadoop 脚本。
按照“在Windows上安装Hadoop教程.pdf“执行完成。
安装还可以参考文档: http://blog.csdn.net/savechina/article/details/5656937
按照“在Windows上安装Hadoop教程.pdf“的说明进行到最后一步时,在启动./start-all.sh之前,需要 格式化一个新的分布式文件,./hadoopnamenode –format .这样就会启动JobTracker.
浏览NameNode 和JobTracker 的网络接口,他们的地址默认为:
NameNode – http://localhost:50070/
JobTracker – http://localhost:50030/
Node数为0,如何配置NameNode 和Datanode??
Ø 运行hadoop自带jar文件:
运行hadoop自带的jar文件,理解MapReduce的过程:hadoop-0.20.2-examples.jar
跑通自己第一个Job程序:
首先开启hadoop服务: ./start-all.sh
1. 根据文档示例编写wordCout程序。
2. 将编写的代码打包成HadoopTest.jar放到本地某一个目录下, (打包的时候要选择mainclass)
或者直接运行hadoop自带文件中的示例jar包(hadoop-0.20.2-examples.jar)。
3. 将要分析的数据传到hdfs上去
在dfs上创建测试输入目录:./hadoop dfs –mkdir test-in
然后将本地文件copy到test-in中: ./hadoop dfs –copyFromLocal [本地文件目录] test-in
验证文件是否复制成功: ./hadoop dfs –ls test-in
注:这里的test-in其实是HDFS路径下的目录,七绝对路径为
“http://localhost:50070/user/XXXXX/test-in”
4. 开始执行
./bin/hadoopjar hadoop-0.20.2-examples.jar wordCount test-in test-out
当遇到文件已存在异常的时候,只要将test-out改一个名字即可。
5. 遇到问题:
抛出文件不存在的异常,原因是找不到tmp目录。开如图上的目录看到并不存在那样的目录结构,说明根本就没有创建相应的目录结构,可能是连tmp都没有找到,所以查找配置文件发现,conf下的mapred-site.xml中中默认配置是
./tmp,所以修改成自己的相应目录就可以了。
6. 运行成功:
Ø 运行自己编写的文件:
(1).逻辑性代码:
1. 编写自己的mapper函数:继承Mapper基类,实现map方法
2. 编写自己的reducer函数:继承reducer基类,实现reduce方法
3. 编写自己的主函数:创建job,配置map、combiner、reducer类型,设置输入输出路径, 设置输出键/值格式,提交任务
(2).驱动性代码:驱动类来注册业务的class为一个可标示的命令,让hadoop jar可以执行。
如:
(3). 最后一步:将自己的项目导出成jar格式,注意:在选择main class时,是选择我们创建的驱动类,而不是逻辑主类。
3. 一个Job的请求过程:
用户通过界面提交一个Job,服务器把Job请求发送给gateway,gateway接收请求后按照一定的逻辑拼装成MR需要的请求文件。
Gateway:我把可以把gateway理解为跳板机,我们的机器不能直接访问集群,需要一个入口,这个入口就是Gateway。
思考:这个跳板机是单独拿出来的一台机器专门做Job的入口的呢,还是只是机群中的普通机器?
JobTracker:
TaskTracker:
我们提交一个JOB(一般通过JobClient,这个类有三种策略来提交一个JOB,1、job完成后才返回状态2、job提交后,返回一个持有状态的Handler,3、提交job,但是不返回状态)
首先会从JobTracker(hadoop中运用了master/slaver机制,他是master服务,那么slaver在这里就是tasktracker)中的得到一个job的definition Id, 其实这个id也就是JobTracker管理job的个数
jobClient会从Configuration找到hadoop系统目录("mapred.system.dir",默认值"/tmp/hadoop/mapred/system")在这里jobClient做了一件比较重要的事情,他把input的数据做split操作(相当于将大数据量切分成若干块,具体切分成多大,这个通过一个公式来计算的:FileInputFormat的策略max(minimumSize,min(maximumSize, blockSize)) 其中minSize表示一个map切分的最小容量,maxSize即最大容量,blockSize表示HDFS中的block容量)[1],从而决定了Map的个数(其实就是MapTask的数量)。jobClient还将一部分资源文件放到jobtracker的FS中(jar、file、archives、split[2])
正式提交
JT(jobtracker)会根据这个job创建一个JobInProgress对象,这个对象记录着这个job所有信息。最后JT会将这个job注册到JobInProgressListener中(以下简称JIPL),让JIPL监听这些job。JIPL是在JT启动的时候启动的监听器(由TaskScheduler注入,作用参照step 7)。一个EagerTaskInitializationListener:它是一个生命周期和JT一样的监听线程,主要功能就是初始化这个Job,并且创建相应的TaskInProgress(TIP,包括M个MapTask,N个ReduceTask,2个CleanTask,2个SetupTask)。另一个就是JobQueueJobInProgressListener,这个listener是处理job队列的,也就是job提交的先后顺序跟它有关系,默认的是FIFO。
在初始化job的时候(其实是初始化MapTask),会将之前的input数据split的信息回流回来,初始化maptask
到这里,JT初始化job工作完毕。
每台slaver机器启动的时候,都会启动一个tasktracker的线程,这个线程主要负责和JT去通信,也就是发送心跳(通过RPC通信协议)。当发送心跳的时候,TT会将自己现有的状态(是否是刚刚启动、是否刚初始化,自己状态是否可以申请新的task,如果JT中没有这个TT的引用,那么需要保存下来)JT首先会获取Setup和CleanUp的Task(默认每个job都会有两个setuptask和两个cleanup task),如果没有了以上两种类型的task,那么剩下的就是MapTask和ReduceTask此时,JT会去向TaskScheduler这个调度类去申请Task。在hadoop中,默认的TaskScheduler是JobQueueTaskScheduler,他持有JPL的引用。当TT发送一个心跳表明自己空闲需要执行Task时,这时候,JT会调用Schedule的assignTask方法去获得一个Task。(这里Hadoop找MapTask的时候,首先node-local,然后rack-local,最后才是不同机架,具体怎么找,还未仔细看)
TT发送心跳后,JT返回给TT一个HeartbeatResponse对象的引用,这里面包含着需要执行Tasks的action数组(如果action的类型LaunchTaskAction:执行一个新的Task, 如果CommitTaskAction:加入commitResponses列表,由Task在适当的时候提交给JobTracker),同时JT还会更新TTS的内容。所以TT根据这两点,就可以很好的判断自己Task在JT那边的状态。 执行任务前先调用localizeTask()更新一下jobConf文件并写入到本地目录中。然后通过调用Task的createRunner()方法创建TaskRunner对象并调用其start()方法,值得注意点是,TaskRunner会去新开一个JVM去执行Task(如果考虑掉Task开销小而且多,可以将jvm reuse)。
两种启动Job方式:
A:Submit()
submit函数会把Job提交给对应的Cluster,然后不等待Job执行结束就立刻返回。同时会把Job实例的状态设置为JobState.RUNNING,从而来表示Job正在进行中。然后在Job运行过程中,可以调用getJobState()来获取Job的运行状态。
B:waitForCompletion(boolean)
waitForCompletion函数会提交Job到对应的Cluster,并等待Job执行结束。函数的boolean参数表示是否打印Job执行的相关信息。返回的结果是一个boolean变量,用来标识Job的执行结果。
执行Job的内部流程:
1).Inputformat会从job的INPUT_DIR目录下读入待处理的文件,检查输入的有效性并将文件切分成InputSplit列表。Job实例可以通过setInputFormatClass(Class<? extends InputFormat>)函数来设置所需的inputformat。
2).当Inputformat对输入文件分片后,会对每个分片构建一个MapperTask实例(MapTask(String, TaskAttemptID, int, TaskSplitIndex, int))。其实整个Mapper类的调度过程,都是由MapperTask来实现的。MapperTask的run(JobConf,TaskUmbilicalProtocol)方法实现了对于Mapper task调度的整个过程。
2.1) RecordReader会解析InputSplit,生成对应的key/value pair。Inputformat中有一个除了用于分片的getSplits(JobContext)方法外,还有一个方法createRecordReader(InputSplit,TaskAttemptContext),该方法用于给每一个分片创建一个RecordReader。重写这个方法,可以添加自己的RecordReader。
2.2)Mapper类会对属于一个InputSplit所有key/value pair调用一次map函数。关于Mapper类的作用,在Javadoc中描述如下:“Mapper maps input key/value pairs to a set ofintermediate key/value pairs”。 Job实例可以通过setMapperClass(Class<? extends Mapper>)函数来设置自己的Mapper类。
2.3)可以通过Job实例的setSortComparatorClass(Class<?extends RawComparator>)方法来为Mapper设定一个Comparator class,用来对Mapper的结果根据key进行排序。
2.4)可以通过Job实例的setPartitionerClass(Class<? extends Partitioner>)方法来为Mapper设定一个Partitioner Class,用来对Mapper的结果根据Reducer进行分片。
2.5)可以通过Job实例的setCombinerClass(Class<? extends Reducer>)方法为Mapper设定一个Combiner Class,用来在本地进行聚集操作,从而减少从Mapper到Reducer的数据传输量。
3).Mapper执行结束之后,ReducerTask类会被用来进行整个Reducer操作的调度
3.1)Shuffle类会被调用从而来获取在Mapper输出中属于本Reducer的分片,并将多个分片combine成一个。
3.2)Shuffle类会使用MergeManager根据Job实例的setSortComparatorClass(Class<?extends RawComparator>)所设定的Comparator class对key/value pair进行排序
3.3)在shuffle操作执行结束之后,如果对于Reducer的input数据,有使用特殊分组的需求的话,可以通过Job实例的setGroupingComparatorClass(Class<?extends RawComparator>)方法来实现定制的分组策略,否则,则使用setSortComparatorClass(Class<?extends RawComparator>)的比较方式。
3.4)在分组后的结果中,针对每一个<key, (list of values)> pair 调用Reduce的reduce(K2, Iterator<V2>, OutputCollector<K3, V3>,Reporter)方法。可以通过Job实例的setReducerClass(Class<?extends Reducer>)方法类设置相应的Reduce实现。
4).Reduce的结果将由OutputCollector.collect(WritableComparable, Writable)写入文件系统
参考文档: http://blog.csdn.net/derekjiang/article/details/6851625
思考:
3. 启动Hadoop过程:
(1) 启动NameNode:
启动NameNode节点;
初始化操作(如在name目录下创建文件);
记录HDFS状态(如安全模式状态);
本机FS注册,启动HDFS容器,并初始化;
(2) 启动DataNode:
(3) 启动SecondaryNameNode:
(4) 启动JobTracker:
(5) 启动TaskTracker:
4. 运行Map,Reduce过程:
六:HBase测试点:
前提:自己维护HBase集群,否则无需关注HBase本身。
1. 功能测试:
(1) Row Key的校验(重点):
rowkey的长度、rowkey的排序、rowkey是否有遗失
(2) Value的校验:
(3) Table schema:
TTL(生存周期):
压缩方式:Value值的压缩是否出错。
(4) Family名称正确性的校验:
(5) 破坏性校验:
由于HBase的数据都是在集群中有备份的,所以才去人工宕机,查看数据是否能够正常取出。
2. 性能测试:
(1) 对HBase性能测试的工具:YCSB
YCSB(Yahoo!Cloud Serving Benchmark)是雅虎开源的一款通用的性能测试工具。
通过这个工具我们可以对各类NoSQL产品进行相关的性能测试。
参考文档: http://www.cnblogs.com/gpcuster/archive/2011/08/16/2141430.html
参考: http://www.spnguru.com/ (趋势科技)
七:Hadoop测试点:
1. Job任务请求:
job需要解析一个request的请求文件,这里需要考虑到文件编码格式的问题。
2. MR数据处理:
(1) MR异常:
3. 程序的稳定和优化:
Hadoop测试参考:HADOOP测试常见问题和测试方法.docx
八:附
1. RPC通信协议:
RPC(RemoteProcedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
九:随想:
1. Hadoop 的分布式并行运算有一个作为主控的JobTracker,用于调度和管理其它的 TaskTracker, JobTracker 可以运行于集群中任一台计算机上。TaskTracker负责执行任务,必须运行于 DataNode 上,即 DataNode 既是数据存储结点,也是计算结点。
思考: JobTracker是如何从闲置的机器中选择出来的?是不是任何一台集群中的机器都可能有成为JobTracker的可能?所以机器都同事装了JobTracker和TaskTracker吗?
是谁在管理着JobTracker的分配和TaskTracker的运行?
2.
随笔记录:
1.Zookeeper中记录了-ROOT-表的location,我们的程序会通过我们配置的zookeeper地址找到zookeeper,然后根据zookeeper中存储的-ROOT-表的location,去到相应的机器上访问-ROOT-表,根据-ROOT-表中描述的.META表找到相应的Ration信息。
-ROOT-表只有一个区域,而.META可以有多个区域。
HBase在产品中还包含了Jetty,在HBase启动时采用嵌入式的方式来启动Jetty,因此可以通过web界面对HBase进行管理和查看当前运行的一些状态,非常轻巧。
简单来说,你在HBase中的表创建的可以看做是一张很大的表,而这个表的属性可以根据需求去动态增加,在HBase中没有表与表之间关联查询。
列存储
HStore存储是HBase存储的核心了,其中由两部分组成,一部分是MemStore,一部分是StoreFiles。MemStore是Sorted Memory Buffer,用户写入的数据首先会放入MemStore,当MemStore满了以后会Flush成一个StoreFile(底层实现是HFile),当StoreFile文件数量增长到一定阈值,会触发Compact合并操作,将多个StoreFiles合并成一个StoreFile,合并过程中会进行版本合并和数据删除,因此可以看出HBase其实只有增加数据,所有的更新和删除操作都是在后续的compact过程中进行的,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O的高性能。当StoreFilesCompact后,会逐步形成越来越大的StoreFile,当单个StoreFile大小超过一定阈值后,会触发Split操作,同时把当前Region Split成2个Region,父Region会下线,新Split出的2个孩子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Region的压力得以分流到2个Region上。
HFile里面的每个KeyValue对就是一个简单的byte 数组 。
HRegion是Hbase中分布式存储和负载均衡的最小单元。最小单元就表示不同的Hregion可以分布在不同的HRegion server上。但一个Hregion是不会拆分到多个server上的。
HFile格式:
一、HBase基本概念: 2个主要概念:
a) Rowkey: Hbase 中的记录是按照rowkey来排序的;
b) Column family:(列族)是在系统启动之前预先定义好的;
c) Hbase优缺点:
1.不支持条件查询以及orderby等查询;
2.列可以动态增加,列为空则不存储数据,节省存储空间;
3.会自动切分数据;4.可以提供高并发读写操作的支持;
访问方式: 访问hbasetable中的行,只有三种方式:
1 通过单个row key访问
2 通过row key的range
3 全表扫描
Row key:行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。
存储: HBase以表的形式存储数据。表有行和列组成,存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)
存储类型: TableName 是字符串
RowKey 和 ColumnName 是二进制值(Java 类型 byte[])
Timestamp 是一个 64 位整数(Java 类型 long)
value 是一个字节数组(Java类型 byte[])
存储结构:即HTable按Row key自动排序,每个Row包含任意数量个Columns,Columns之间按Columnkey自动排序,每个Column包含任意数量个Values。理解该存储结构将有助于查询结果的迭代。
(RowKey,List(SortedMap(column,List(value,TimeStamp))))
列簇:hbase表中的每个列,都归属与某个列族。列名都以列族作为前缀。
HBase中的列可以动态新增。
存储单元:HBase中通过row和columns确定的为一个存贮单元称为cell。
每个cell都保存着同一份数据的多个版本。版本通过时间戳来索引。
HBase为null的Column不会被存储,这样既节省了空间又提高了读性能
cell中的数据是没有类型的,全部是字节码形式存贮
两种数据版本回收方式:一是保存数据的最后n个版本
二是保存最近一段时间内的版本(比如最近七天)
用户可以针对每个列族进行设置值value:每个值由4个键唯一索引
tableName+RowKey+ColumnKey+Timestamp=>value
二、搭建HBase环境:
http://hbase.apache.org/book/quickstart.html 和 http://hbase.apache.org/book/notsoquick.html 。 如果你在windows环境下配置cygwin及ssh遇到问题可以参考 http://qa.taobao.com/?p=10633
1. 创建一个Maven工程。
mvn archetype:generate-DgroupId=com.alibaba.webx -DartifactId=tutorial1 -Dversion=1.0-SNAPSHOT -Dpackage=com.alibaba.webx.tutorial1-DarchetypeArtifactId=archetype-webx-quickstart-DarchetypeGroupId=com.alibaba.citrus.sample-DarchetypeVersion=1.0-SNAPSHOT -DinteractiveMode=false cmd进入刚才建立的项目,运行:mvn jetty:run 在浏览器中打开:localhost:8081就可以看到我们新建的webx项目了。具体里面是怎么运行的,可以查看webx用户手册。
2. 加入Hadoop、HBase依赖:
3. 在src/test/resources目录下新建文件conf/hbase-site.xml
文件具体配置为:
配置你工程要使用的zookeeper客户端端口号和zookeeper的地址,这个地址可以向开发索要。
4. 写测试文件:
如果不报错,表示链接已经通过,接下来就可以创建表以及对表的增删改查了。
三、基础知识:
1. 通过HBase shell 与HBase交互:
进入控制台:bin/hbase shell
创建表:create ‘表名’,’列簇名’,’列簇名’
增加记录:put ‘表名’,’Row Key’,’列簇名:列名’,’value’
查询:get ‘表名’,’Row Key’
删除:delete‘表名’,’Row Key’,’列簇名:列名’ (只能删除一列)
delete‘表名’,’Row Key’ (删除RowKey的所有列)
删除表:>disable ‘表名’
>drop ‘表名’
2. 通过Java 的API与HBase交互:
步骤一:
创建一个Maven工程加入依赖:
如果你的Maven库里还没有hbase,还需要配置下repository:
步骤二:
确保HBase环境已启动且能连接到,将HBase环境的hbase-site.xml文件拷贝到上述工程的src/test/resources目录
加载配置->创建表->增加记录->根据RowKey查询->遍历查询与迭代->删除记录->删除表
具体操作可以参考: http://qa.taobao.com/?p=13894
http://www.cnblogs.com/panfeng412/archive/2011/08/14/2137984.html
四、深入理解HBase:
思考:HBase服务器内部由那些主要部件构成?
HBase的内部工作原理是什么?
1. HBase的工作原理:
首先HBase Client端会连接Zookeeper Qurom(从下面的代码也能看出来,例如:HBASE_CONFIG.set("hbase.zookeeper.quorum","192.168.50.216") )。通过Zookeeper组件Client能获知哪个Server管理-ROOT-Region。那么Client就去访问管理-ROOT-的Server,在META中记录了HBase中所有表信息,(你可以使用 scan '.META.' 命令列出你创建的所有表的详细信息),从而获取Region分布的信息。一旦Client获取了这一行的位置信息,比如这一行属于哪个Region,Client将会缓存这个信息并直接访问HRegionServer。久而久之Client缓存的信息渐渐增多,即使不访问.META.表也能知道去访问哪个HRegionServer。HBase中包含两种基本类型的文件,一种用于存储WAL的log,另一种用于存储具体的数据,这些数据都通过DFS Client和分布式的文件系统HDFS进行交互实现存储。
2. Client访问数据过程:
Client访问用户数据之前需要首先访问zookeeper,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过client端会做cache缓存。
-ROOT-表、.META都是存放在哪里??
client访问hbase上数据的过程并不需要master参与(寻址访问zookeeper和region server,数据读写访问region server),master仅仅维护者table和region的元数据信息,负载很低。
3. 在HBase上进行MapReduce操作:
4. HBase系统架构:
HBase Client使用HBase的RPC机制与HMaster和HRegionServer进行通信,对于管理类操作,Client与HMaster进行RPC;对于数据读写类操作,Client与HRegionServer进行RPC
5. Zookeeper:
Zookeeper简单说就是协调和服务于分布式应用程序的服务。
Zookeeper Quorum中除了存储了-ROOT-表的地址和HMaster的地址,HRegionServer也会把自己以Ephemeral方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的健康状态。此外,Zookeeper也避免了HMaster的单点问题。
1 保证任何时候,集群中只有一个master
2存贮所有Region的寻址入口。
3 实时监控RegionServer的状态,将Region server的上线和下线信息实时通知给Master
4 存储Hbase的schema,包括有哪些table,每个table有哪些column family
Zookeeper到底为我们干了什么?
1. 集中配置:可以APP1的配置配置到/APP1 znode下的所有机器。
2. 集群管理:同步:维护活机列表(让集群所有机器得到实时更新),
组服务:从集群中选择Master。
3. …..
参考: http://hi.baidu.com/surendaxiao/blog/item/cb1b42f86b03084e252df233.html
6. HMaster:
HMaster没有单点问题,HBase中可以启动多个HMaster,通过Zookeeper的MasterElection机制保证总有一个Master运行,HMaster在功能上主要负责Table和Region的管理工作:
1. 管理用户对Table的增、删、改、查操作
2. 管理HRegionServer的负载均衡,调整Region分布
3. 在Region Split后,负责新Region的分配
4. 在HRegionServer停机后,负责失效HRegionServer 上的Regions迁移
7. HRegionServer:
HRegionServer主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是HBase中最核心的模块。
HRegionServer内部管理了一系列HRegion对象,每个HRegion对应了Table中的一个Region,HRegion中由多个HStore组成。每个HStore对应了Table中的一个ColumnFamily的存储,可以看出每个Column Family其实就是一个集中的存储单元,因此最好将具备共同IO特性的column放在一个ColumnFamily中,这样最高效。
思考:
8. Hadoop+HBase+Zookeeper三者关系:
1.经过Map、Reduce运算后产生的结果看上去是被写入到HBase了,但是其实HBase中HLog和StoreFile中的文件在进行flush to disk操作时,这两个文件存储到了HDFS的DataNode中,HDFS才是永久存储。
2.ZooKeeper跟HadoopCore、HBase有什么关系呢?ZooKeeper都提供了哪些服务呢?主要有:管理Hadoop集群中的NameNode,HBase中HBaseMaster的选举,Servers之间状态同步等。具体一点,细一点说,单只HBase中ZooKeeper实例负责的工作就有:存储HBase的Schema,实时监控HRegionServer,存储所有Region的寻址入口,当然还有最常见的功能就是保证HBase集群中只有一个Master。
Hadoop、ZooKeeper和HBase之间应该按照顺序启动和关闭:启动Hadoop—>启动ZooKeeper集群—>启动HBase—>停止HBase—>停止ZooKeeper集群—>停止Hadoop。
五:理解Hadoop:
1. MapReduce
(1) MapReduce基础:
一个简单的MapReduce程序需要三样东西
1. 实现Mapper,处理输入的对,输出中间结果
2. 实现Reduce,对中间结果进行运算,输出最终结果
3. 在main方法里定义运行作业,定义一个job,在这里控制job如何运行等。
Mapper接口:是一个泛型,有4个形式的参数类型,分别指定map函数的输入键,输入值,输出键,输出值。
数据类型:Hadoop规定了自己的一套可用于网络序列优化的基本类型,而不是使用内置的java类型,这些都在org.apache.hadoop.io包中定义,上面使用的Text类型相当于java的String类型,IntWritable类型相当于java的Integer类型。
Maper 和Reducer 可以理解为分久必合,合久必分!
Maper是将任务切分成很多个小任务,分配给不同的工作者去完成
Reducer是将哪些工作者做完的工作结果收集起来加以整理汇总成最后结果。
总结:job的配置有着复杂的属性参数,如文件分割策略、排序策略、map输出内存缓冲区的大小、工作线程数量等,深入理解掌握这些参数才能使自己的MapReduce程序在集群环境中运行的最优。
(2)深入理解MapReduce:
(1)在map进行之前,需要对输入文件在客户端先进行“分片”,然后将分片信息上传到HDFS。
(2)分片上传结束后,jobtracker拿到分片信息,来分配map,reduct task;map对每条记录的输出以<key,value> 的形式输出。
(3)如果定义了combiner,则在本地会对map处理的结果进行处理:对相同key的聚合,对key的排序,value的迭代。combiner完成类似于本地reduce的功能。
(4)在进入reduce阶段之前,系统会完成一些列操作(merge,sort):将list中key相同的数据进行合并、排序,最后形成<k1`,list<v1`>>的数据;
然后发往一个reduce
(5)进入一个reduce,相同的key的map输出会到达同一个reduce,reduce对key相同的多个value进行“reduce操作”;
> 没有combiner的处理过程:
> 添加combiner的处理过程:
?为什么我买的map函数和reduce函数一般使用静态类?
答:task内部可以共享静态类属性,每个task可能会多次调用map或reduce函数,但每个key只对应某个节点上的某个task的reduce函数的一次执行。
多个task之间不能共享静态类属性,即使是在同一台机器上,因为是以进程的方式在运行。
1. Map类:(继承TableMapper或者Mapper)
Map原理:
在map阶段,使用job.setInputFormatClass定义的InputFormat将输入的数据集分割成小数据块splites,同时InputFormat提供一个RecordReder的实现。本例子中使用的是 TextInputFormat,他提供的RecordReder会将文本的一行的行号作为key,这一行的文本作为value。这就是自定义Map的输入是<LongWritable,Text>的原因。然后调用自定义Map的map方法,将一个个<LongWritable, Text>对输入给Map的map方法。注意输出应该符合自定义Map中定义的输出<IntPair, IntWritable>。最终是生成一个List<IntPair,IntWritable>。在map阶段的最后,会先调用job.setPartitionerClass对这个List进行分区,每个分区映射到一个reducer。每个分区内又调用job.setSortComparatorClass设置的key比较函数类排序。可以看到,这本身就是一个二次排序。如果没有通过job.setSortComparatorClass设置key比较函数类,则使用key的实现的compareTo方法。在第一个例子中,使用了IntPair实现的compareTo方法,而在下一个例子中,专门定义了key比较函数类。
Q: map的结果发给那个reduce?谁来管理这一切?
A: Partitioner用于划分键值空间(key space)。
Partitioner负责控制map输出结果key的分割。Key(或者一个key子集)被用 于产生分区,通常使用的是Hash函数。分区的数目与一个作业的reduce任务的数目是一样的。因此,它控制将中间过程的key(也就是这条记录)应该发送给m个reduce任务中的哪一个来进行reduce操作。
2. Reduce类:(继承TableReducer或者Reducer)
Reduce的原理:在reduce阶段,reducer接收到所有映射到这个reducer的map输出后,也是会调用job.setSortComparatorClass设置的key比较函数类对所有数据对排序。然后开始构造一个key对应的value迭代器。这时就要用到分组,使用jobjob.setGroupingComparatorClass设置的分组函数类。只要这个比较器比较的两个key相同,他们就属于同一个组,它们的value放在一个value迭代器,而这个迭代器的key使用属于同一个组的所有key的第一个key。最后就是进入Reducer的reduce方法,reduce方法的输入是所有的(key和它的value迭代器)。同样注意输入与输出的类型必须与自定义的Reducer中声明的一致。
reduce的输出是没有排序的。
Q: Reduce的数目应该设置多少?
A:Reduce的数目建议是0.95或1.75乘以 ( *mapred.tasktracker.reduce.tasks.maximum)。用0.95,所有reduce可以在maps一完成时就立刻启动,开始传输map的输出结 果。用1.75,速度快的节点可以在完成第一轮reduce任务后,可以开始第二轮,这样可以得到比较好的负载均衡的效果。上述比例因子比整体数目稍小一些是为了给框 架中的推测性任务(speculative-tasks) 或失败的任务预留一些reduce的资源。
Q: Reduce的三个阶段都干了什么?
A: Reducer有3个主要阶段:shuffle、sort和reduce。
Shuffle :Reducer的输入就是Mapper已经排好序的输出。在这个阶段,框架通过HTTP为每个Reducer获得所有Mapper输出中与之相关的分块。(其实就是copy的过程)
Sort :这个阶段,框架将按照key的值对Reducer的输入进行分组 (因为不同mapper的输出中可能会有相同的key,combain保证了同一台机器相同key的合并,但是不同机器也可能有相同的key)。
map的输出是一边被取回一边被合并的。
3. Job 的配置:
(1). 使用job.setInputFormatClass定义的InputFormat将输入的数据集分割成小数据块splites,HadoopMap/Reduce框架为每一个Split产生一个map任务.
Map的数目通常是由输入数据的大小决定的,一般就是所有输入文件的总块(block)数。如果你输入10TB的数据,每个块(block)的大小是 128MB,你将需要大约82,000个map来完成任务,除非使用setNumMapTasks(int)将这个数值设置得更高。
(2).如果需要中间过程对key的分组规则和reduce前对key的分组规则不同,那么可以通过 JobConf.setOutputValueGroupingComparator(Class)来指定一个Comparator。再加上 JobConf.setOutputKeyComparatorClass(Class)可用于控制中间过程的key如何被分组,所以结合两者可以实现按值的二次排序
(3).一些作业的参数可以被直截了当地进行设置(例如: setNumReduceTasks(int)),而另一些参数则与框架或者作业的其他参数之间微妙地相互影响,并且设置起来比较复杂(例如: setNumMapTasks(int))
(4).Mapper和Reducer的实现可以利用Reporter 来报告进度,或者仅是表明自己运行正常。我们从界面上看到的图形就是利用Reporter来进行进度的展示。
(2) MapReduce基本编程:
Ø 创建一个Maven工程
Ø 加入hadoop依赖
Ø 编写Map类
Ø 编写reduce类
Ø 定义job
(3) 进行Mapreduce测试:
l 用MRUnit做单元测试:
Ø 加入mrunit依赖
Ø 单独测试Map
Ø 单独测试Reduce
Ø 测试MapReduce
参考:叶渡:Hadoop学习笔记_yedu.pdf
疑惑:1. 使用MRUnit,测试代码在.run下通过,在.runTest()失败,原因是什么?两者有什么区别?
l 运行MapReduce Job进行集成测试
流程:预设置(准备输入文件、启动hadoop进程等)->运行作业->输出结果跟预期结果的对比->报告导致失败的原因
l 精简HBaseMapReduce测试:
使用Hadoop/HBaseMini Cluster (iTest-hadoop)
参考文档: http://qa.taobao.com/?p=13939
(不安装Hadoop、HBase环境,只要有JDK搞定MapReduce的Job测试)
2. 本地搭建单机版hadoop环境(win):
Hadoop主要是在Linux 平台下运行的,如果想在 Windows 平台下运行,你需要安装 Cygwin 才能运行, Hadoop 脚本。
按照“在Windows上安装Hadoop教程.pdf“执行完成。
安装还可以参考文档: http://blog.csdn.net/savechina/article/details/5656937
按照“在Windows上安装Hadoop教程.pdf“的说明进行到最后一步时,在启动./start-all.sh之前,需要 格式化一个新的分布式文件,./hadoopnamenode –format .这样就会启动JobTracker.
浏览NameNode 和JobTracker 的网络接口,他们的地址默认为:
NameNode – http://localhost:50070/
JobTracker – http://localhost:50030/
Node数为0,如何配置NameNode 和Datanode??
Ø 运行hadoop自带jar文件:
运行hadoop自带的jar文件,理解MapReduce的过程:hadoop-0.20.2-examples.jar
跑通自己第一个Job程序:
首先开启hadoop服务: ./start-all.sh
1. 根据文档示例编写wordCout程序。
2. 将编写的代码打包成HadoopTest.jar放到本地某一个目录下, (打包的时候要选择mainclass)
或者直接运行hadoop自带文件中的示例jar包(hadoop-0.20.2-examples.jar)。
3. 将要分析的数据传到hdfs上去
在dfs上创建测试输入目录:./hadoop dfs –mkdir test-in
然后将本地文件copy到test-in中: ./hadoop dfs –copyFromLocal [本地文件目录] test-in
验证文件是否复制成功: ./hadoop dfs –ls test-in
注:这里的test-in其实是HDFS路径下的目录,七绝对路径为
“http://localhost:50070/user/XXXXX/test-in”
4. 开始执行
./bin/hadoopjar hadoop-0.20.2-examples.jar wordCount test-in test-out
当遇到文件已存在异常的时候,只要将test-out改一个名字即可。
5. 遇到问题:
抛出文件不存在的异常,原因是找不到tmp目录。开如图上的目录看到并不存在那样的目录结构,说明根本就没有创建相应的目录结构,可能是连tmp都没有找到,所以查找配置文件发现,conf下的mapred-site.xml中中默认配置是
./tmp,所以修改成自己的相应目录就可以了。
6. 运行成功:
Ø 运行自己编写的文件:
(1).逻辑性代码:
1. 编写自己的mapper函数:继承Mapper基类,实现map方法
2. 编写自己的reducer函数:继承reducer基类,实现reduce方法
3. 编写自己的主函数:创建job,配置map、combiner、reducer类型,设置输入输出路径, 设置输出键/值格式,提交任务
(2).驱动性代码:驱动类来注册业务的class为一个可标示的命令,让hadoop jar可以执行。
如:
(3). 最后一步:将自己的项目导出成jar格式,注意:在选择main class时,是选择我们创建的驱动类,而不是逻辑主类。
3. 一个Job的请求过程:
用户通过界面提交一个Job,服务器把Job请求发送给gateway,gateway接收请求后按照一定的逻辑拼装成MR需要的请求文件。
Gateway:我把可以把gateway理解为跳板机,我们的机器不能直接访问集群,需要一个入口,这个入口就是Gateway。
思考:这个跳板机是单独拿出来的一台机器专门做Job的入口的呢,还是只是机群中的普通机器?
JobTracker:
TaskTracker:
我们提交一个JOB(一般通过JobClient,这个类有三种策略来提交一个JOB,1、job完成后才返回状态2、job提交后,返回一个持有状态的Handler,3、提交job,但是不返回状态)
首先会从JobTracker(hadoop中运用了master/slaver机制,他是master服务,那么slaver在这里就是tasktracker)中的得到一个job的definition Id, 其实这个id也就是JobTracker管理job的个数
jobClient会从Configuration找到hadoop系统目录("mapred.system.dir",默认值"/tmp/hadoop/mapred/system")在这里jobClient做了一件比较重要的事情,他把input的数据做split操作(相当于将大数据量切分成若干块,具体切分成多大,这个通过一个公式来计算的:FileInputFormat的策略max(minimumSize,min(maximumSize, blockSize)) 其中minSize表示一个map切分的最小容量,maxSize即最大容量,blockSize表示HDFS中的block容量)[1],从而决定了Map的个数(其实就是MapTask的数量)。jobClient还将一部分资源文件放到jobtracker的FS中(jar、file、archives、split[2])
正式提交
JT(jobtracker)会根据这个job创建一个JobInProgress对象,这个对象记录着这个job所有信息。最后JT会将这个job注册到JobInProgressListener中(以下简称JIPL),让JIPL监听这些job。JIPL是在JT启动的时候启动的监听器(由TaskScheduler注入,作用参照step 7)。一个EagerTaskInitializationListener:它是一个生命周期和JT一样的监听线程,主要功能就是初始化这个Job,并且创建相应的TaskInProgress(TIP,包括M个MapTask,N个ReduceTask,2个CleanTask,2个SetupTask)。另一个就是JobQueueJobInProgressListener,这个listener是处理job队列的,也就是job提交的先后顺序跟它有关系,默认的是FIFO。
在初始化job的时候(其实是初始化MapTask),会将之前的input数据split的信息回流回来,初始化maptask
到这里,JT初始化job工作完毕。
每台slaver机器启动的时候,都会启动一个tasktracker的线程,这个线程主要负责和JT去通信,也就是发送心跳(通过RPC通信协议)。当发送心跳的时候,TT会将自己现有的状态(是否是刚刚启动、是否刚初始化,自己状态是否可以申请新的task,如果JT中没有这个TT的引用,那么需要保存下来)JT首先会获取Setup和CleanUp的Task(默认每个job都会有两个setuptask和两个cleanup task),如果没有了以上两种类型的task,那么剩下的就是MapTask和ReduceTask此时,JT会去向TaskScheduler这个调度类去申请Task。在hadoop中,默认的TaskScheduler是JobQueueTaskScheduler,他持有JPL的引用。当TT发送一个心跳表明自己空闲需要执行Task时,这时候,JT会调用Schedule的assignTask方法去获得一个Task。(这里Hadoop找MapTask的时候,首先node-local,然后rack-local,最后才是不同机架,具体怎么找,还未仔细看)
TT发送心跳后,JT返回给TT一个HeartbeatResponse对象的引用,这里面包含着需要执行Tasks的action数组(如果action的类型LaunchTaskAction:执行一个新的Task, 如果CommitTaskAction:加入commitResponses列表,由Task在适当的时候提交给JobTracker),同时JT还会更新TTS的内容。所以TT根据这两点,就可以很好的判断自己Task在JT那边的状态。 执行任务前先调用localizeTask()更新一下jobConf文件并写入到本地目录中。然后通过调用Task的createRunner()方法创建TaskRunner对象并调用其start()方法,值得注意点是,TaskRunner会去新开一个JVM去执行Task(如果考虑掉Task开销小而且多,可以将jvm reuse)。
两种启动Job方式:
A:Submit()
submit函数会把Job提交给对应的Cluster,然后不等待Job执行结束就立刻返回。同时会把Job实例的状态设置为JobState.RUNNING,从而来表示Job正在进行中。然后在Job运行过程中,可以调用getJobState()来获取Job的运行状态。
B:waitForCompletion(boolean)
waitForCompletion函数会提交Job到对应的Cluster,并等待Job执行结束。函数的boolean参数表示是否打印Job执行的相关信息。返回的结果是一个boolean变量,用来标识Job的执行结果。
执行Job的内部流程:
1).Inputformat会从job的INPUT_DIR目录下读入待处理的文件,检查输入的有效性并将文件切分成InputSplit列表。Job实例可以通过setInputFormatClass(Class<? extends InputFormat>)函数来设置所需的inputformat。
2).当Inputformat对输入文件分片后,会对每个分片构建一个MapperTask实例(MapTask(String, TaskAttemptID, int, TaskSplitIndex, int))。其实整个Mapper类的调度过程,都是由MapperTask来实现的。MapperTask的run(JobConf,TaskUmbilicalProtocol)方法实现了对于Mapper task调度的整个过程。
2.1) RecordReader会解析InputSplit,生成对应的key/value pair。Inputformat中有一个除了用于分片的getSplits(JobContext)方法外,还有一个方法createRecordReader(InputSplit,TaskAttemptContext),该方法用于给每一个分片创建一个RecordReader。重写这个方法,可以添加自己的RecordReader。
2.2)Mapper类会对属于一个InputSplit所有key/value pair调用一次map函数。关于Mapper类的作用,在Javadoc中描述如下:“Mapper maps input key/value pairs to a set ofintermediate key/value pairs”。 Job实例可以通过setMapperClass(Class<? extends Mapper>)函数来设置自己的Mapper类。
2.3)可以通过Job实例的setSortComparatorClass(Class<?extends RawComparator>)方法来为Mapper设定一个Comparator class,用来对Mapper的结果根据key进行排序。
2.4)可以通过Job实例的setPartitionerClass(Class<? extends Partitioner>)方法来为Mapper设定一个Partitioner Class,用来对Mapper的结果根据Reducer进行分片。
2.5)可以通过Job实例的setCombinerClass(Class<? extends Reducer>)方法为Mapper设定一个Combiner Class,用来在本地进行聚集操作,从而减少从Mapper到Reducer的数据传输量。
3).Mapper执行结束之后,ReducerTask类会被用来进行整个Reducer操作的调度
3.1)Shuffle类会被调用从而来获取在Mapper输出中属于本Reducer的分片,并将多个分片combine成一个。
3.2)Shuffle类会使用MergeManager根据Job实例的setSortComparatorClass(Class<?extends RawComparator>)所设定的Comparator class对key/value pair进行排序
3.3)在shuffle操作执行结束之后,如果对于Reducer的input数据,有使用特殊分组的需求的话,可以通过Job实例的setGroupingComparatorClass(Class<?extends RawComparator>)方法来实现定制的分组策略,否则,则使用setSortComparatorClass(Class<?extends RawComparator>)的比较方式。
3.4)在分组后的结果中,针对每一个<key, (list of values)> pair 调用Reduce的reduce(K2, Iterator<V2>, OutputCollector<K3, V3>,Reporter)方法。可以通过Job实例的setReducerClass(Class<?extends Reducer>)方法类设置相应的Reduce实现。
4).Reduce的结果将由OutputCollector.collect(WritableComparable, Writable)写入文件系统
参考文档: http://blog.csdn.net/derekjiang/article/details/6851625
思考:
3. 启动Hadoop过程:
(1) 启动NameNode:
启动NameNode节点;
初始化操作(如在name目录下创建文件);
记录HDFS状态(如安全模式状态);
本机FS注册,启动HDFS容器,并初始化;
(2) 启动DataNode:
(3) 启动SecondaryNameNode:
(4) 启动JobTracker:
(5) 启动TaskTracker:
4. 运行Map,Reduce过程:
1. 在分布式环境中客户端创建任务并提交。
2. InputFormat做Map前的预处理,主要负责以下工作:
1. 验证输入的格式是否符合JobConfig的输入定义,这个在实现Map和构建Conf的时候就会知道,不定义可以是Writable的任意子类。
2. 将input的文件切分为逻辑上的输入InputSplit,其实这就是在上面提到的在分布式文件系统中blocksize是有大小限制的,因此大文件会被划分为多个block。
3. 通过RecordReader来再次处理inputsplit为一组records,输出给Map。(inputsplit只是逻辑切分的第一步,但是如何根据文件中的信息来切分还需要RecordReader来实现,例如最简单的默认方式就是回车换行的切分)
3. RecordReader处理后的结果作为Map的输入,Map执行定义的Map逻辑,输出处理后的key和value对应到临时中间文件。
4. Combiner可选择配置,主要作用是在每一个Map执行完分析以后,在本地优先作Reduce的工作,减少在Reduce过程中的数据传输量。
5. Partitioner可选择配置,主要作用是在多个Reduce的情况下,指定Map的结果由某一个Reduce处理,每一个Reduce都会有单独的输出文件。(后面的代码实例中有介绍使用场景)
6. Reduce执行具体的业务逻辑,并且将处理结果输出给OutputFormat。
7. OutputFormat的职责是,验证输出目录是否已经存在,同时验证输出结果类型是否如Config中配置,最后输出Reduce汇总后的结果。
5. MapReduce 中如何处理HBase中的数据?如何读取HBase数据给Map?如何将结果存储到HBase中?
Mapper类:包括一个内部类(Context)和四个方法(setup,map,cleanup,run);
setup,cleanup用于管理Mapper生命周期中的资源。setup -> map -> cleanup,
run方法执行了这个过程;
map方法用于对一次输入的key/value对进行map动作,对应HBase操作也就是一行的处理;
job的配置:
5.1TableInputFormat完成了什么功能?
(1)通过设置conf.set(TableInputFormat.INPUT_TABLE,"udc_sell");设定HBase的输入表;
设置conf.set(TableInputFormat.SCAN,TableMRUtil.convertScanToString(scan));设定对HBase输入表的scan方式;
(2)通过TableInputFormat.setConf(Configration conf)方法初始化scan对象;
scan对象是从job中设置的对象,以字符串的形式传给TableInputFormat,在TableInputFormat内部将scan字符创转换为scan对象
* TableMapReduceUtily有两个方法:convertScanToString和convertStringToScan作用?
将scan实例转换为Base64字符串 和将Base64字符串还原为scan实例;
Q:为什么不直接穿Scan对象而是费尽周折地转换来转换去呢?
A:
(3)TableInputFormat继承了TableInputFormatBase实现了InputFormat抽象类的两个抽象方法:
getSplits()和createRecordReader()方法:
l getSplits()断定输入对象的切分原则:对于TableInputFormatBase,会遍历HBase相应表的所有HRegion,每一个HRegion都会被分成一个split,所以切分的块数是与表中HRegion的数目是相同的; InputSplitsplit =newTableSplit(table.getTableName(),splitStart, splitStop, regionLocation);在split中只会记载HRegion的其实rowkey和终止rowkey,具体的去读取这篇区域的数据是createRecordReader()实现的。
计算出来的每一个分块都将被作为一个map Task的输入;
Q:但是分出的块分给那台机器的那个task去执行Map,即jobTracker如何调度任务给taskTracker?
A: 需要进一步了解Map的本地化运行机制和jobTracker的调度算法;(可能是就近原则).
对于一个map任务,jobtracker会考虑tasktracker的网络位置,并选取一个距离其输入分片文件最近的tasktracker。在最理想的情况下,任务是数据本地化的(data-local),也就是任务运行在输入分片所在的节点上。同样,任务也可能是机器本地化的:任务和输入分片在同一个机架,但不在同一个节点上。reduce任务,jobtracker简单滴从待运行的reduce任务列表中选取下一个来运行,用不着考虑数据段饿本地化。
l createRecordReader()按照必然格式读取响应数据:
接收split块,返回读取记录的结果;
public RecordReader<ImmutableBytesWritable,Result> createRecordReader(InputSplit split, TaskAttemptContext context){
}
trr.init()返回的是这个分块的起始rowkey的记录;
RecordReader将一个split解析成<key,value>对的形式提供给map函数,key就是rowkey,value就是对应的一行数据;
RecordReader用于在划分中读取<Key,Value>对。RecordReader有五个虚方法,分别是: initialize:初始化,输入参数包括该Reader工作的数据划分InputSplit和Job的上下文context;nextKey:得到输入的下一个Key,如果数据划分已经没有新的记录,返回空; nextValue:得到Key对应的Value,必须在调用nextKey后调用;getProgress:得到现在的进度; close:来自java.io的Closeable接口,用于清理RecordReader。
5.2 job.setInputFormatClass(TableInputFormat.class);
5.3 TableMapReduceUtil.initTableReducerJob("daily_result",DailyReduce.class, job);
使用了该方法就不需要再单独定义
initTableReducerJob()方法完成了一系列操作:
(1). job.setOutputFormatClass(TableOutputFormat.class); 设置输出格式;
(2). conf.set(TableOutputFormat.OUTPUT_TABLE, table);设置输出表;
(3).初始化partition;
六:HBase测试点:
前提:自己维护HBase集群,否则无需关注HBase本身。
1. 功能测试:
(1) Row Key的校验(重点):
rowkey的长度、rowkey的排序、rowkey是否有遗失
(2) Value的校验:
(3) Table schema:
TTL(生存周期):
压缩方式:Value值的压缩是否出错。
(4) Family名称正确性的校验:
(5) 破坏性校验:
由于HBase的数据都是在集群中有备份的,所以才去人工宕机,查看数据是否能够正常取出。
2. 性能测试:
(1) 对HBase性能测试的工具:YCSB
YCSB(Yahoo!Cloud Serving Benchmark)是雅虎开源的一款通用的性能测试工具。
通过这个工具我们可以对各类NoSQL产品进行相关的性能测试。
参考文档: http://www.cnblogs.com/gpcuster/archive/2011/08/16/2141430.html
参考: http://www.spnguru.com/ (趋势科技)
七:Hadoop测试点:
1. Job任务请求:
job需要解析一个request的请求文件,这里需要考虑到文件编码格式的问题。
2. MR数据处理:
(1) MR异常:
3. 程序的稳定和优化:
Hadoop测试参考:HADOOP测试常见问题和测试方法.docx
八:附
1. RPC通信协议:
RPC(RemoteProcedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
九:随想:
1. Hadoop 的分布式并行运算有一个作为主控的JobTracker,用于调度和管理其它的 TaskTracker, JobTracker 可以运行于集群中任一台计算机上。TaskTracker负责执行任务,必须运行于 DataNode 上,即 DataNode 既是数据存储结点,也是计算结点。
思考: JobTracker是如何从闲置的机器中选择出来的?是不是任何一台集群中的机器都可能有成为JobTracker的可能?所以机器都同事装了JobTracker和TaskTracker吗?
是谁在管理着JobTracker的分配和TaskTracker的运行?
2.
随笔记录:
1.Zookeeper中记录了-ROOT-表的location,我们的程序会通过我们配置的zookeeper地址找到zookeeper,然后根据zookeeper中存储的-ROOT-表的location,去到相应的机器上访问-ROOT-表,根据-ROOT-表中描述的.META表找到相应的Ration信息。
-ROOT-表只有一个区域,而.META可以有多个区域。