Hbase是什么?
- 基于列式存储的数据库,基于Hadoop的hdfs存储,zookeeper进行管理。
- 适合存储半结构化或非结构化数据,对于数据结构字段不够确定或者杂乱无章很难按一个概念去抽取的数据。
- 为null的记录不会被存储.不占用空间
- 表包含rowkey,时间戳,和列族。新写入数据时,时间戳更新,同时可以查询到以前的版本.
- 是主从架构。hmaster作为主节点,hregionserver作为从节点。
HBase 的特点是什么?
- 大:一个表可以有数十亿行,上百万列;
- 每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列
- 面向列:面向列(族)的存储和权限控制,列(族)独立检索;
- 稀疏:空(null)列并不占用存储空间,表可以设计的非常稀疏;
- 数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳;
- 数据类型单一:Hbase 中的数据都是字符串,没有类型。
HMaster 作用
- 负责meta表的维护
- 为hregionserver分配region,负载均衡重新分配region
- 发现失效的regionserver时重新分配该节点上的region
- 处理schema更新请求
HRegionServer作用
- 维护分配到的region,处理对这些region的IO请求
- 负责切分达到阀值的region
- 每个RegionServer各自保管自己的Hlog
hbase读数据过程
- client —> zk 查找meta表:从中找到 meta表,此表记录Hregin和hregionserver的对应关系
- 根据namespace、表名和rowkey信息。找到写入数据对应的region信息
- client—>hregin读数据:访问hregin所在的hregionserver
- memstore中查找数据(内存:主要负责写)—>找到返回client
- BlockCache中查找数据(内存:主要负责读)—>找到返回client
- storeFile中查找数据(磁盘文件)—>找到返回client
Hbase写数据过程
写操作必须保证Hlog和memstore都写入完成才会返回成功,而且使用读写行锁保证一次对行写入期间其它读写请求会阻塞等待。
- client—> zk 查找meta表:查找mate表,此表记录Hregin和Hregionserver的对应关系
- 根据namespace、表名和rowkey信息。找到写入数据对应的region信息
- 找到这个region对应的regionServer,然后发送请求
- 把数据分别写到HLog(write ahead log也称为WAL,作用:容灾)和memstore各一份
- memstore阈值溢写(默认64M),Flush生成storeFile文件
- 删除HLog中的历史数据
- storeFile达到阈值(默认4个),触发compack合并操作。合并成一个大的storeFile
- storeFile文件大小达到阈值(默认256M)Regin分裂成两个,父Regin下线,两个子regin上线
删数据
hbase可以指定行键的列版本、列、列族、整行进行删除。
删除不是立刻删掉,而是插入一条新的数据,将该行标记为删除。当执行major_compact时,会逐条遍历数据,将删除的数据真正地删除。
storeFile的compack合并
本质就是以IO操作换取后续的读性能的提高。
- Major Compaction大合并:大合并,重量级。就是将一个Region下的所有StoreFile合并成一个StoreFile文件,在大合并的过程中,之前删除的行和过期的版本都会被删除。大合并一般一周做一次,由于执行期间会对整个集群的磁盘和带宽带来较大影响,一般建议设置hbase.hregion.majorcompaction设为0来禁用该功能,并在夜间集群负载较低时通过定时任务脚本来执行。
- minor合并(minor compaction):小合并,轻量级。将多个小文件(通过参数配置决定是否满足合并的条件)重写为数量较少的大文件,减少存储文件数量(多路归并),因为hfile的每个文件都是经过归类的,所以合并速度很快,主要受磁盘IO性能影响
Hbase查询快原理
从磁盘读数据
hbase是根据rowkey查询的,只要能快速的定位rowkey, 就能实现快速的查询,主要是以下因素:
- hbase是可划分成多个region,你可以简单的理解为关系型数据库的多个分区。
- 键是有序的
- 按列存储的
实时查询
- 可以认为是从内存中查询,一般响应时间在1秒内。HBase的机制是数据先写入到内存中,当数据量达到一定的量(如128M),再写入磁盘中
- 在内存中,是不进行数据的更新或合并操作的,只增加数据,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O的高性能。
举例说明
- 假设表有10亿条记录,占空间1TB, 分列成了500个region, 1个region占2个G. 最多读取2G的记录,就能找到对应记录;
- 是按列存储的,其实是列族,假设分为3个列族,每个列族就是666M, 如果要查询的东西在其中1个列族上,1个列族包含1个或者多个HStoreFile,假设一个HStoreFile是128M, 该列族包含5个HStoreFile在磁盘上. 剩下的在内存中
- rowkey是排好序了的,你要的记录有可能在最前面,也有可能在最后面,假设在中间,我们只需遍历2.5个HStoreFile共300M
- 每个HStoreFile(HFile的封装),是以键值对(key-value)方式存储,只要遍历一个个数据块中的key的位置,并判断符合条件可以了。 一般key是有限的长度,假设跟value是1:19(忽略HFile上其它块),最终只需要15M就可获取的对应的记录,按照磁盘的访问100M/S,只需0.15秒。 加上块缓存机制(LRU原则),会取得更高的效率
Hbase写入快原理
- 因为它其实并不是真的立即写入文件中,而是先写入内存,随后异步刷入HFile。
- 所以在客户端看来,写入速度很快。另外,写入时候将随机写入转换成顺序写,数据写入速度也很稳定
hbase导入数据
- 通过HBase API进行批量写入数据;
- 使用Sqoop工具批量导数到HBase集群;
- 使用MapReduce批量导入;
- HBase BulkLoad的方式。
hbase 的存储结构
Hbase 中的每张表都通过行键 (rowkey) 按照一定的范围被分割成多个子表(HRegion),默认一个 HRegion 超过 256M 就要被分割成两个,由 HRegionServer 管理,管理哪些 HRegion 由 Hmaster 分配。 HRegion 存取一个子表时,会创建一个 HRegion 对象,然后对表的每个列族 (Column Family) 创建一个 store 实例, 每个 store 都会有 0个或多个 StoreFile 与之对应,每个 StoreFile 都会对应一个 HFile , HFile 就是实际的存储文件,因此,一个 HRegion 还拥有一个 MemStore 实例。
rowKey 的设计原则
- rowkey 长度原则:不要超过 16 个字节
- rowkey 散列原则:热点问题
- rowkey 唯一原则:rowkey 是按照字典顺序排序存储的,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
scan 和 get 的功能
- get:按指定RowKey获取唯一一条记录,每个get 是以一个 row 来标记的.一个 row 中可以有很多 family 和 column。
- scan:按指定的条件获取一批记录,实现条件查询功能使用的就是 scan 方式
Hbase 中一个 Cell 的结构
- 通过 row 和 columns 确定的为一个存贮单元称为 cell。
- Cell:由{row key, column(= + ), version}是唯一确定的单元cell 中的数据是没有类型的,全部是字节码形式存贮。
HBase 宕机
宕机分为 HMaster 宕机和 HRegisoner 宕机.
- 如果是 HRegisoner 宕机,HMaster 会将其所管理的 region 重新分布到其他活动的 RegionServer 上,由于数据和日志都持久在 HDFS 中,该操作不会导致数据丢失,所以数据的一致性和安全性是有保障的
- 如果是 HMaster 宕机, HMaster 没有单点问题, HBase 中可以启动多个HMaster,通过 Zookeeper 的 Master Election 机制保证总有一个 Master 运行。即ZooKeeper 会保证总会有一个 HMaster 在对外提供服务。
HRegionServer宕机
- ZooKeeper 会监控 HRegionServer 的上下线情况,当 ZK 发现某个 HRegionServer 宕机之后会通知 HMaster 进行失效备援;
- HRegionServer 会停止对外提供服务,就是它所负责的 region 暂时停止对外提供服务
- HMaster 会将该 HRegionServer 所负责的 region 转移到其他 HRegionServer 上,并且会对 HRegionServer 上存在 memstore 中还未持久化到磁盘中的数据进行恢复;
- 这个恢复的工作是由 WAL重播 来完成,这个过程如下
wal实际上就是一个文件,存在/hbase/WAL/对应RegionServer路径下
宕机发生时,读取该RegionServer所对应的路径下的wal文件,然后根据不同的region切分成不同的临时文件recover.edits
当region被分配到新的RegionServer中,RegionServer读取region时会进行是否存在recover.edits,如果有则进行恢复
HBase 优化
- 高可用
在 HBase 中 Hmaster 负责监控 RegionServer 的生命周期,均衡 RegionServer 的负载,如果 Hmaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以 HBase 支持对 Hmaster 的高可用配置。 - 预分区
每一个 region 维护着 startRow 与 endRowKey,如果加入的数据符合某个 region 维护的rowKey 范围,则该数据交给这个 region 维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高 HBase 性能
方案 1:shell 方法
create ‘tb_splits’, {NAME => ‘cf’,VERSIONS=> 3},{SPLITS => [‘10’,‘20’,‘30’]}
方案 2: JAVA 程序控制
· 取样,先随机生成一定数量的 rowkey,将取样数据按升序排序放到一个集合里;
· 根据预分区的 region 个数,对整个集合平均分割,即是相关的 splitKeys;
· HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][]splitkeys)可以指定预分区的 splitKey,即是指定 region 间的 rowkey 临界值。 - 预分区
一条数据的唯一标识就是 rowkey,那么这条数据存储于哪个分区,取决于 rowkey 处于哪个一个预分区的区间内,设计 rowkey 的主要目的 ,就是让数据均匀的分布于所有的 region中,在一定程度上防止数据倾斜。接下来我们就谈一谈 rowkey 常用的设计方案 - 内存优化
HBase 操作过程中需要大量的内存开销,毕竟 Table 是可以缓存在内存中的,一般会分配整个可用内存的 70%给 HBase 的 Java 堆。但是不建议分配非常大的堆内存,因为 GC 过程持续太久会导致 RegionServer 处于长期不可用状态,一般 16~48G 内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。 - 基础优化
- 关闭自动刷写:HTable.setAutoFlushTo(false)
- 增加写入缓存:HTable.setWriteBufferSize(writeBufferSize)
- 不使用WAL:Put.setWriteToWAL(false)
- 压缩传输:hcd.setCompressionType(Algorithm.SNAPPY)
10.设置最大版本数:HColumnDescriptor.setMaxVersions(int maxVersions)
设置生命周期:HColumnDescriptor.setTimeToLive(int timeToLive)
phoenix的使用
Phoenix是一个HBase的开源SQL引擎,它使开发者可以像访问普通数据库那样使用jdbc访问HBase
优点
- 支持SQL查询hbase,自动转换SQL为最佳并行scan语句
- 将where子句交给过滤器处理,将聚合查询交给协处理器处理
- 支持直接创建二级索引来提升非主键的查询性能
- 跳过扫描过滤器来优化IN、Like、OR查询
- 优化写操作
缺点 - 不支持事务、不支持复杂查询
- 严格的版本限制,每个phoenix对应一个版本hbase
- 与hbase强相关,可能导致元数据被破坏
HBase 内部机制
Hbase 是一个能适应联机业务的数据库系统物理存储:hbase 的持久化数据是将数据存储在 HDFS 上。
存储管理:一个表是划分为很多 region 的,这些 region 分布式地存放在很多 regionserver上 Region 内部还可以划分为 store,store 内部有 memstore 和 storefile。
版本管理:hbase 中的数据更新本质上是不断追加新的版本,通过 compact 操作来做版本间的文件合并 Region 的 split。
集群管理:ZooKeeper + HMaster + HRegionServer。
提高 HBase 客户端的读写性能
- 开启 bloomfilter 过滤器,开启 bloomfilter 比没开启要快 3、4 倍
- Hbase 对于内存有特别的需求,在硬件允许的情况下配足够多的内存给它
- 通过修改 hbase-env.sh 中的 export HBASE_HEAPSIZE=3000 #这里默认为 1000m
- 增大 RPC 数量:通过修改 hbase-site.xml 中的 hbase.regionserver.handler.count 属性,可以适当的放大RPC 数量,默认值为 10 有点小。
hbase中的布隆过滤器
布隆过滤器是hbase中的高级功能,它能够减少特定访问模式(get/scan)下的查询时间。不过由于这种模式增加了内存和存储的负担,所以被默认为关闭状态。
当我们随机读get数据时,如果采用hbase的块索引机制,hbase会加载很多块文件。如果采用布隆过滤器后,它能够准确判断该HFile的所有数据块中,是否含有我们查询的数据,从而大大减少不必要的块加载,从而增加hbase集群的吞吐率。
hbase支持如下类型的布隆过滤器:
- NONE 不使用布隆过滤器
- ROW 行键使用布隆过滤器
- ROWCOL 列键使用布隆过滤器
其中ROWCOL是粒度更细的模式。
Hbase中scan对象的setCache和setBatch 方法的使用
为设置获取记录的列个数,默认无限制,也就是返回所有的列.每次从服务器端读取的行数,默认为配置文件中设置的值.
解决Hbase中region太小和region太大带来的冲突
Region过大会发生多次compaction,将数据读一遍并重写一遍到hdfs 上,占用io,region过小会造成多次split,region 会下线,影响访问服务,调整hbase.hregion.max.filesize 为256m.
hbase 的meta表和root表
0.96以下版本的三层架构
meta表的rowKey由表名、起始key、时间戳组成,如果起始key为空,则表示第一个region,按照起始key排序使得行键不需要终止key就能表示范围。
值则是终止Key、列族、列值,该RegionServer的地址等等。
meta表由于数据量过大可能被分割由多个RS存储,因此又设置了root表存放meta表中所有的region,以及该region所属的meta表的位置。
因此三层架构需要三次跳转才能获取到HRegion,如果缓存失效则需要6次,理论上三层架构最少都能存储2ZB的数据。
0.96以上版本的双层架构
三层架构使hbase最少存2ZB的数据,事实上根本用不到这么多,于是删除了root表,只使用meta表定位,meta表的一个region最多可以定位16TB的行键范围,假设一个行键范围包括10条数据,就已经是160TB了,假如一个region大于128M,则更多了,因此根本不需要root表。
HBase Coprocessor协处理器
提供了一种机制可以让开发者直接在RegionServer上运行自定义代码来管理数据。
Hbase比较过滤器
比较过滤器:需要两个参数:比较运算符和比较器
//比较运算符
public enum CompareOp {
LESS, // 检查是否小于比较器里的值
LESS_OR_EQUAL, // 检查是否小于或等于比较器里的值
EQUAL, // 检查是否等于比较器里的值
NOT_EQUAL, // 检查是否不等于比较器里的值
GREATER_OR_EQUAL, // 检查是否大于或等于比较器里的值
GREATER, // 检查是否大于比较器里的值
NO_OP, // 默认返回false,因此过滤掉所有的数据
}
//通过比较器可以实现多样化目标匹配效果,比较器有以下子类可以使用:
BinaryComparator // 匹配完整字节数组
BinaryPrefixComparator // 匹配字节数组前缀
BitComparator // 按位执行与、或、异或比较
NullComparator // 判断当前值是不是 NULL
RegexStringComparator // 正则表达式匹配字符串
SubstringComparator // 子串匹配,相当于 contains()
- 行键过滤器 RowFilter
筛选出匹配的所有的行,基于行键(Rowkey)过滤数据,可以执行精确匹配,子字符串匹配或正则表达式匹配,过滤掉不匹配的数据。
Scan scan = new Scan();
Filter filter = new RowFilter(CompareOp.LESS_OR_EQUAL,
new BinaryComparator(Bytes.toBytes("uid-100")));
scan.setFilter(filter);
- 列族过滤器 FamilyFilter
当 HBase 表有多个列族时,可以用来筛选不同列族中的列。
Scan scan = new Scan();
Filter filter = new FamilyFilter(CompareFilter.CompareOp.LESS,
new BinaryComparator(Bytes.toBytes("cf-d")));
scan.setFilter(filter);
- 列名过滤器 QualifierFilter
根据列名进行筛选。
Scan scan = new Scan();
Filter filter = new QualifierFilter(CompareFilter.CompareOp.EQUAL,
new BinaryComparator(Bytes.toBytes("col-1")));
scan.setFilter(filter1);
- 值过滤器 ValueFilter
筛选特定值的单元格,可以与 RegexStringComparator 搭配使用,完成复杂的筛选。
Scan scan = new Scan();
Filter filter = new ValueFilter(CompareFilter.CompareOp.NOT_EQUAL,
new SubstringComparator("abc"));
scan.setFilter(filter);
- 参考列过滤器 DependentColumnFilter
一种更复杂的过滤器,不止简单的通过用户指定的信息筛选数据。允许指定一个参考列或引用列,使用参考列控制其他列的过滤。该过滤器会使用参考列的时间戳,并在过滤时包括所有与引用时间戳相同的列。
Scan scan = new Scan();
Filter filter = new DependentColumnFilter(Bytes.toBytes("cf-d"),
Bytes.toBytes("col-1"),
"false",
CompareOp.EQUAL,
new BinaryComparator(Bytes.toBytes("val-1"))))
scan.setFilter(filter);
hbase专用过滤器
- 单列值过滤器 SingleColumnValueFilter
使用某一列的值,决定一行数据是否被过滤。对于不包含指定列的行数据,通过 setFilterIfMissing() 决定是否返回。
Scan scan = new Scan();
SingleColumnValueFilter filter = new
SingleColumnValueFilter(Bytes.toBytes("cf-d"),
Bytes.toBytes("col-5"),
CompareFilter.CompareOp.NOT_EQUAL,
new SubstringComparator("val-1"));
filter.setFilterIfMissing(true); // 如果不设置为 true,那些不包含指定列的行也会返回
scan.setFilter(filter);
- 单列值排除器 SingleColumnValueExcludeFilter
继承自 SingleColumnValueFilter,实现的与单列值过滤器相反的语义 - 行前缀过滤器 PrefixFilter
基于行键(Rowkey)的前缀过滤行数据。Scan 操作以字典序查找,当行键大于前缀时,Scan 结束。
Scan scan = new Scan();
Filter filter = new PrefixFilter(Bytes.toBytes("row1"));
scan.setFilter(filter);
- 列前缀过滤器 ColumnPrefixFilter
通过对列名称的前缀匹配过滤,返回的结果只包含满足过滤器的列。
Scan scan = new Scan();
Filter filter = new ColumnPrefixFilter(Bytes.toBytes("col-"));
scan.setFilter(filter);
- 分页过滤器 PageFilter
使用该过滤器,对结果进行按行分野,需要指定 pageSize 参数,控制每页返回的行数,并设置 startRow 多次调用 getScanner(),感觉功能只适用于一些特殊场景,性能也并不高。 - 行键过滤器 KeyOnlyFilter
这个 Filter 只会返回每行的行键+列簇+列,而不返回值(value),对不需要值的应用场景来说,非常实用,减少了值的传递。构造方法可以设置 lenAsValue 参数(默认 false),表示返回时,value 设为原列值的长度。
KeyOnlyFilter filter = new KeyOnlyFilter();
KeyOnlyFilter filter = new KeyOnlyFilter(true);
- 首次行键过滤器 FirstKeyOnlyFilter
这个 Filter 仅仅返回每一行中的第一个 cell 的值,可以用于高效的执行行数统计操作,在扫描到第一个 cell 时,立即跳到下一行数据,性能相比全表扫描得到提升。
FirstKeyOnlyFilter filter = new FirstKeyOnlyFilter();
- 包含结束的过滤器 InclusiveStopFilter
一般的扫描结果中,设置一个开始行键和一个终止行键,是前闭后开区间,不包含结束行,使用这个过滤器时将结束行加入到结果中。
Filter filter = new InclusiveStopFilter(Bytes.toBytes("uid-10"));
- 时间戳过滤器 TimestampsFilter
当需要在扫描结果中对版本进行细粒度控制时,可以使用这个 Filter 传入一个时间戳集合,对时间进行限制,只会返回与指定时间戳相同的版本数据,并且与设置时间戳范围共同使用。
Filter filter = new TimestampsFilter(Arrays.asList(5L, 10L, 15L));
Scan scan1 = new Scan();
scan1.setMaxVersions(3)
scan1.setFilter(filter);
Scan scan2 = new Scan();
scan2.setMaxVersions(3)
scan2.setFilter(filter);
scan2.setTimeRange(8, 12);
- 列计数过滤器 ColumnCountGetFilter
使用这个过滤器,限制每行最多返回多少列。注意当一行的列数达到设定的最大值,过滤器会停止 Scan 操作,所以不适合全表扫描,适合在 Get 方法中使用。 - 列分页过滤器 ColumnPaginationFilter
与 PageFilter 类似,可以对一行的所有列进行分也,需要传入偏移量 offset 和返回数量 limit。
```java
Filter filter = new ColumnPaginationFilter(10,5);
```
- 随机行过滤器 RandomRowFilter
这个 Filter 可以使结果中包含随机行,参数 chance 取值在 0.0 到 1.0 之间,表示随机取行数的比例,每一行会调用 Random.nextFloat() 与 chance 比较来确定是否被过滤。
```java
Filter filter = new RandomRowFilter(0.5F);
```
- 附加过滤器
普通过滤器可以提供对返回结果的筛选限制,一些额外的控制可以附加在过滤器上 - 跳转过滤器 SkipFilter
包装了用户的一个过滤器,当过滤器发现某一行的一列需要过滤时,整行数据都被过滤掉。上面的例子是,使用 SkipFilter 和 ValueFilter 组合,获取不等于指定列值的行,同时过滤掉其他不符合条件的行(即只要有一行中一列的值等于“val-0”,就会被过滤)。
```java
Scan scan = new Scan();
Filter filter1 = new ValueFilter(CompareFilter.CompareOp.NOT_EQUAL,
new BinaryComparator(Bytes.toBytes("val-0")));
Filter filter2 = new SkipFilter(filter1);
```
- 全匹配过滤 WhileMatchFilter
与SkipFilter 相似,不过当一条数据被过滤掉时,会停止 Scan 操作。可以用来检查全表数据中,是否有某些数据不符合条件。
```java
Scan scan = new Scan();
Filter filter1 = new ValueFilter(CompareFilter.CompareOp.NOT_EQUAL,
new BinaryComparator(Bytes.toBytes("val-0")));
Filter filter2 = new WhileMatchFilter(filter1);
```