1.Hbase的基本概述
-
什么是BigTable
- BigTable 是一个分布式存储系统,它利用谷歌提出的MapReduce分布式并行计算模型来处理海量数据,使用谷歌分布式文件系统GFS作为底层数据存储,采用Chubby提供协同服务管理,可以扩展到PB级别的数据和上千台机器,具备广泛应用性、可扩展性、高性能和高可用性等特点。
-
什么是Hbase
- HBase 是一个高可靠、高性能、面向列、可伸缩的分布式数据库,是谷歌BigTable的开源实现。
- HBase 要用来存储非结构化和半结构化的松散数据。
- HBase 的目标是处理非常庞大的表,可以通过水平扩展的方式,利用廉价计算机集群处理由超过10亿行数据和数百万列元素组成的数据表。
-
HBase和BigTable的底层技术对应关系
BigTable HBase 文件存储系统 GFS HDFS 海量数据处理 MapReduce Hadoop MapReduce 协同服务管理 Chubby Zookeeper -
为什么需要Hbase
- Hadoop可以很好地解决大规模数据的离线批量处理问题,但是受限于Hadoop MapReduce编程框架的高延迟数据处理机制,使得Hadoop无法满足大规模数据实时处理应用的需求。
- HDFS面向批量访问模式,而不是随机访问模式。
- 传统的通用关系型数据库无法应对在数据规模剧增时导致的系统扩展性和性能问题。
- 传统关系数据库在数据结构变化时一般需要停机维护,空列浪费存储空间。
-
Hbase与传统数据库的区别
数据类型
- 关系数据库采用关系模型,具有丰富的数据类型和存储方式
- HBase则采用了更加简单的数据模型,它把数据存储为未经解释的字符串
数据操作
- 关系数据库中包含了丰富的操作,其中会涉及复杂的多表连接
- HBase操作则不存在复杂的表与表之间的关系,只有简单的插入、查询、删除、清空等,因为HBase在设计上就避免了复杂的表和表之间的关系
存储模式
- 关系数据库是基于行模式存储的
- HBase是基于列存储的,每个列族都由几个文件保存,不同列族的文件是分离的
数据索引
- 关系数据库通常可以针对不同列构建复杂的多个索引,以提高数据访问性能
- HBase只有一个索引——行键,通过巧妙的设计,HBase中的所有访问方法,或者通过行键访问,或者通过行键扫描,从而使得整个系统不会慢下来
数据维护
- 关系数据库中,更新操作会用最新的当前值去替换记录中原来的旧值,旧值被覆盖后就不会存在。
- HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留
可伸缩性
- 关系数据库很难实现横向扩展,纵向扩展的空间也比较有限
- HBase和BigTable这些分布式数据库就是为了实现灵活的水平扩展而开发的,能够轻易地通过在集群中增加或者减少硬件数量来实现性能的伸缩
2.HBase的访问接口
类型 | 特点 | 场合 |
---|---|---|
Native Java API | 最常规和高效的访问方式 | 适合Hadoop MapReduce作业并行批处理HBase表数据 |
HBase Shell | HBase的命令行工具,最简单的接口 | 适合HBase管理使用 |
Thrift Gateway | 利用Thrift序列化技术,支持C++、PHP、Python等多种语言 | 适合其他异构系统在线访问HBase表数据 |
REST Gateway | 解除了语言限制 | 支持REST风格的Http API访问HBase |
Pig | 使用Pig Latin流式编程语言来处理HBase中的数据 | 适合做数据统计 |
Hive | 简单 | 当需要以类似SQL语言方式来访问HBase的时候 |
3.HBase的数据模型
-
基本概述
- HBase 是一个稀疏、多维度、排序的映射表,这张表的索引是行键、列族、列限定符和时间戳
- HBase 中的每个值是一个未经解释的字符串,没有数据类型
- HBase 使得用户在表中存储数据,每一行都有一个可排序的行键和任意多的列
- HBase 中的表在水平方向由一个或者多个列族组成,一个列族中可以包含任意多个列,同一个列族里面的数据存储在一起
- HBase 中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留
-
相关概念
表
:HBase采用表来组织数据,表由行和列组成,列划分为若干个列族行
:每个HBase表都由若干行组成,每个行由行键(row key)来标识。列族
:一个HBase表被分组成许多“列族”(Column Family)的集合,它是基本的访问控制单元列限定符
:列族里的数据通过列限定符(或列)来定位单元格
:在HBase表中,通过行、列族和列限定符确定一个“单元格”(cell),单元格中存储的数据没有数据类型,总被视为字节数组byte[]时间戳
:每个单元格都保存着同一份数据的多个版本,这些版本采用时间戳进行索引
-
数据坐标
HBase中需要根据行键、列族、列限定符和时间戳来确定一个单元格,因此,可以视为一个“四维坐标”,即[行键, 列族, 列限定符, 时间戳]
键 值 [“201505003”, “Info”, “email”, 1174184619081] “xie@qq.com” [“201505003”, “Info”, “email”, 1174184620720] “you@163.com” -
概念视图
-
物理视图
-
行列存储
4.HBase的实现原理
-
功能组件
-
库函数
:链接到每个客户端- 客户端并不是直接从Master主服务器上读取数据,而是在获得Region的存储位置信息后,直接从Region服务器上读取数据
- 客户端并不依赖Master,而是通过Zookeeper来获得Region位置信息,大多数客户端甚至从来不和Master通信,这种设计方式使得Master负载很小
-
一个Master主服务器
服务器Master负责管理和维护HBase表的分区信息,维护Region服务器列表,分配Region,负载均衡
-
许多个Region服务器
Region服务器负责存储和维护分配给自己的Region,处理来自客户端的读写请求
-
-
Region与表
-
一个HBase表被划分成多个Region
-
一个Region会分裂成多个新的Region
-
不同的Region可以分布在不同的Region服务器上
-
-
Region定位
层次 名称 作用 第一层 Zookeeper文件 记录了-ROOT-表的位置信息 第二层 -ROOT-表 记录了.META.表的Region位置信息 -ROOT-表只能有一个Region。通过-ROOT-表,就可以访问.META.表中的数据 第三层 .META.表 记录了用户数据表的Region位置信息,.META.表可以有多个Region,保存了HBase中所有用户数据表的Region位置信息 - 为了加快访问速度,.META.表的全部Region都会被保存在内存中
- 为了加速寻址,客户端会缓存位置信息
- 为了解决缓存失效,寻址过程客户端只需要询问Zookeeper服务器,不需要连接Master服务器
5.HBase的运行机制
-
系统架构
-
客户端
客户端包含访问HBase的接口,同时在缓存中维护着已经访问过的Region位置信息,用来加快后续数据访问过程
-
Zookeeper服务器
Zookeeper可以帮助选举出一个Master作为集群的总管,并保证在任何时刻总有唯一一个Master在运行,这就避免了Master的“单点失效”问题
-
Master
主服务器Master主要负责表和Region的管理工作
-
Region
Region服务器是HBase中最核心的模块,负责维护分配给自己的Region,并响应用户的读写请求
-
-
Region的工作机制
用户读写数据过程
- 用户写入数据时,被分配到相应Region服务器去执行
- 用户数据首先被写入到MemStore和Hlog中
- 只有当操作写入Hlog之后,commit()调用才会将其返回给客户端
- 当用户读取数据时,Region服务器会首先访问MemStore缓存,如果找不到,再去磁盘上面的StoreFile中寻找
缓存的刷新
- 系统会周期性地把MemStore缓存里的内容刷写到磁盘的StoreFile文件中,清空缓存,并在Hlog里面写入一个标记
- 每次刷写都生成一个新的StoreFile文件,因此,每个Store包含多个StoreFile文件
- 每个Region服务器都有一个自己的HLog 文件,每次启动都检查该文件,确认最近一次执行缓存刷新操作之后是否发生新的写入操作;如果发现更新,则先写入MemStore,再刷写到StoreFile,最后删除旧的Hlog文件,开始为用户提供服务
StoreFile的合并
- 每次刷写都生成一个新的StoreFile,数量太多,影响查找速度
- 调用Store.compact()把多个合并成一个
- 合并操作比较耗费资源,只有数量达到一个阈值才启动合并
-
Store的工作机制
- 多个StoreFile合并成一个
- 单个StoreFile过大时,又触发分裂操作,1个父Region被分裂成两个子Region
-
HLog的工作机制
- 分布式环境必须要考虑系统出错,HBase采用HLog保证系统恢复
- HBase系统为每个Region服务器配置了一个HLog文件,它是一种预写式日志(Write Ahead Log)
- 用户更新数据必须首先写入日志后,才能写入MemStore缓存,并且直到MemStore缓存内容对应的日志已经写入磁盘,该缓存内容才能被刷写到磁盘
6.HBase的应用方案
-
性能优化
-
Row Key
-
行键是按照字典序存储,因此,设计行键时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
-
举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为行键的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE - timestamp作为行键,这样能保证新写入的数据在读取时可以被快速命中。
-
-
InMemory
创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到Region服务器的缓存中,保证在读取的时候被cache命中
-
Max Version
创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)
-
Time To Live
创建表的时候,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2 * 24 * 60 * 60)
-
-
性能监视
-
Master-status(自带)
HBase Master默认基于Web的UI服务端口为60010,HBase region服务器默认基于Web的UI服务端口为60030,可以查看HBase集群的当前状态
-
Ganglia
Ganglia是UC Berkeley发起的一个开源集群监视项目,用于监控系统性能
-
OpenTSDB
OpenTSDB可以从大规模的集群(包括集群中的网络设备、操作系统、应用程序)中获取相应的metrics并进行存储、索引以及服务,从而使得这些数据更容易让人理解,如web化,图形化等
-
Ambari
Ambari 的作用就是创建、管理、监视 Hadoop 的集群
-
-
构建SQL引擎
在NoSQL数据存储HBase上提供SQL接口
-
好处
- 易使用。使用诸如SQL这样易于理解的语言,使人们能够更加轻松地使用HBase
- 减少编码。使用诸如SQL这样更高层次的语言来编写,减少了编写的代码量
-
方案
-
Hive整合HBase
Hive与HBase的整合功能从Hive0.6.0版本已经开始出现,利用两者对外的API接口互相通信,通信主要依靠hive_hbase-handler.jar工具包(Hive Storage Handlers)。由于HBase有一次比较大的版本变动,所以并不是每个版本的Hive都能和现有的HBase版本进行整合,所以在使用过程中特别注意的就是两者版本的一致性。
-
Phoenix
Phoenix由Salesforce.com开源,是构建在Apache HBase之上的一个SQL中间层,可以让开发者在HBase上执行SQL查询。
-
-
-
构建二级索引
HBase只有一个针对行健的索引
-
Coprocessor
Coprocessor提供了两个实现:endpoint和observer,endpoint相当于关系型数据库的存储过程,而observer则相当于触发器
- 优点:非侵入性,引擎构建在HBase之上,既没有对HBase进行任何改动,也不需要上层应用做任何妥协
- 缺点:每插入一条数据需要向索引表插入数据,即耗时是双倍的,对HBase的集群的压力也是双倍的
-
Hindex
Hindex 是华为公司开发的纯 Java 编写的HBase二级索引,兼容 Apache HBase 0.94.8
-
HBase+Redis
-
HBase+solr
-
7.HBase的安装使用
-
HBase安装与配置
安装
- 下载安装包hbase-1.1.2-bin.tar.gz
- 解压安装包hbase-1.1.2-bin.tar.gz至路径 /usr/local
- 配置系统环境,将hbase下的bin目录添加到系统的path中
配置
- HBase有三种运行模式,单机模式、伪分布式模式、分布式模式
- 启动关闭Hadoop和HBase的顺序一定是:启动Hadoop—>启动HBase—>关闭HBase—>关闭Hadoop
-
HBase常用Shell命令
create
:创建表list
:列出所有表信息put
:向表、行、列指定的单元格添加数据scan
:浏览表的相关信息get
:通过表名、行、列、时间戳、时间范围和版本号来获得相应单元格的值enable
:使表有效disable
:使表无效drop
:删除表
-
HBase常用Java API
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.*; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; public class Chapter4{ public static Configuration configuration; public static Connection connection; public static Admin admin; public static void main(String[] args) throws IOException{ createTable(“student”,new String[]{“score”}); insertData(“student”,“zhangsan”,“score”,“English”,“69”); insertData(“student”,“zhangsan”,“score”,“Math”,“86”); insertData(“student”,“zhangsan”,“score”,“Computer”,“77”); getData(“student”, “zhangsan”, “score”, “English”); } }
-
建立连接
public static void init(){ 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 createTable(String myTableName,String[] colFamily) throws IOException { TableName tableName = TableName.valueOf(myTableName); if(admin.tableExists(tableName)){ System.out.println("table exists!"); }else { HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName); for(String str: colFamily){ HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(str); hTableDescriptor.addFamily(hColumnDescriptor); } admin.createTable(hTableDescriptor); } }
-
插入数据
public static void insertData( String tableName, String rowKey, String colFamily, String col, String val) throws IOException { Table table = connection.getTable(TableName.valueOf(tableName)); Put put = new Put(Bytes.toBytes(rowkey)); put.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(col), Bytes.toBytes(val)); table.put(put); table.close(); }
-
查看数据
public static void getData( String tableName,String rowKey,String colFamily,String col) throws IOException{ Table table = connection.getTable(TableName.valueOf(tableName)); Get get = new Get(Bytes.toBytes(rowkey)); get.addColumn(Bytes.toBytes(colFamily),Bytes.toBytes(col)); //获取的result数据是结果集,还需要格式化输出想要的数据才行 Result result = table.get(get); System.out.println(new String( result.getValue(colFamily.getBytes(),col==null?null:col.getBytes()))); table.close(); }
-
关闭连接
public static void close(){ try{ if(admin != null){ admin.close(); } if(connection != null){ connection.close(); } }catch (IOException e){ e.printStackTrace(); } }
-