关于网上HDFS和Hbase部分知识点
1、HDFS的设计
HDFS是什么:HDFS即Hadoop分布式文件系统(Hadoop Distributed Filesystem),以流式数据访问模式来存储超大文件,运行于商用硬件集群上,是管理网络中跨多台计算机存储的文件系统。
HDFS不适合用在:要求低时间延迟数据访问的应用,存储大量的小文件,多用户写入,任意修改文件。
2、HDFS的概念
HDFS数据块:HDFS上的文件被划分为块大小的多个分块,作为独立的存储单元,称为数据块,默认大小是64MB。
使用数据块的好处是:
- 一个文件的大小可以大于网络中任意一个磁盘的容量。文件的所有块不需要存储在同一个磁盘上,因此它们可以利用集群上的任意一个磁盘进行存储。
- 简化了存储子系统的设计,将存储子系统控制单元设置为块,可简化存储管理,同时元数据就不需要和块一同存储,用一个单独的系统就可以管理这些块的元数据。
- 数据块适合用于数据备份进而提供数据容错能力和提高可用性。
查看块信息
HDFS的三个节点:Namenode,Datanode,Secondary Namenode
Namenode:HDFS的守护进程,用来管理文件系统的命名空间,负责记录文件是如何分割成数据块,以及这些数据块分别被存储到那些数据节点上,它的主要功能是对内存及IO进行集中管理。
Datanode:文件系统的工作节点,根据需要存储和检索数据块,并且定期向namenode发送他们所存储的块的列表。
Secondary Namenode:辅助后台程序,与NameNode进行通信,以便定期保存HDFS元数据的快照。
HDFS Federation(联邦HDFS):
通过添加namenode实现扩展,其中每个namenode管理文件系统命名空间中的一部分。每个namenode维护一个命名空间卷,包括命名空间的源数据和该命名空间下的文件的所有数据块的数据块池。
HDFS的高可用性(High-Availability)
Hadoop的2.x发行版本在HDFS中增加了对高可用性(HA)的支持。在这一实现中,配置了一对活动-备用(active-standby)namenode。当活动namenode失效,备用namenode就会接管它的任务并开始服务于来自客户端的请求,不会有明显的中断。
架构的实现包括:
- namenode之间通过高可用的共享存储实现编辑日志的共享。
- datanode同时向两个namenode发送数据块处理报告。
- 客户端使用特定的机制来处理namenode的失效问题,这一机制对用户是透明的。
故障转移控制器:管理着将活动namenode转移给备用namenode的转换过程,基于ZooKeeper并由此确保有且仅有一个活动namenode。每一个namenode运行着一个轻量级的故障转移控制器,其工作就是监视宿主namenode是否失效并在namenode失效时进行故障切换。
3、命令行接口
两个属性项: fs.default.name 用来设置Hadoop的默认文件系统,设置hdfs URL则是配置HDFS为Hadoop的默认文件系统。dfs.replication 设置文件系统块的副本个数
文件系统的基本操作:hadoop fs -help可以获取所有的命令及其解释
常用的有:
- hadoop fs -ls / 列出hdfs文件系统根目录下的目录和文件
- hadoop fs -copyFromLocal <local path> <hdfs path> 从本地文件系统将一个文件复制到HDFS
- hadoop fs -rm -r <hdfs dir or file> 删除文件或文件夹及文件夹下的文件
- hadoop fs -mkdir <hdfs dir>在hdfs中新建文件夹
HDFS的文件访问权限:只读权限(r),写入权限(w),可执行权限(x)
4、Hadoop文件系统
Hadoop有一个抽象的文件系统概念,HDFS只是其中的一个实现。Java抽象接口org.apache.hadoop.fs.FileSystem定义了Hadoop中的一个文件系统接口。该抽象类实现HDFS的具体实现是 hdfs.DistributedFileSystem
5、Java接口
最简单的从Hadoop URL读取数据 (这里在Eclipse上连接HDFS编译运行)
package filesystem; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import org.apache.hadoop.fs.FsUrlStreamHandlerFactory; import org.apache.hadoop.io.IOUtils; public class URLCat { static { URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory()); } public static void main(String[] args) throws MalformedURLException, IOException { InputStream in = null; String input = "hdfs://192.168.92.138:9000/user/test.txt"; try { in = new URL(input).openStream(); IOUtils.copyBytes(in, System.out, 4096,false); }finally { IOUtils.closeStream(in); } } }
这里调用Hadoop的IOUtils类,在输入流和输出流之间复制数据(in和System.out)最后两个参数用于第一个设置复制的缓冲区大小,第二个设置结束后是否关闭数据流。
还可以通过FileSystem API读取数据
代码如下:
package filesystem; import java.io.IOException; import java.io.InputStream; import java.net.URI; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; public class FileSystemCat { public static void main(String[] args) throws IOException { String uri = "hdfs://192.168.92.136:9000/user/test.txt"; Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(URI.create(uri),conf); InputStream in = null; try { in = fs.open(new Path(uri)); IOUtils.copyBytes(in, System.out, 1024,false); }finally { IOUtils.closeStream(in); } } }
这里调用open()函数来获取文件的输入流,FileSystem的get()方法获取FileSystem实例。
使用FileSystem API写入数据
代码如下:
package filesystem; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.util.Progressable; public class FileCopyWithProgress { public static void main(String[] args) throws Exception { String localSrc = "E:\\share\\input\\2007_12_1.txt"; String dst = "hdfs://192.168.92.136:9000/user/logs/2008_10_2.txt"; InputStream in = new BufferedInputStream(new FileInputStream(localSrc)); Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(URI.create(dst),conf); OutputStream out = fs.create(new Path(dst),new Progressable() { public void progress() { System.out.print("*"); } }); IOUtils.copyBytes(in, out, 1024,true); } }
FileSystem的create()方法用于新建文件,返回FSDataOutputStream对象。 Progressable()用于传递回掉窗口,可以用来把数据写入datanode的进度通知给应用。
使用FileSystem API删除数据
代码如下:
package filesystem; import java.net.URI; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; public class FileDelete { public static void main(String[] args) throws Exception{ String uri = "hdfs://192.168.92.136:9000/user/1400.txt"; Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(URI.create(uri),conf); fs.delete(new Path(uri)); } }
使用delete()方法来永久性删除文件或目录。
FileSystem的其它一些方法:
- public boolean mkdirs(Path f) throws IOException 用来创建目录,创建成功返回true。
- public FileStatus getFileStates(Path f) throws FIleNotFoundException 用来获取文件或目录的FileStatus对象。
- public FileStatus[ ] listStatus(Path f)throws IOException 列出目录中的内容
- public FileStatus[ ] globStatu(Path pathPattern) throws IOException 返回与其路径匹配于指定模式的所有文件的FileStatus对象数组,并按路径排序
6、数据流
HDFS读取文件过程:
过程描述:
(1)客户端调用FileSyste对象的open()方法在分布式文件系统中打开要读取的文件。
(2)分布式文件系统通过使用RPC(远程过程调用)来调用namenode,确定文件起始块的位置。
(3)分布式文件系统的DistributedFileSystem类返回一个支持文件定位的输入流FSDataInputStream对象,FSDataInputStream对象接着封装DFSInputStream对象(存储着文件起始几个块的datanode地址),客户端对这个输入流调用read()方法。
(4)DFSInputStream连接距离最近的datanode,通过反复调用read方法,将数据从datanode传输到客户端。
(5) 到达块的末端时,DFSInputStream关闭与该datanode的连接,寻找下一个块的最佳datanode。
(6)客户端完成读取,对FSDataInputStream调用close()方法关闭连接。
HDFS文件写入的过程:
过程描述:
写文件过程分析:
(1) 客户端通过对DistributedFileSystem对象调用create()函数来新建文件。
(2) 分布式文件系统对namenod创建一个RPC调用,在文件系统的命名空间中新建一个文件。
(3)Namenode对新建文件进行检查无误后,分布式文件系统返回给客户端一个FSDataOutputStream对象,FSDataOutputStream对象封装一个DFSoutPutstream对象,负责处理namenode和datanode之间的通信,客户端开始写入数据。
(4)FSDataOutputStream将数据分成一个一个的数据包,写入内部队列“数据队列”,DataStreamer负责将数据包依次流式传输到由一组namenode构成的管线中。
(5)DFSOutputStream维护着确认队列来等待datanode收到确认回执,收到管道中所有datanode确认后,数据包从确认队列删除。
(6)客户端完成数据的写入,对数据流调用close()方法。
(7)namenode确认完成。
namenode如何选择在那个datanode存储复本?
需要对可靠性,写入带宽和读取带宽进行权衡。默认布局是:在运行客户端的节点上放第一个复本(如果客户端运行在集群之外,则在避免挑选存储太满或太忙的节点的情况下随机选择一个节点。)第二个复本放在与第一个不同且随机另外选择的机架中节点上。第三个复本与第二个复本放在同一个机架上,且随机选择另一个节点。其它复本放在集群中随机选择的节点中,尽量避免在同一个机架上放太多复本。
一个复本个数为3的集群放置位置如图:
HDFS一致性:HDFS在写数据务必要保证数据的一致性与持久性,目前HDFS提供的两种两个保证数据一致性的方法hsync()方法和hflush()方法。
hflush: 保证flush的数据被新的reader读到,但是不保证数据被datanode持久化。
hsync: 与hflush几乎一样,不同的是hsync保证数据被datanode持久化。
深入hsync()和hflush()参考两篇博客
http://www.cnblogs.com/foxmailed/p/4145330.html
http://www.cnblogs.com/yangjiandan/p/3540498.html
7、通过Flume和Sqoop导入数据
可以考虑使用一些现成的工具将数据导入。
Apache Fluem是一个将大规模流数据导入HDFS的工具。典型应用是从另外一个系统中收集日志数据并实现在HDFS中的聚集操作以便用于后期的分析操作。
Apache Sqoop用来将数据从结构化存储设备批量导入HDFS中,例如关系数据库。Sqoop应用场景是组织将白天生产的数据库中的数据在晚间导入Hive数据仓库中进行分析。
8、通过distcp并行复制
distcp分布式复制程序,它从Hadoop文件系统间复制大量数据,也可以将大量的数据复制到Hadoop。
典型应用场景是在HDFS集群之间传输数据。
% hadoop distcp hdfs://namenode1/foo hdfs://namenode2/bar
9、Hadoop存档
HDFS中每个文件均按块方式存储,每个块的元数据存储在namenode的内存中,因此Hadoop存储小文件会非常低效。因为大量的小文件会耗尽namenode中的大部分内存。Hadoop的存档文件或HAR文件,将文件存入HDFS块,减少namenode内存使用,允许对文件进行透明地访问。
Hadoop存档是通过archive工具根据一组文件创建而来的。运行archive指令:
% hadoop archive -archiveName files.har /my/files /my
列出HAR文件的组成部分:
% hadoop fs -ls /my/files.har
files.har是存档文件的名称,这句指令存储 HDFS下/my/files中的文件。
HAR文件的组成部分:两个索引文件以及部分文件的集合。
存档的不足:
新建一个存档文件会创建原始文件的一个副本,因此需要与要存档的文件容量相同大小的磁盘空间。
一旦存档文件,不能从中增加或删除文件。
HDFS(Hadoop Distributed File System)是Hadoop项目的核心子项目,是分布式计算中数据存储管理的基础,是基于流数据模式访问和处理超大文件的需求而开发的,可以运行于廉价的商用服务器上。它所具有的高容错、高可靠性、高可扩展性、高获得性、高吞吐率等特征为海量数据提供了不怕故障的存储,为超大数据集(Large Data Set)的应用处理带来了很多便利。
HDFS 源于 Google 在2003年10月份发表的GFS(Google File System) 论文。 它其实就是 GFS 的一个克隆版本
之所以选择 HDFS 存储数据,因为 HDFS 具有以下优点:
1、高容错性
-
数据自动保存多个副本。它通过增加副本的形式,提高容错性。
-
某一个副本丢失以后,它可以自动恢复,这是由 HDFS 内部机制实现的,我们不必关心。
2、适合批处理
-
它是通过移动计算而不是移动数据。
-
它会把数据位置暴露给计算框架。
3、适合大数据处理
-
处理数据达到 GB、TB、甚至PB级别的数据。
-
能够处理百万规模以上的文件数量,数量相当之大。
-
能够处理10K节点的规模。
4、流式文件访问
-
一次写入,多次读取。文件一旦写入不能修改,只能追加。
-
它能保证数据的一致性。
5、可构建在廉价机器上
-
它通过多副本机制,提高可靠性。
-
它提供了容错和恢复机制。比如某一个副本丢失,可以通过其它副本来恢复。
当然 HDFS 也有它的劣势,并不适合所有的场合:
1、低延时数据访问
-
比如毫秒级的来存储数据,这是不行的,它做不到。
-
它适合高吞吐率的场景,就是在某一时间内写入大量的数据。但是它在低延时的情况下是不行的,比如毫秒级以内读取数据,这样它是很难做到的。
2、小文件存储
-
存储大量小文件(这里的小文件是指小于HDFS系统的Block大小的文件(默认64M))的话,它会占用 NameNode大量的内存来存储文件、目录和块信息。这样是不可取的,因为NameNode的内存总是有限的。
-
小文件存储的寻道时间会超过读取时间,它违反了HDFS的设计目标。
3、并发写入、文件随机修改
-
一个文件只能有一个写,不允许多个线程同时写。
-
仅支持数据 append(追加),不支持文件的随机修改。
HDFS的架构图
HDFS 采用Master/Slave的架构来存储数据,这种架构主要由四个部分组成,分别为HDFS Client、NameNode、DataNode和Secondary NameNode。下面我们分别介绍这四个组成部分
1、Client:就是客户端。
- 文件切分。文件上传 HDFS 的时候,Client 将文件切分成 一个一个的Block,然后进行存储。
- 与 NameNode 交互,获取文件的位置信息。
- 与 DataNode 交互,读取或者写入数据。
- Client 提供一些命令来管理 HDFS,比如启动或者关闭HDFS。
- Client 可以通过一些命令来访问 HDFS。
2、NameNode:就是 master,它是一个主管、管理者。
- 管理 HDFS 的名称空间
- 管理数据块(Block)映射信息
- 配置副本策略
- 处理客户端读写请求。
3、DataNode:就是Slave。NameNode 下达命令,DataNode 执行实际的操作。
- 存储实际的数据块。
- 执行数据块的读/写操作。
4、Secondary NameNode:并非 NameNode 的热备。当NameNode 挂掉的时候,它并不能马上替换 NameNode 并提供服务。
- 辅助 NameNode,分担其工作量。
- 定期合并 fsimage和fsedits,并推送给NameNode。
- 在紧急情况下,可辅助恢复 NameNode。
HDFS的文件读取原理,主要包括以下几个步骤:
- 首先调用FileSystem对象的open方法,其实获取的是一个DistributedFileSystem的实例。
- DistributedFileSystem通过RPC(远程过程调用)获得文件的第一批block的locations,同一block按照重复数会返回多个locations,这些locations按照hadoop拓扑结构排序,距离客户端近的排在前面。
- 前两步会返回一个FSDataInputStream对象,该对象会被封装成 DFSInputStream对象,DFSInputStream可以方便的管理datanode和namenode数据流。客户端调用read方法,DFSInputStream就会找出离客户端最近的datanode并连接datanode。
- 数据从datanode源源不断的流向客户端。
- 如果第一个block块的数据读完了,就会关闭指向第一个block块的datanode连接,接着读取下一个block块。这些操作对客户端来说是透明的,从客户端的角度来看只是读一个持续不断的流。
- 如果第一批block都读完了,DFSInputStream就会去namenode拿下一批blocks的location,然后继续读,如果所有的block块都读完,这时就会关闭掉所有的流。
HDFS的文件写入原理,主要包括以下几个步骤:
- 客户端通过调用 DistributedFileSystem 的create方法,创建一个新的文件。
- DistributedFileSystem 通过 RPC(远程过程调用)调用 NameNode,去创建一个没有blocks关联的新文件。创建前,NameNode 会做各种校验,比如文件是否存在,客户端有无权限去创建等。如果校验通过,NameNode 就会记录下新文件,否则就会抛出IO异常。
- 前两步结束后会返回 FSDataOutputStream 的对象,和读文件的时候相似,FSDataOutputStream 被封装成 DFSOutputStream,DFSOutputStream 可以协调 NameNode和 DataNode。客户端开始写数据到DFSOutputStream,DFSOutputStream会把数据切成一个个小packet,然后排成队列 data queue。
- DataStreamer 会去处理接受 data queue,它先问询 NameNode 这个新的 block 最适合存储的在哪几个DataNode里,比如重复数是3,那么就找到3个最适合的 DataNode,把它们排成一个 pipeline。DataStreamer 把 packet 按队列输出到管道的第一个 DataNode 中,第一个 DataNode又把 packet 输出到第二个 DataNode 中,以此类推。
- DFSOutputStream 还有一个队列叫 ack queue,也是由 packet 组成,等待DataNode的收到响应,当pipeline中的所有DataNode都表示已经收到的时候,这时akc queue才会把对应的packet包移除掉。
- 客户端完成写数据后,调用close方法关闭写入流。
- DataStreamer 把剩余的包都刷到 pipeline 里,然后等待 ack 信息,收到最后一个 ack 后,通知 DataNode 把文件标示为已完成。
namenode如何选择在哪个datanode 存储副本(replication)?这里需要对可靠性、写入带宽和读取带宽进行权衡。Hadoop对datanode存储副本有自己的副本策略,在其发展过程中一共有两个版本的副本策略,分别如下所示
- 引入了NameNode Federation,解决了横向内存扩展
- 引入了Namenode HA,解决了namenode单点故障
- 引入了YARN,负责资源管理和调度
-
增加了ResourceManager HA解决了ResourceManager单点故障
1、NameNode Federation
架构如下图
- 存在多个NameNode,每个NameNode分管一部分目录
- NameNode共用DataNode
这样做的好处就是当NN内存受限时,能扩展内存,解决内存扩展问题,而且每个NN独立工作相互不受影响,比如其中一个NN挂掉啦,它不会影响其他NN提供服务,但我们需要注意的是,虽然有多个NN,分管不同的目录,但是对于特定的NN,依然存在单点故障,因为没有它没有热备,解决单点故障使用NameNode HA
2、NameNode HA
解决方案:
- 基于NFS共享存储解决方案
- 基于Qurom Journal Manager(QJM)解决方案
1、基于NFS方案
Active NN与Standby NN通过NFS实现共享数据,但如果Active NN与NFS之间或Standby NN与NFS之间,其中一处有网络故障的话,那就会造成数据同步问题
2、基于QJM方案
架构如下图
Active NN、Standby NN有主备之分,NN Active是主的,NN Standby备用的
集群启动之后,一个namenode是active状态,来处理client与datanode之间的请求,并把相应的日志文件写到本地中或JN中;
Active NN与Standby NN之间是通过一组JN共享数据(JN一般为奇数个,ZK一般也为奇数个),Active NN会把日志文件、镜像文件写到JN中去,只要JN中有一半写成功,那就表明Active NN向JN中写成功啦,Standby NN就开始从JN中读取数据,来实现与Active NN数据同步,这种方式支持容错,因为Standby NN在启动的时候,会加载镜像文件(fsimage)并周期性的从JN中获取日志文件来保持与Active NN同步
为了实现Standby NN在Active NN挂掉之后,能迅速的再提供服务,需要DN不仅需要向Active NN汇报,同时还要向Standby NN汇报,这样就使得Standby NN能保存数据块在DN上的位置信息,因为在NameNode在启动过程中最费时工作,就是处理所有DN上的数据块的信息
为了实现Active NN高热备,增加了FailoverController和ZK,FailoverController通过Heartbeat的方式与ZK通信,通过ZK来选举,一旦Active NN挂掉,就选取另一个FailoverController作为active状态,然后FailoverController通过rpc,让standby NN转变为Active NN
FailoverController一方面监控NN的状态信息,一方面还向ZK定时发送心跳,使自己被选举。当自己被选为主(Active)的时候,就会通过rpc使相应NN转变Active状态
3、结合HDFS2的新特性,在实际生成环境中部署图
这里有12个DN,有4个NN,NN-1与NN-2是主备关系,它们管理/share目录;NN-3与NN-4是主备关系,它们管理/user目录
一、HBase介绍
1、基本概念
HBase是一种Hadoop数据库,经常被描述为一种稀疏的,分布式的,持久化的,多维有序映射,它基于行键、列键和时间戳建立索引,是一个可以随机访问的存储和检索数据的平台。HBase不限制存储的数据的种类,允许动态的、灵活的数据模型,不用SQL语言,也不强调数据之间的关系。HBase被设计成在一个服务器集群上运行,可以相应地横向扩展。
2、HBase使用场景和成功案例
- 互联网搜索问题:爬虫收集网页,存储到BigTable里,MapReduce计算作业扫描全表生成搜索索引,从BigTable中查询搜索结果,展示给用户。
- 抓取增量数据:例如,抓取监控指标,抓取用户交互数据,遥测技术,定向投放广告等
- 内容服务
- 信息交互
3、HBase Shell命令行交互:
启动Shell $ hbase shell
列出所有的表 hbase > list
创建名为mytable的表,含有一个列族hb hbase > create ' mytable' , 'hb'
在‘mytable’表的'first'行中的‘hb:data’列对应的数据单元中插入字节数组‘hello HBase’
hbase > put 'mytable' , 'first' , 'hb:data' , 'hello HBase'
读取mytable表 ‘first’行的内容 hbase > get 'mytable' , 'first'
读取mytable表所有的内容 hbase > scan ‘mytable'
二、入门
1、API
和数据操作有关的HBase API有5个,分别是 Get(读),Put(写),Delete(删),Scan(扫描)和Increment(列值递增)
2、操作表
首先要创建一个configuration对象
Configuration conf = HBaseConfiguration.create();
使用eclipse时的话还必须将配置文件添加进来。
conf.addResource(new Path("E:\\share\\hbase-site.xml"));
conf.addResource(new Path("E:\\share\\core-site.xml"));
conf.addResource(new Path("E:\\share\\hdfs-site.xml"));
使用连接池创建一张表。
HTablePool pool = new HTablePool(conf,1);
HTableInterface usersTable = pool.getTable("users");
3、写操作
用来存储数据的命令是put,往表里存储数据,需要创建Put实例。并制定要加入的行
Put put = new Put(byte[] row) ;
Put的add方法用来添加数据,分别设定列族,限定符以及单元格的指
put.add(byte[] family , byte[] qualifier , byte[] value) ;
最后提交命令给表
usersTable.put(put);
usersTable.close();
修改数据,只需重新提交一次最新的数据即可。
HBase写操作的工作机制:
HBase每次执行写操作都会写入两个地方:预写式日志(write-ahead log,也称HLog)和MemStore(写入缓冲区),以保证数据持久化,只有当这两个地方的变化信息都写入并确认后,才认为写动作完成。MemStore是内存里的写入缓冲区,HBase中数据在永久写入硬盘之前在这里累积,当MemStore填满后,其中的数据会刷写到硬盘,生成一个HFile。
4、读操作
创建一个Get命令实例,包含要查询的行
Get get = new Get(byte[] row) ;
执行addColumn()或addFamily()可以设置限制条件。
将get实例提交到表会返回一个包含数据的Result实例,实例中包含行中所有列族的所有列。
Result r = usersTable.get(get) ;
可以对result实例检索特定的值
byte[] b = r.getValue(byte[] family , byte[] qualifier) ;
工作机制:
BlockCache用来保存从HFile中读入内存的频繁访问的数据,避免硬盘读,每个列族都有自己的BlockCache。从HBase中读出一行,首先会检查MemStore等待修改的队列,然后检查BlockCache看包含该行的Block是否最近被访问过,最后访问硬盘上的对应HFile。
5、删除操作
创建一个Delete实例,指定要删除的行。
Delete delete = new Delete(byte[] row) ;
可以通过deleteFamily()和deleteColumn()方法指定删除行的一部分。
6表扫描操作
Scan scan = new Scan() 可以指定起始行和结束行。
setStartRow() , setStopRow() , setFilter()方法可以用来限制返回的数据。
addColumn()和addFamily()方法还可以指定列和列族。
HBase模式的数据模型包括:
表:HBase用表来组织数据。
行:在表里,数据按行存储,行由行键唯一标识。行键没有数据类型,为字节数组byte[]。
列族:行里的数据按照列族分组,列族必须事先定义并且不轻易修改。表中每行拥有相同的列族。
列限定符:列族里的数据通过列限定符或列来定位,列限定符不必事先定义。
单元:存储在单元里的数据称为单元值,值是字节数组。单元由行键,列族或列限定符一起确定。
时间版本:单元值有时间版本,是一个long类型。
一个HBase数据坐标的例子:
HBase可以看做是一个键值数据库。HBase的设计是面向半结构化数据的,数据记录可能包含不一致的列,不确定大小等。
三、分布式的HBase、HDFS和MapReduce
1、分布式模式的HBase
HBase将表会切分成小的数据单位叫region,分配到多台服务器。托管region的服务器叫做RegionServer。一般情况下,RgionServer和HDFS DataNode并列配置在同一物理硬件上,RegionServer本质上是HDFS客户端,在上面存储访问数据,HMaster分配region给RegionServer,每个RegionServer托管多个region。
HBase中的两个特殊的表,-ROOT-和.META.,用来查找各种表的region位置在哪。-ROOT-指向.META.表的region,.META.表指向托管待查找的region的RegionServer。
一次客户端查找过程的3层分布式B+树如下图:
HBase顶层结构图:
zookeeper负责跟踪region服务器,保存root region的地址。
Client负责与zookeeper子集群以及HRegionServer联系。
HMaster负责在启动HBase时,把所有的region分配到每个HRegion Server上,也包括-ROOT-和.META.表。
HRegionServer负责打开region,并创建对应的HRegion实例。HRegion被打开后,它为每个表的HColumnFamily创建一个Store实例。每个Store实例包含一个或多个StoreFile实例,它们是实际数据存储文件HFile的轻量级封装。每个Store有其对应的一个MemStore,一个HRegionServer共享一个HLog实例。
一次基本的流程:
a、 客户端通过zookeeper获取含有-ROOT-的region服务器名。
b、 通过含有-ROOT-的region服务器查询含有.META.表中对应的region服务器名。
c、 查询.META.服务器获取客户端查询的行键数据所在的region服务器名。
d、 通过行键数据所在的region服务器获取数据。
HFile结构图:
Trailer有指向其他块的指针,Index块记录Data和Meta块的偏移量,Data和Meta块存储数据。默认大小是64KB。每个块包含一个Magic头部和一定数量的序列化的KeyValue实例。
KeyValue格式:
该结构以两个分别表示键长度和值长度的定长数字开始,键包含了行键,列族名和列限定符,时间戳等。
预写日志WAL:
每次更新都会写入日志,只有写入成功才会通知客户端操作成功,然后服务器可以按需自由地批量处理或聚合内存中的数据。
编辑流在memstore和WAL之间分流的过程:
处理过程:客户端通过RPC调用将KeyValue对象实例发送到含有匹配region的HRegionServer。接着这些实例被发送到管理相应行的HRegion实例,数据被写入到WAL,然后被放入到实际拥有记录的存储文件的MemStore中。当memstore中的数据达到一定的大小以后,数据会异步地连续写入到文件系统中,WAL能保证这一过程的数据不会丢失。
2、HBase和MapReduce
从MapReduce应用访问HBase有3种方式:
作业开始时可以用HBase作为数据源,作业结束时可以用HBase接收数据,任务过程中用HBase共享资源。
- 使用HBase作为数据源
阶段map
protected void map(ImmutableBytesWritable rowkey,Result result,Context context){
};
从HBase表中读取的作业以[rowkey:scan result]格式接收[k1,v1]键值对,对应的类型是ImmutableBytesWritable和Result。
创建实例扫描表中所有的行
Scan scan = new Scan();
scan.addColumn(…);
接下来在MapReduce中使用Scan实例。
TableMapReduceUtil.initTableMapperJob(tablename,scan,map.class,
输出键的类型.class,输出值的类型.class,job);
- 使用HBase接收数据
reduce阶段
protected void reduce(
ImmutableBytesWritable rowkey,Iterable<put>values,Context context){
};
把reducer填入到作业配置中,
TableMapReduceUtil.initTableReducerJob(tablename,reduce.class,job);
3、HBase实现可靠性和可用性
HDFS作为底层存储,为集群里的所有RegionServer提供单一命名空间,一个RegionServer读写数据可以为其它所有RegionServer读写。如果一个RegionServer出现故障,任何其他RegionServer都可以从底层文件系统读取数据,基于保存在HDFS里的HFile开始提供服务。接管这个RegionServerz服务的region。
四、优化HBase
1、随机读密集型
优化方向:高效利用缓存和更好的索引
- 增加缓存使用的堆的百分比,通过参数 hfile.block.cache.size 配置。
- 减少MemStore占用的百分比,通过hbase.regionserver.global.memstore.lowerLimit和hbase.regionserver.global.memstore.upperLimit来调节。
- 使用更小的数据块,使索引的粒度更细。
- 打开布隆过滤器,以减少为查找指定行的Key Value对象而读取的HFile的数量。
- 设置激进缓存,可以提升随机读性能。
- 关闭没有被用到随机读的列族,提升缓存命中率。
2、顺序读密集型
优化方向:减少使用缓存。
- 增大数据块的大小,使每次硬盘寻道时间取出的数据更多。
- 设置较高的扫描器缓存值,以便在执行大规模顺序读时每次RPC请求扫描器可以取回更多行。 参数 hbase.client.scanner.caching 定义了在扫描器上调用next方法时取回的行的数量。
- 关闭数据块的缓存,避免翻腾缓存的次数太多。通过Scan.setCacheBlocks(false)设置。
- 关闭表的缓存,以便在每次扫描时不再翻腾缓存。
- 3、写密集型
优化方向:不要太频繁刷写,合并或者拆分。
- 调高底层存储文件(HStoreFile)的最大大小,region越大意味着在写的时候拆分越少。通过参数 hbase.hregion.max.filesize设置。
- 增大MemStore的大小,通过参数hbase.hregion.memstore.flush.size调节。刷写到HDFS的数据越多,生产的HFile越大,会在写的时候减少生成文件的数量,从而减少合并的次数。
- 在每台RegionServer上增加分配给MemStore的堆比例。把upperLimit设为能够容纳每个region的MemStore乘以每个RegionServer上预期region的数量。
- 垃圾回收优化,在hbase-env.sh文件里设置,可以设置初始值为:-Xmx8g -Xms8g -Xmn128m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70
- 打开MemStore-Local Allocation Buffer这个特性,有助于防止堆的碎片化。 通过参数hbase.hregion.memstore.mslab.enabled设置
4、混合型
优化方向:需要反复尝试各种组合,然后运行测试,得到最佳结果。
影响性能的因素还包括:
- 压缩:可以减少集群上的IO压力
- 好的行键设计
- 在预期集群负载最小的时候手工处理大合并
- 优化RegionServer处理程序计数