第5章〓大数据分布式数据库系统HBase HBase是一个具有高性能、高可靠性、面向列、可伸缩的分布式存储系统,利用HBase技术可在普通计算机上搭建大规模结构化的存储集群。HBase的目标是存储并处理大型数据,具体来说是仅需使用普通的硬件配置,就能够处理由成千上万的行和列所组成的大型数据。 与MapReduce的离线批处理计算框架不同,HBase是一个可以随机访问的存储和检索数据平台,弥补了HDFS不能随机访问数据的缺陷,适合实时性要求不是非常高的业务场景。HBase中的数据以Byte[]数组的方式存储,它不区分数据类型,支持结构化、半结构化、非结构化数据,允许动态、灵活的数据模型。 本章简要介绍了HBase的特性及其与传统关系数据库的对比,分别介绍了HBase应用场景、数据模型、HBase工作原理、Shell操作命令,并介绍了常用的HBase编程接口,以及编程实例。 5.1HBase概述 5.1.1HBase简介 HBase是一个分布式的、面向列的开源数据库,该技术来源于Google的论文“BigTable: 一个结构化数据的分布式存储系统”。HBase的目标是处理非常庞大的表,可以通过水平扩展的方式,利用计算机集群处理由超过10亿行数据和数百万列元素组成的数据表。 HBase利用Hadoop MapReduce来处理HBase中的海量数据,实现高性能计算; 利用ZooKeeper作为协同服务,实现稳定服务和失败恢复; 使用HDFS作为高可靠的底层存储,利用廉价集群提供海量数据存储能力。此外,为了方便在HBase上进行数据处理,Sqoop为HBase提供了高效、便捷的RDBMS数据导入功能,Pig和Hive为HBase提供了高层语言支持。 5.1.2HBase特性 HBase的特性如下。 (1) 规模大: 一个表可以有上亿行,上百万列。 (2) 面向列: 面向列表(簇)的存储和权限控制,列(簇)独立检索。 (3) 稀疏: 对于为空(NULL)的列,并不占用存储空间,因此,表可以设计得非常稀疏。 (4) 无模式: 每一行都有一个可以排序的主键和任意多的列,列可以根据需要动态增加,同一张表中不同的行可以有截然不同的列。 (5) 数据多版本: 在创建表时可以自定义多个Version(版本)。可以将版本数理解为开辟的存储空间数目,当写入数据的次数大于版本信息时,先被存储的信息将会被清理,即HBase只保存数据最新的N个版本,当版本数过多时老版本会被删除。默认情况下,初始版本为1,即只提供一个版本空间存储数据。 (6) 数据类型单一: HBase中的数据都是以Byte[]数组的方式存储。 5.1.3HBase与传统关系数据库对比 关系数据库(RDBS)从20世纪70年代发展到今天,已经是一种非常成熟稳定的数据库管理系统,通常具备的功能包括面向磁盘的存储和索引结构、多线程访问、基于锁的同步访问机制、基于日志记录的回复机制和事务机制等。 但是随着Web 2.0应用的不断发展,传统的关系数据库已经无法满足Web 2.0的需求,无论在数据高并发方面,还是在高可扩展性和高可用性方面,传统的关系数据库都显得有些力不从心。关系数据库的关键特性——完善的事务机制和高效的查询机制,在Web 2.0时代也成为“鸡肋”。HBase在内的非关系数据库的出现,有效弥补了传统关系数据库的缺陷,使Web 2.0应用得到了广泛使用。HBase和 RDBMS的对比如表5.1所示。 表5.1HBase和 RDBMS的对比 对比项HBaseRDBMS 数据类型用户把不同格式的结构化、非结构化数据存储为Byte[]数组,需自己编写程序把字符串解析成不同数据类型关系数据库采用关系模型,具有丰富的数据类型和存储方式 数据操作HBase操作只有简单的插入、查询、删除、清空等,无法实现像关联数据库中的表与表之间连接的操作关系数据库中包含丰富的操作,如插入、删除、更新、查询等,其中会涉及复杂的多表连接,通常借助多个表之间的主外键关联来实现 数据存储HBase是基于列存储的,每个列族都由几个文件保存,不同列族的文件是分离的 关系数据库是基于行模式存储的,元组或行会被连续地存储在磁盘页中 索引HBase只有一个索引——行键关系数据库通常可以针对不同列构建复杂的多个索引,以提高数据访问性能 数据更新HBase在更新时,当更新次数不超过版本号时,并不会删除数据旧的版本,而是生成一个新的版本,旧的版本仍然保留在关系数据库中,更新操作会用最新的当前值去替换记录中原来的旧值,旧值被覆盖后就不会存在 扩展性可以实现灵活的水平扩展关系数据库很难实现横向扩展,纵向扩展的空间也比较有限 5.1.4HBase应用场景 HBase使用场景如下。 (1) 确信有足够大的数据。HBase适合分布在有上亿或上千亿行的数据。如果只有上千或上百万行,则用传统的RDBMS可能是更好的选择。因为所有数据可以在一两个结点保存,集群其他结点可能闲置。 (2) 确信可以不依赖所有RDBMS的额外特性(例如,列数据类型、第二索引、事务、高级查询语言等)。 (3) 确信有足够的硬件环境。因为 HDFS 在小于5个数据结点时,基本上体现不出它的优势。 HBase的一个典型应用是WebTable,一个以网页URL为主键的表,其中包含爬取的页面和页面的属性(例如语言和MIME类型)。WebTable非常大,行数可以达十亿级别。在WebTable上连续运行用于批处理分析和解析的MapReduce作业,能够获取相关的统计信息,增加验证的MIME类型列以及人工搜索引擎进行索引的解析后的文本内容。同时,表格还会被以不同运行速度的“爬取器”随机访问并随机更新其中的行; 在用户单击访问网站的缓存页面时,需要实时地将这些被随机访问的页面提供给他们。 5.2HBase数据模型 HBase是一个类似于BigTable的分布式数据库,它是一个稀疏的长期存储的(存在HDFS上)、多维度的、排序的映射表。这张表的索引是行键、列(列族: 列限定符)和时间戳。HBase的数据都是字符串,没有类型。 可以将一个表想象成一个大的映射关系,通过行键+列(列族: 列限定符)+时间戳,就可以定位特定数据。由于HBase是稀疏存储数据的,所以某些列可以是空白的。 5.2.1HBase数据模型术语 HBase实际上就是一个稀疏、多维、持久化存储的映射表,它采用行键(Row Key)、列族(Column Family)、列限定符(Column Qualifier)和时间戳(Timestamp)进行索引,每个值都是未经解释的字节数组Byte[]。 1. 表 HBase采用表(Table)来组织数据,表由行和列组成,列划分为若干个列族。 2. 行 每个HBase表都由若干行(Row)组成,每个行由行键(Row Key)来标识。访问表中的行有三种方式: 全表扫描、通过单个行键访问、通过一个行键的区间来访问。行键可以是任意字符串,其最大长度为64KB。一般来说,行键的长度多为10~100B,在HBase内部行键被保存的方式多为字节数组。 3. 列族 一个HBase表被分组成许多“列族(Column Family)”的集合,它是基本的访问控制单元。列族需要在表创建时就定义好。当列族被创建好之后,数据可以被存放到列族中的某个列下面。列名都以列族作为前缀,举例来说,score:math和score:English这两个列都属于score这个列族。 4. 列限定符 列限定符(Column Qualifier)是列族中数据的索引,列族里的数据通过列限定符(或列)来定位。例如,person:student,student就可以看作对于列族person的一个列限定符,里面放的就是关于学生的数据。列限定符没有数据类型,一般被视为字节数组Byte[]。在HBase中,为了方便,使用冒号(:)来分隔列族和列族修饰符,这是写在HBase源码中的,不能够修改。 5. 单元格 在HBase表中,通过行、列族、列限定符和时间戳或版本来确定一个“单元格(Cell)”。例如,A {row, column, version}元组就是HBase中的一个单元格。单元格中的内容是不可分割的字节数组。单元格中存储的数据没有数据类型,总被视为字节数组Byte[]。每个版本对应一个不同的时间戳。 6. 时间戳 每个单元格都保存着同一份数据的多个版本,这些版本采用时间戳(Timestamp)进行索引。每次对一个单元格执行操作(新建、删除、修改)时,HBase都会隐式地自动生成并存储一个时间戳。在每个存储单元中,不同版本的数据按照时间戳倒序排序,即最新的数排在最前面。时间戳一般是64位整型,可以由用户自己赋值,也可以由HBase在数据库写入时自动赋值。 5.2.2HBase数据逻辑模型 下面以一个实例来阐释HBase的逻辑视图模型。表5.2是一张用来存储学生信息的HBase表,学号作为行键来唯一标识每个学生。表中存有如下两组数据: 学生LiMing,学号1001,英语成绩100分,数学成绩80分; 学生ZhangSan,学号1002,英语成绩100分,数学成绩90分。表中的数据通过一个行键、一个列族和列限定符进行索引和查询定位。即通过{row key, column family, timestamp}可以唯一地确定一个存储值,即一个键值对: {row key, column family, timestamp} → value 表5.2HBase数据逻辑视图——学生表 行键列族“Sname” 列族“course” mathEnglish时间戳value 1001 course:matht380 course:Englisht2100 Snamet1LiMing 1002 course:matht690 course:Englisht5100 Snamet4ZhangSan 5.2.3HBase数据物理模型 在逻辑视图中,HBase中的每个表是由许多行组成的,但是在物理存储层面,它采用了基于列的存储方式,而不是像传统关系数据库那样采用基于行的存储方式,这也是HBase和传统关系数据库的重要区别。表5.2中的逻辑视图在物理存储的时候,会存储成如表5.3所示的形式。按照Sname和course这两个列族分别存放,属于同一个列族的数据会保存在一起,空的列不会被存储成null,而是不会被存储,但是当被请求时会返回null值。 表5.3HBase数据物理视图——学生表 列族行键列 限 定 符时间戳value Sname 1002Snamet4ZhangSan 1001Snamet1LiMing course 1002 matht69 Englisht5100 1001 matht390 Engliht2100 5.3HBase工作原理 5.3.1HBase体系结构 面对海量数据,HBase可采用Master/Slave的方式进行分布式部署,一般采用HDFS作为底层数据存储。HBase的表包含的行的数量通常很大,无法存储在一台机器上,通常根据需要按行键的值对表中的行进行分区,每个行区间构成一个分区,并成为Region,它包含位于这个区间的所有数据。HBase体系结构如图5.1所示。 图5.1HBase体系结构 5.3.2HBase工作组件 1. Client(客户端) 客户端包含访问HBase的接口,可以做一些本地缓存,例如,借助ZooKeeper服务器从主服务器HBase Master(HMaster)获取Region(HRegion)位置信息,并从HRegion服务器(HRegionServer)上读取数据。 2. Master(HMaster) 管理运行不同的HRegion服务器,也为客户端操作HBase的所有元数据提供接口,同时负责HRegionServer的故障处理和HRegion的切分。在HBase集群中可以有多个HMaster,实现集群的高可用。同时HMaster也负责对表的操作。 HMaster功能: (1) 管理用户对表的增、删、改、查等操作。 (2) 实现不同Region服务器之间的负载均衡。 (3) 在Region分裂或合并后,负责重新调整Region的分布。 (4) 对发生故障失效的Region服务器上的Region进行迁移。 3. HRegion和HRegion服务器 HRegion是HBase中分布式存储和负载均衡的最小单元,即不同的HRegion可以分别在不同的HRegion服务器上,但同一个HRegion是不会拆分到多个HRegion服务器上的。HRegion服务器的功能是管理HRegion,负责HRegion的切分和合并。同时,HRegion服务器也是HBase中最核心的模块,负责维护分配给自己的HRegion,并响应用户的读写请求。HBase一般采用HDFS作为底层存储文件系统,因此Region服务器需要向HDFS文件系统中读写数据。采用HDFS作为底层存储,可以为HBase提供可靠稳定的数据存储,HBase自身并不具备数据复制和维护数据副本的功能,而HDFS可以为HBase提供这些支持。HRegion按大小分割,每个表一般只有一个HRegion,随着数据不断插入表,HRegion不断增大,当HRegion的某个列族达到一个阈值(默认256MB)时就会分成两个新的HRegion。 4. Store HRegion虽然是分布式存储的最小单元,但并不是存储的最小单元。HRegion由一个或者多个Store组成,每个Store保存一个列族; 每个Store又由一个MemStore和0至多个StoreFile组成,StoreFile包含HFile,HFile是HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际上,StoreFile就是对HFile做了轻量级包装,即StoreFile底层就是HFile; MemStore存储在内存中,StoreFile存储在HDFS上(数据写入先写MemStore,当MemStore超过阈值(默认64MB),则会刷入以StoreFile存入磁盘)。HBase系统为每个Region服务器配置了一个HLog文件,它是一种预写式日志(Write Ahead Log)。 可以说,HRegion服务器是HBase的核心模块,而Store则是HRegion服务器的核心。每个Store对应了表中的一个列族的存储。每个Store包含一个MemStore缓存和若干个StoreFile文件。当某个Store的StoreFile文件大小超过阈值时,该Store所在的HRegion就会被分裂成两个HRegion,从而保证每个Store都不会过大。 5. HLog 每当要写一个请求,数据会先写入MemStore中,当MemStore超过了当前阈值才会被放入StoreFile中,但是在这个期间一旦主机停电,那么之前MemStore缓存中的数据就会全部丢失。因此,HBase使用HLog(一种预写式日志)来应对这种突发情况。 HBase要求用户更新数据必须首先被计入日志后才能写入MemStore缓存,并且直到MemStore缓存内容对应的日志已经被写入磁盘后,该缓存内容才会被刷新写入磁盘。 6. ZooKeeper服务器 ZooKeeper服务器通常由多台机器构成集群来提供稳定可靠的协同服务。在HBase服务器集群中,包含一个HMaster和多个HRegion服务器,HMaster就是这个HBase集群的“总管”,它必须知道HRegion服务器的状态。使用ZooKeeper就可以轻松做到这点,每个HRegion服务器都需要到ZooKeeper中进行注册,ZooKeeper会实时监控每个HRegion服务器的状态并通知给HMaster,当某个HRegion服务器发生故障时,ZooKeeper会通知HMaster。 在HBase中还可以同时启动多个HMaster,这时ZooKeeper不仅能够帮助维护当前集群中的机器的服务状态,而且能够帮助选出一个HMaster作为“总管”,让这个总管来管理集群,并保证在任何时刻总有唯一一个HMaster在运行,这样就很好地避免了HMaster的“单点失效”问题。 在ZooKeeper文件中还存储了ROOT表(不能被分割,用于实现HRegion定位)的地址和HMaster的地址。HBase通过“三级寻址”方式找到所需的数据,客户端利用ZooKeeper服务器上的ZooKeeper文件访问HBase数据。ROOT表记录了.META.表(分裂成多个HRegion)的HRegion位置信息,.META.表记录了用户数据表的HRegion位置信息。HBase数据结构的三层访问如图5.2所示。 图5.2HBase数据结构的三层访问 5.4HBase安装 下面开始在Linux系统上安装HBase。 5.4.1下载HBase 如图5.3所示,访问HBase的官方网址https://hbase.apache.org/,单击Download下方的here链接,跳转进入HBase下载页面。 图5.3HBase官方主页 HBase下载页面如图5.4所示,可以看到多个HBase版本。由于HBase的版本需要可以匹配Hadoop的版本,HBase 2.5.5版本目前可以匹配Hadoop 3.2.4版本。本教程以HBase 2.5.5版本作为安装教学版本,单击bin链接。跳转到hbase2.5.5bin.tar.gz下载界面,如图5.5所示。 图5.4HBase下载页面 在图5.5中,单击文件链接,将hbase2.5.5bin.tar.gz下载。 图5.5hbase2.5.5bin.tar.gz下载页面 如果下载压缩包发现卡顿,可以尝试其他的下载链接。 5.4.2安装HBase 1. 解压HBase压缩包 下载好的HBase文件是压缩包格式,需要对HBase文件解压。使用tar命令,对hbase2.5.5bin.tar.gz解压。终端输入: tar –zxvf hbase-2.5.5-bin.tar.gz 2. 配置HBase环境变量 将HBase的解压路径配置到Linux环境变量文件上。如图5.6所示,使用vi命令,对~/.bashrc编辑。终端输入: vi ~/.bashrc 图5.6使用vi命令编辑~/.bashrc文件 进入vi编辑模式,在~/.bashrc文件尾行添加HBase压缩包的解压路径(以实际解压路径为准),如图5.7所示。 图5.7在~/.bashrc文件中添加HBase的解压路径 编辑内容: export HBASE_HOME=/home/xiaor/luxm/opts/hbase-2.5.5 export PATH=$PATH:$HBASE_HOME/bin 编辑完成后,如图5.8所示,再执行source命令使上述配置在当前终端立即生效。终端输入: source ~/.bashrc 图5.8使用source命令生效~/.bashrc 生效~/.bashrc文件后,输入“hbase version”检查HBase版本,确定HBase环境变量是否配置成功,如图5.9所示。终端输入: hbase version 图5.9使用hbase version检查HBase环境变量的配置是否成功 如果配置成功,可以查看到HBase的版本: 2.5.5。 3. 配置HBase系统文件 HBase环境变量配置成功后,还需要配置HBase的系统文件。 HBase有三种运行模式: 单机模式、伪分布式模式、分布式模式。这里安装伪分布式模式。在安装HBase前,请确保JDK 1.8以上、Hadoop 2.7以上已经成功安装到Linux系统上。 使用vi命令配置hbaseenv.sh,文件路径是: 解压路径/conf/。如图5.10所示,当前的解压路径是: /home/xiaor/luxm/opts/hbase2.5.5/。终端输入: vi /home/xiaor/luxm/opts/hbase-2.5.5/conf/hbase-env.sh 图5.10vi配置hbaseenv.sh文件 如图5.11所示,编辑hbaseenv.sh内容: export JAVA_HOME=/home/xiaor/luxm/opts/jdk1.8.0_361 export HBASE_CLASSPATH=/home/xiaor/luxm/bigdatas/opts/hbase-2.5.5/conf export HBASE_MANAGES_ZK=true 用命令vi打开并配置hbasesite.xml,文件路径在解压路径的conf目录下,如图5.12所示。终端输入: vi /home/xiaor/luxm/opts/hbase-2.5.5/conf/hbase-site.xml 图5.11编辑hbaseenv.sh内容 图5.12vi配置hbasesite.xml 如图5.13所示,配置hbasesite.xml内容。 图5.13配置hbasesite.xml内容 <configuration> <property> <name>hbase.rootdir</name> <value>hdfs://192.168.56.111:9000/hbase</value> </property> <property> <name>hbase.cluster.distributed</name> <value>true</value> </property> <property> <name>hbase.unsafe.stream.capability.enforce</name> <value>false</value> </property> <property> <name>hbase.zookeeper.quorum</name> <value>192.168.56.111:2181</value> </property> </configuration> 配置完成后,HBase的伪分布式模式安装完成。 5.4.3启动HBase 首先启动Hadoop的HDFS服务,如图5.14所示,这里已经将Hadoop的sbin路径配置到~/.bashrc的环境变量path上,终端输入: start-dfs.sh 图5.14启动Hadoop的HDFS服务 然后启动HBase,通过上文的配置HBase的环境变量,可知HBase的bin路径已经配置到~/.bashrc的环境变量path上,如图5.15所示。终端输入: start-hbase.sh 图5.15启动HBase服务 如图5.16所示,通过jps命令查看HBase有三个进程: HMaster、HRegionServer与HQuorumPeer,说明HBase启动成功。终端输入: jps 图5.16jps检查HBase是否启动成功 HBase启动后,可以直接进入HBase的Shell界面,如图5.17所示。终端输入: hbase shell 图5.17进入HBase的Shell界面 HBase Shell界面如图5.18所示,这里可以使用HBase的命令操作HBase。 图5.18HBase的Shell界面 5.4.4关闭HBase 如图5.19所示,关闭HBase服务,使用stophbase.sh。终端输入: stop-hbase.sh 图5.19关闭HBase服务 关闭后,终端输入jps,查看是否还存在HBase的三个进程: HMaster、HRegionServer与HQuorumPeer。如果都不存在,说明HBase关闭成功。 5.5HBase操作命令 HBase提供了常用的Shell命令,方便用户对表进行操作。首先,需要启动HDFS和HBase进程; 然后,在终端输入“hbase shell”命令即可进入Shell环境。 5.5.1HBase表操作 1. create: 创建表 (1) 创建表student_1,列族为Sname,列族版本号为5(相当于可以存储最近5个版本的记录),命令如下。 hbase>create 'student_1', {NAME => 'Sname', VERSIONS => 1} (2) 创建表student_2,三个列族分别为Sname、gender、score,命令如下。 hbase>create 'student_2',{NAME => 'Sname'}, {NAME => 'gender}, {NAME => 'score'} 或者使用如下等价的命令(推荐): hbase>create 'student_2', 'Sname', 'gender, 'score' (3) 创建表student_3,将表依据分割算法HexStringSplit分布在5个Region里,命令如下。 Hbase>create 'student_3', 'Sname',{NUMREGIONS => 5, SPLITALGO => 'HexStringSplit'} (4) 创建表student_4,指定切分点,命令如下。 hbase>create 'student_4', 'Sname', {SPLITALGO => ['10','20', '30', '40']} 2. alter: 修改列族模式 (1) 向表student_1中添加列族gender,命令如下。 hbase>alter 'student_1', NAME => 'gender' (2) 删除表student_1中的列族gender,命令如下。 Hbase>alter 'student_1', NAME => 'gender', METHOD => 'delete' (3) 设定表student_1中列族最大为128MB,命令如下。 hbase>alter 'student_1', METHOD => 'table_att', MAX_FILESIZE => '134217728' 上面命令中,“134217728”表示字节数,128MB等于134 217 728B。 3. list: 列出HBase中所有的表信息 hbase>list 4. count: 统计表中的行数 可以使用如下命令统计表student_1的行数。 hbase>count 'student_1' 5. describe: 显示表的相关信息 可以使用如下命令显示表student_1的信息。 hbase>describe 'student_1' 6. enable/disable: 使表有效或无效 可以使用如下命令使表t1无效。 hbase>disable 'student_1' 使表有效: hbase>enable 'student_1' 7. drop删除表 删除某个表之前,必须先使该表无效。例如,删除表student_1。 hbase>disable 'student_1' hbase>drop 'student_1' 8. exists: 判断表是否存储 exists: 判断表是否存在。 判断表student_1是否存在: hbase>exists 'student_1' 9. exit: 退出 退出HBase Shell: hbase>exit 5.5.2HBase数据操作 1. put: 向表、行、列指定的单元格添加数据 向表student_2中行键为1001号的学生和列score:math所对应的单元格中添加数据80,时间戳设为1234(若不指定会默认生成系统当前时间与1970年1月1日零点的毫秒值之差作为时间戳),命令如下。 hbase>put 'student_2', 1001, 'score:math', 80,1234 2. get: 通过指定表名、行键、列、时间戳、时间范围和版本号来获得相应 单元格的值 (1) 获得表student_2、行键1001、列score:math、时间范围为[ts1,ts2]、4个版本的数据,命令如下。 hbase>get 'student_2','1001',{COLUMN =>'score:math', TIMERANGE=>[ts1, ts2], VERSIONS=> 4} (2) 获得表student_2、行键1001、列score:math和score:English上的数据,命令如下。 hbase>get 'student_2', '1001', 'score:math', 'score:English' 3. scan: 查询整个表的相关信息 可以通过TIMERANGE、FILTER、LIMIT、STARTROW、STOPROW、TIMESTAMP、MAXLENGTH、COLUMNS、CACHE来限定所需要浏览的数据。 (1) 浏览表“.META.”、列info:regioninfo上的数据,命令如下。 hbase>scan '.META.', {COLUMNS => 'info:regioninfo'} (2) 浏览表student_1、列score:English、时间范围为[1303668804,1303668904]的数据,命令如下。 hbase>scan 'student_1', {COLUMNS => 'score:English', timerange => [1303668804, 1303668904]} 4. delete: 删除指定单元格的数据 删除表student_1、行1001、列族Sname、时间戳为ts1上的数据,命令如下。 hbase>delete 'student_1', '1001', 'Sname', ts1 5.6HBase编程接口 5.6.1HBase常用Java API 与HBase数据存储管理相关的Java API主要包括Admin、HBaseConfiguration、HTableDescriptor、HColumnDescriptor、Put、Get、ResultScanner、Result、Scan。以下讲解这些类的功能与常用方法。 1. org.apache.hadoop.hbase.client.Admin public interface Admin是一个接口,必须通过调用Connection.getAdmin()方法,返回一个实例化对象。该接口用于管理HBase数据库的表信息,包括创建表、删除表、列出表项、使表有效或无效、添加或删除表的列族成员、检查HBase的运行状态等,如表5.4所示。 表5.4Admin接口的主要方法 方法功能 void addColumn(TableName tableName, ColumnFamilyDescriptor columnFamily)向一个已存在的表中添加列 void closeRegion(String regionname, String serverName)关闭Region void createTable(TableDescriptor desc)创建表 void disableTable(TableName tableName)使表无效 void deleteTable(TableName tableName)删除表 void enableTable(TableName tableName)使表有效 boolean tableExists(TableName tableName)检查表是否存在 HTableDescriptor[] listTables()列出所有表 void abort(String why, Throwable e)终止服务器或客户端 boolean balance()负载均衡 2. org.apache.hadoop.hbase.HBaseConfiguration 该类用于管理HBase的配置信息,如表5.5所示。 表5.5HBaseConfiguration类的主要方法 方法功能 Configuration create()使用默认的HBase配置文件创建Configuration Configuration addHbaseResources(Configuration conf)向当前Configuration添加conf中的配置信息 void merge(Configuration destConf, Configuration srcConf)合并两个Configuration 3. org.apache.hadoop.hbase.client.Table public interface Table接口必须调用Connection.getTable()返回一个实例化对象。该接口用于与HBase进行通信。多线程情况下,使用HTablePool较好,如表5.6所示。 表5.6Table接口的主要方法 方法功能 void close()释放所有资源 void delete(Delete delete)删除指定的单元格或行 boolean exists(Get get)检查Get对象指定的列是否存在 Result get(Get get)从指定行的单元格中取得相应的值 void put(Put put)向表中添加值 ResultScanner getScanner(byte[] family) ResultScanner getScanner(byte[] family, byte[] qualifier) ResultScanner getScanner(Scan scan)获得ResultScanner实例 HTableDescriptor getTableDescriptor()获得当前表格的HTableDescriptor对象 TableName getName()获取当前表名 4. org.apache.hadoop.hbase.HTableDescriptor HTableDescriptor包含HBase中表格的详细信息,例如,表中的列族、该表的类型(ROOT、.META.)、该表是否只读、MemStore的最大空间、Region什么时候应该分裂等,如表5.7所示。 表5.7HTableDescriptor类的主要方法 方法功能 HTableDescriptor addFamily(HColumnDescriptor family)添加列族 Collection<HColumnDescriptor>getFamilies()返回所有列族的名称 TableName getTableName()返回表名实例 Byte[] getValue(Bytes key)获得属性值 HTableDescriptor removeFamily(byte[] column)删除列族 HTableDescriptor setValue(byte[] key, byte[] value)设置属性的值 5. org.apache.hadoop.hbase.HColumnDescriptor HColumnDescriptor包含列族的详细信息,例如,列族的版本号、压缩设置等,如表5.8所示。 表5.8HColumnDescriptor类的主要方法 方法功能 Byte[] getName()获得列族名称 Byte[] getValue(byte[] key)获得某列单元格的值 HColumnDescriptor setValue(byte[] key, byte[] value)设置某列单元格的值 6. org.apache.hadoop.hbase.client.Put 用来对单元格执行添加数据操作,如表5.9所示。 表5.9Put类的主要方法 方法功能 Put addColumn(byte[] family, byte[] qualifier, byte[] value)将指定的列族、列、值添加到Put对象中 List<Cell>get(byte[] family, byte[] qualifier)获取列族和列中的所有单元格 boolean has(byte[] family, byte[] qualifier)列族和列是否存在 boolean has(byte[] family, byte[] qualifier, byte[] value)检查列族和列中是否存在value 7. org.apache.hadoop.hbase.client.Get 用来获取单行的信息,如表5.10所示。 表5.10Get类的主要方法 方法功能 Get addColumn(byte[] family, byte[] qualifier)根据列族和列获取对应的列 Get setFilter(Filter filter)通过设置过滤器获取具体的列 8. org.apache.hadoop.hbase.client.Result 用于存放Get或Scan操作后的查询结果,并以<key,value>的格式存储在map结构中,如表5.11所示。 表5.11Result类的主要方法 方法功能 boolean containsColumn(byte[] family, byte[] qualifier)检查是否包含列族和列限定符指定的列 List<Cell>getColumnCells(byte[] family, byte[] qualifier)获得列族和列限定符指定的列中的所有单元格 NavigableMap<byte[],byte[]> getFamilyMap(byte[] family)根据列族获得包含列和值的所有行的键值对 Byte[] getValue(byte[] family, byte[] qualifier)获得列族和列指定的单元格的最新值 9. org.apache.hadoop.hbase.client.ResultScanner 客户端获取值的接口,如表5.12所示。 表5.12ResultScanner类的主要方法 方法功能 void close()关闭scanner并释放资源 Result next()获得下一个Result实例 10. org.apache.hadoop.hbase.client.Scan 可以利用Scan设定需要查找的数据,如设定版本号、起始行号、终止行号、列族、列名、返回值数量上限等,如表5.13所示。 表5.13Scan类的主要方法 方法功能 Scan addFamily(byte[] family)设定列族 Scan addColumn (byte[] family, byte[] qualifier)设定列族和列 Scan setMaxVersions() Scan setMaxVersions(int maxVersions)设定版本的最大个数 Scan setTimeRange (long minStamp, long maxStamp)设定最大最小时间戳范围 Scan setFilter(Filter filter)设定Filter过滤 Scan setStartRow(byte[] startRow)设定开始的行 Scan setStopRow(byte[] stopRow)设定结束的行(不包含) Scan setBatch(int batch)设定最多返回的单元格数目 5.6.2HBase API编程实例 5.6.1节学习了HBase数据库的Java API操作,这一节在Linux操作系统下,使用IDEA进行程序开发。在进行HBase编程之前需要启动Hadoop和HBase。 1. 在IDEA中创建项目 IDEA启动后,呈现如图5.20所示的启动界面,选择Create New Project(创建一个新项目),弹出如图5.21所示的界面,选中左边栏Java,单击Next按钮,弹出如图5.22所示的界面,输入创建项目名为hbaseDemo,项目路径可以自由定义,本书将项目路径设置为~/IdeaProjects/hbaseDemo,单击Finish按钮,IDEA启动后界面如图5.23所示。 图5.20IDEA启动界面 图5.21项目选择页面 图5.22项目名称路径设置页面 图5.23IDEA启动之后的界面 2. 为项目添加所需要的JAR包 在这里已经配置好了JDK 1.8的环境,需要配置HBase需要用到的JAR包。单击左上角的File下拉菜单,选择Project Structure命令进入如图5.24所示的界面。 图5.24Project Structure中的Libraries 单击左上角的“+”号,选择Java,出现如图5.25所示的界面。为了能够编写一个能够与HBase交互的Java应用程序,需要在这个界面中加载该Java程序所需要用到的JAR包,这些JAR包中包含可以访问HBase的Java API。这些JAR包都位于Linux系统的HBase安装目录下,在本书中位于/usr/local/hbase/lib目录下。选中所有的.jar文件(包括文件夹下面的),然后单击OK按钮,如图5.26所示。 图5.25添加相关JAR包界面 图5.26相关JAR包添加完成 3. 编写Java应用程序 编写一个Java应用程序,对HBase数据库进行操作。具体实现功能如下。 (1) 完成Hadoop伪分布与HBase数据库的连接和关闭。 (2) 创建表student_test。 (3) 向student_test表中插入一条数据: 行键1001,Sname为XiaoQiang。 (4) 在student_test表中删除一条数据。 (5) 查询数据。 在IDEA工作界面左侧的面板中(如图5.27所示),右击src文件夹,在弹出的快捷菜单中选择New→Java Class命令,弹出如图5.28所示对话框。 图5.27新建Java Class文件 图5.28新建Java Class文件时的设置页面 在对话框中输入类名“HBaseOperation”,单击OK按钮,弹出如图5.29所示的界面。 图5.29新建一个类文件之后的IDEA界面 IDEA自动创建了一个名为HBaseOperation.java的源代码文件,在该文件中可以输入如下代码。 import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.*; import org.apache.hadoop.hbase.client.*; import java.io.IOException; public class HBaseOperation { public static Configuration configuration; public static Connection connection; public static Admin admin; //主函数 public static void main(String[] args) throws IOException { createTable("student_test", new String[]{"Sname"}); insertRow("student_test", "1001", "Sname", "", "XiaoQiang"); getData("student_test", "1001", "Sname", "", "XiaoQiang"); } //建立连接 public static void init(){ //configuration.addResource("core-site.xml"); //configuration.addResource("hbase-site.xml"); configuration = HBaseConfiguration.create(); configuration.set("hbase.rootdir", "hdfs://localhost:9000/hbase"); try { connection = ConnectionFactory.createConnection(configuration); admin = connection.getAdmin(); } catch (IOException e) { e.printStackTrace(); } } //关闭连接 public static void close(){ if(admin != null){ try { admin.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != connection){ try { connection.close(); } catch (IOException e) { e.printStackTrace(); } } } //建立表 @Deprecate public static void createTable(String myTableName, String[] colFamily) throws IOException{ init(); TableName tableName = TableName.valueOf(myTableName); if (admin.tableExists(tableName)){ System.out.println("该表已存在"); }else{ HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName); for (String str : colFamily) { HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(str); hTableDescriptor.addFamily(hColumnDescriptor); } admin.createTable(hTableDescriptor); } close(); } //删除表 public static void deleteTable(String tableName) throws IOException{ init(); TableName tn = TableName.valueOf(tableName); if (admin.tableExists(tn)){ admin.disableTable(tn); admin.deleteTable(tn); } close(); } //查看已有表 @Deprecate public static void listTables() throws IOException{ init(); HTableDescriptor[] hTableDescriptors = admin.listTables(); for (HTableDescriptor hTableDescriptor : hTableDescriptors) { System.out.println(hTableDescriptor.getNameAsString()); } close(); } //插入数据 public static void insertRow(String tableName, String rowKey, String colFamily, String col, String val) throws IOException{ init(); Table table = connection.getTable(TableName.valueOf(tableName)); Put put = new Put(rowKey.getBytes()); put.addColumn(colFamily.getBytes(), col.getBytes(), val.getBytes()); table.put(put); table.close(); close(); } //删除数据 public static void deleteRow(String tableName, String rowKey, String colFamily, String col, String val) throws IOException{ init(); Table table = connection.getTable(TableName.valueOf(tableName)); Delete delete = new Delete(rowKey.getBytes()); //删除指定列族 //delete.addFamily(Bytes.toBytes(colFamily)); //删除指定列 //delete.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(col)); table.delete(delete); table.close(); close(); } //根据rowKey查找数据 public static void getData(String tableName, String rowKey, String colFamily, String col, String val) throws IOException{ init(); Table table = connection.getTable(TableName.valueOf(tableName)); Get get = new Get(rowKey.getBytes()); get.addColumn(colFamily.getBytes(), col.getBytes()); Result result = table.get(get); showCell(result); table.close(); close(); } //格式化输出 public static void showCell(Result result){ Cell[] cells = result.rawCells(); for (Cell cell : cells) { System.out.println("RowName:" + new String(CellUtil.cloneRow(cell))+ ""); System.out.println("TimeStamp:" + cell.getTimestamp() + ""); System.out.println("column Family:" + new String(CellUtil.cloneFamily(cell)) + ""); System.out.println("row Name:" + new String(CellUtil.cloneQualifier(cell)) + ""); System.out.println("value" + new String(CellUtil.cloneValue(cell)) + ""); } } } 4. 编译运行程序 在确保Hadoop和HBase已经启动的情况下编译运行上面编写的代码。单击IDEA工作界面右上方的绿色箭头,即可运行程序,如图5.30所示。 程序运行完成之后,在控制台面板中会打印写入HBase数据库的学生信息,如图5.31所示。 可以在Linux的终端中启动HBase Shell,查看生成的student_test表,启动HBase Shell的命令,如图5.32所示。 进入HBase Shell,可以使用list命令查看HBase数据库中是否含有名称为student_test的表。如图5.33所示,发现student_test表已经创建成功。 可以使用scan命令查询刚才插入student_test表中的一行数据,如图5.34所示,说明HBase中插入方法成功。 图5.30运行程序 图5.31控制台打印信息 图5.32控制台打印信息 图5.33查看HBase中的所有表 图5.34通过scan命令查询插入的数据 如果需要反复调试HBaseOperation.java代码,需要删除HBase已经创建的t2,可以使用Shell命令删除student_test表,如图5.35所示。 图5.35删除student_test表 代码中的删除一条数据和关闭Hadoop与HBase连接的操作请自行测试。 5. 应用程序的部署 可以将上面编写的Java应用程序打包后部署到远程的服务器上运行。具体的部署方法请参看第15章,这里不再赘述。 习题 1. 简述HBase及HBase的特性。 2. 对比分析HBase与传统关系数据库的异同。 3. 解释下列HBase中的模型术语: 行键、列族、列限定符、单元格、时间戳。 4. 描述HBase的逻辑视图和物理视图有什么不同。 5. 已知职工表emp: 语文老师,姓名张三,年龄30,薪水8000; 英语老师,姓名王五,年龄35,薪水10000。请分别作出HBase逻辑视图和物理视图。 6. 简述HBase各功能组件及其作用。 7. 简述HBase的三层架构中各层次的名称和作用。 8. 列举并说明HBase的几个常用命令。