概述

1、HBase是Apache提供的一个基于Hadoop的、开源的、有版本的、分布式的、可扩展的、能够存储大量数据的非关系型数据库(也就是不是通过数据表来存的,基于其他方式)
HBase是Doug Cutting在Yahoo工作期间,根据Google的论文The BigTable来实现的,所以HBase和BigTable的设计思想和实现原理一模一样,只是BigTable是用C语言实现的,而HBase是用Java实现的
2、HBase允许对数据进行随机且实时的读写大量数据 - billions of rows X millions of columns 十亿行百万列,随机是指随便改一个地方
3、HBase借鉴了列存储的思想,但是HBase并不完全是列存储 (所以才可以达到百万列存储)

mysql数据落地磁盘,一行一行数据是在一起的。
HBase数据落地时,是一列一列的存储在一起1,2,3,4,5,6,7在一起,也便于压缩。每一列存在不同的地方。

4、HBase中不支持SQL,提供了一套全新的操作指令
5、Hbase中没有join,没有多表关联,他的表的拆分是吧相关联的列信息放到一组,叫做一个列簇,还是属于原来的表,而不是把一个宽表拆成多个子表,如果是mysql的话需要拆成两张表,然后join

6、HBase不强调列,列可以动态增删(所以可以存非结构化的数据)
7、HBase适合于存储稀疏数据(大部分数据是空着的),例如半结构化数据(json,任意一个json都可以存,新增加的值就新增列即可),也可以存储结构化数据
8、在HBase中,一个表要想删除,需要先禁用
9、HBase作为数据库,提供了完整的增删改查的功能,本身是将数据存储在HDFS上的,但是HDFS是一次写入多次读取不允许修改,那么HBase如何实现"改"这个效果?
HBase并不是真正去修改HDFS上的数据,而是在尾部追加,并且每一条数据之后都会附带一个时间戳,通过时间戳来比较"新旧",只要不指定,那么总是返回最新的数据,从而给用户一种可以更改数据的"错觉" 【这就是为什么每一个cell都有一个时间戳了】

基本概念

  • Rowkey - 行键:
    1)行键类似于传统的关系型数据库中主键,行键是唯一的
    2)行键在使用的时候不需要起名,行键没有名字
    3)在定义表的时候,不需要定义行键,而是在添加数据的时候手动添加行键
    4)在Hbase中,默认会对行键按照字典序进行升序排序,顺次比较字符·1,110,1111

  • Column Family - 列族/列簇:
    1)一张表中至少包含1个列族,可以包含多个列族
    2)理论上列族个数不固定,但是实际开发中列族个数一般不超过3个
    3)一个列族中可以包含0到多个列,而且列是可以动态增删的
    4)在定义表的时候需要定义列族

  • VERSION - 版本:
    每一个数据都附带一个时间戳,这个时间戳称之为是数据的版本,每个数据都有一个版本
    如果不指定,那么HBase表只对外开放一个版本的数据,即最新的版本
    如果需要获取多个版本的数据,那么需要在建表的时候指定版本开放的数量

  • Cell - 单元:
    如果需要锁定唯一的一条数据,行键+列族+列+版本
    一个Cell中只包含1条数据,所以一个数据格中可以有好多好多的cell

  • namespace - 名称空间
    namespace类似于RDBMS中的database,作用是用于区分同名表
    HBase启动的时候,自带了2个名称空间:default和hbase
    如果建表的时候没有指定namespace,那么默认放置在default下

Hbase安装

1.   三台云主机开启Zookeepercd /home/software/zookeeper-3.4.8/binsh zkServer.sh startsh zkServer.sh status
2.  在第一个节点上启动Hadoop(伪分布式)start-all.sh
3.  在第一个节点上下载HBase的安装包cd /home/software/wget http://bj-yzjd.ufile.cn-north-02.ucloud.cn/hbase-1.3.1-bin.tar.gz
4.  解压安装包tar -xvf hbase-1.3.1-bin.tar.gz
5.  进入conf目录cd hbase-1.3.1/conf
6.  编辑hbase-env.shvim hbase-env.sh添加export JAVA_HOME=/home/presoftware/jdk1.8export HBASE_MANAGES_ZK=false #Hbase在启动过程中需要zk进行管理,这里的意思是关掉hbase自带的zk,用我们自己的zk将export HBASE_MASTER_OPTS和export HBASE_REGIONSERVER_OPTS注释掉 # java8的话这两个可以注释掉保存退出,然后重新生效source hbase-env.sh
7.  编辑hbase-site.xml vim hbase-site.xml添加# hbase基于hadoop,表示数据存在hdfs哪里<property><name>hbase.rootdir</name><value>hdfs://hadoop01:9000/hbase</value></property># 开启hbase分布式<property><name>hbase.cluster.distributed</name><value>true</value></property># zk地址,hbase需要zk进行管理<property><name>hbase.zookeeper.quorum</name><value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value></property>
8.  编辑regionserversvim regionservers添加云主机的名字,例如hadoop01hadoop02hadoop03
9.  远程拷贝cd ../..scp -r hbase-1.3.1 root@hadoop02:/home/software/scp -r hbase-1.3.1 root@hadoop03:/home/software/
10. 进入bin目录cd hbase-1.3.1/bin
11. 启动HBasesh start-hbase.sh三个节点上都应该出现HRegionServer,第一个节点上还会出现HMaster
12. 启动HBase客户端操作HBasesh hbase shell

基本指令

指令 解释
help 查看不同组的命令
status 查看HBase的运行状态
version 查看HBase的版本
whoami 查看当前用户
create ‘person’, {NAME=>‘basic’},{NAME=>‘info’},{NAME=>‘other’} 或者:create ‘person’, ‘basic’, ‘info’, ‘other’ 建立了一个person表,包含basic、info和other三个列族
put ‘person’, ‘p1’, ‘basic:name’, ‘Helen’ 向person表中的basic列族的name列添加一个行键为p1的值为Helen的数据
get ‘person’, ‘p1’ 查看person表中行键为p1对应的值
get ‘person’, ‘p1’, {COLUMN=>[‘basic’,‘info’]} 或者 get ‘person’, ‘p1’, ‘basic’, ‘info’ 查看person表中行键为p1对应的basic列族和info列族的值
get ‘person’, ‘p1’, {COLUMN=>[‘basic:name’, ‘info:height’]} 或者 get ‘person’, ‘p1’, ‘basic:name’, ‘info:height’ 查看指定列的值
scan ‘person’ 查询整个表
scan ‘person’, {COLUMNS=>[‘basic’, ‘info’]} 查询person表中basic列族和info列族的值
scan ‘person’, {COLUMNS=>[‘basic:name’, ‘info:weight’]} 查询person表中basic列族name列和info列族weight列的值
delete ‘person’, ‘p1’, ‘other:addr’ 删除person表中p1行键对应的other列族addr列的值,只能删除摸一个具体的列
deleteall ‘person’, ‘p1’ 删除一行
truncate ‘person’ 摧毁重建表
describe ‘person’ / desc ‘person’ 描述这个表的结构
list 查看所有名称空间下的表
disable ‘person’ 禁用表
drop ‘person’ 删除表
create ‘person’, {NAME=>‘basic’, VERSIONS=>3}, {NAME=>‘info’, VERSIONS=>5} 建立一个person表包含basic列族和info列族,其中basic列族允许对外提供3个版本的数据,info列族允许对外提供5个版本的数据
get ‘person’, ‘p1’, {COLUMN=>‘basic:age’, VERSIONS=>3} 获取basic列族age列最近3个版本的数据
exists ‘student’ 判断一个表是否存在
create_namespace ‘hbasedemo’ 创建名称空间hbasedemo,相当于创建一个数据库
list_namespace 查看已有的名称空间
create ‘hbasedemo:person’, ‘basic’, ‘info’ 在hbasedemo下新建了一张person表
list_namespace_tables ‘hbasedemo’ 查看指定名称空间下的表
drop_namespace ‘hbasedemo’ 删除指定的名称空间,要求这个空间下没有表

hbase添加数据时,如果行键一样,相当于是在一行上面添加数据
HBase中修改和新增是同一个语法,添加相同列簇列名相当于覆盖
列可以动态添加,没有值的地方就空着。稀疏数据

HBase API操作

创建表

1、导入依赖

 <dependency><groupId>org.apache.hbase</groupId><artifactId>hbase</artifactId><version>1.3.1</version><type>pom</type></dependency><dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-common</artifactId><version>1.3.1</version></dependency><dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-client</artifactId><version>1.3.1</version></dependency><dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-server</artifactId><version>1.3.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version></dependency>

2、将log4j.properties放入resources目录下面

3、编写代码

package cn.tedu.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.junit.Test;import java.io.IOException;public class HBaseDemo {// 创建表@Testpublic void createTable() throws IOException {// create 'student', 'basic', 'info'// 获取HBase的配,配置文件中的内容Configuration conf = HBaseConfiguration.create();// 通过Zookeeper才能连接HBase,需要设置Zookeeper的连接地址conf.set("hbase.zookeeper.quorum", "10.9.162.133:2181,10.9.152.65:2181,10.9.130.83:2181");// 获取表的管理权HBaseAdmin admin = new HBaseAdmin(conf);// 构建一个表描述器,为了构建表的元数据//create 'student'HTableDescriptor table = new HTableDescriptor(TableName.valueOf("student")); //表名// 构建列描述器,列簇名HColumnDescriptor cf1 = new HColumnDescriptor("basic");HColumnDescriptor cf2 = new HColumnDescriptor("info");// 添加列族table.addFamily(cf1);table.addFamily(cf2);// 建表admin.createTable(table);// 关闭管理权admin.close();}}

如果在运行过程中,出现多次retry,在hosts文件中,把其他的两台的云主机的ip都添加进去

增删改查
package cn.tedu.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.CompareFilter;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.RegexStringComparator;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.junit.Test;import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class HBaseDemo {// 添加数据/修改数据@Testpublic void putData() throws IOException {// put 'student', 'p1', 'basic:name', 'Helen'// put 'student', 'p1', 'info:addr', 'beijing'// 先获取hbase配置Configuration conf = HBaseConfiguration.create();// 设置Zookeeper的连接地址conf.set("hbase.zookeeper.quorum", "10.9.162.133:2181,10.9.152.65:2181,10.9.130.83:2181");// 连接表,HbaseTableHTable table = new HTable(conf, "student");// 封装Put对象// 参数表示的是行键Put put = new Put("s1".getBytes());// family - 列族// qualifier - 列// value - 数据值put.addColumn("basic".getBytes(), "name".getBytes(), "Helen".getBytes());put.addColumn("info".getBytes(), "addr".getBytes(), "beijing".getBytes());//可以封装多列// 添加数据table.put(put); //table.put(List<Put>)// 关闭连接table.close();}// 测试:添加百万条数据// 花费时间:17536 ~ 17.5s@Testpublic void putMillionData() throws IOException {long begin = System.currentTimeMillis();Configuration conf = HBaseConfiguration.create();conf.set("hbase.zookeeper.quorum", "10.9.162.133:2181,10.9.152.65:2181,10.9.130.83:2181");HTable table = new HTable(conf, "student");List<Put> list = new ArrayList<>();for (int i = 0; i < 1000000; i++) {Put put = new Put(("s" + i).getBytes());put.addColumn("basic".getBytes(), "id".getBytes(), ("no" + i).getBytes());list.add(put);// 每1w条数据,就向HBase表中添加一次if (list.size() >= 10000) {table.put(list);list.clear();}}table.close();long end = System.currentTimeMillis();System.err.println(end - begin);}// 删除数据@Testpublic void deleteData() throws IOException {Configuration conf = HBaseConfiguration.create();conf.set("hbase.zookeeper.quorum", "10.9.162.133:2181,10.9.152.65:2181,10.9.130.83:2181");//表名HTable table = new HTable(conf, "student");// 封装成Delete对象,行键Delete del = new Delete("s1".getBytes());//列名,不指定列名的话,就是删除一行del.addColumn("info".getBytes(), "addr".getBytes());// 删除数据table.delete(del);// 关闭连接table.close();}// 查询数据,查询单条@Testpublic void getData() throws IOException {Configuration conf = HBaseConfiguration.create();conf.set("hbase.zookeeper.quorum", "10.9.162.133:2181,10.9.152.65:2181,10.9.130.83:2181");HTable table = new HTable(conf, "student");// 封装Get对象Get get = new Get("s1".getBytes());//如果不指定列的话,表示获取整行数据//建议指定要查询的列,可以减少数据传输量get.addColumn("basic".getBytes(), "name".getBytes());// 查询数据,将查询的结果封装到Result中Result r = table.get(get);byte[] value = r.getValue("basic".getBytes(), "name".getBytes());System.err.println(new String(value));table.close();}// 遍历数据@Testpublic void scanData() throws IOException {Configuration conf = HBaseConfiguration.create();conf.set("hbase.zookeeper.quorum", "10.9.162.133:2181,10.9.152.65:2181,10.9.130.83:2181");HTable table = new HTable(conf, "student");// 封装Scan对象,不指定范围的话就是遍历所有行// Scan scan = new Scan();// 从指定行键开始打印到最后// Scan scan = new Scan("s9".getBytes());// 指定范围,不包含s91行键Scan scan = new Scan("s90".getBytes(), "s91".getBytes());// 遍历数据,将结果封装成一个ResultScanner - 结果扫描器返回ResultScanner rs = table.getScanner(scan);Iterator<Result> it = rs.iterator();//获取迭代器while (it.hasNext()) {Result r = it.next();System.err.println(new String(r.getValue("basic".getBytes(), "id".getBytes())));//具体某一列的值}table.close();}// 过滤@Testpublic void filter() throws IOException {Configuration conf = HBaseConfiguration.create();conf.set("hbase.zookeeper.quorum", "10.9.162.133:2181,10.9.152.65:2181,10.9.130.83:2181");HTable table = new HTable(conf, "student");Scan scan = new Scan();// 封装Filter对象,提供了很多的子类// 根据行键过滤//CompareFilter.CompareOp.EQUAL 比较模式:等于//匹配规则 : 至少含5555的字符串Filter filter = new RowFilter(CompareFilter.CompareOp.EQUAL,new RegexStringComparator(".*5555.*"));// 设置过滤器scan.setFilter(filter);ResultScanner rs = table.getScanner(scan);Iterator<Result> it = rs.iterator();while (it.hasNext()) {Result r = it.next();System.err.println(new String(r.getValue("basic".getBytes(), "id".getBytes())));}table.close();}// 删除表@Testpublic void dropTable() throws IOException {Configuration conf = HBaseConfiguration.create();conf.set("hbase.zookeeper.quorum", "10.9.162.133:2181,10.9.152.65:2181,10.9.130.83:2181");// 获取管理权,DDLHBaseAdmin admin = new HBaseAdmin(conf);// 禁用表admin.disableTable("student");// 删除表admin.deleteTable("student");// 关流admin.close();}
}

基本理论

HRegion

1、在HBase中,会从行键方向上(横向)将一个Table切分为一个到多个HRegion -
表在刚建立的时候默认情况下里面只有1个HRegion,但是随着数据量的增加,会分裂为多个HRegion

2、每一个table的HRegion会分布到HRegionServer上,HRegionServer管理着HRegion

3、因为行键是有序的,字典序,所以每一个HRegion的数据不交叉的【不会出现一个HRegion从1,6-10,第二个从2-5,11-20】
一本字典撕成两半
HRegion管理数据,数据最终是落地到HDFS上;也就是说HRegion知道自己管理的数据(行键的范围)都具体落在哪些dataNode上。

4、当HRegion达到指定大小(默认大小是10G)的时候,进行分裂,均裂为2个HRegion,其中一个HRegion的自动进行转移,转移到其他相对空闲节点
这个过程中只是发生了管理权的转移(好比某一部分行键的元数据转移到了另外的HRegion进行管理,转移到了另外的HRegionServer上,但是底层的HDFS上的数据不需要动 ),不代表数据发生转移

5、HRegion中包含一个到多个HStore,每一个列族对应了一个HStore,即有几个列族,那么在一个HRegion中就会出现几个HStore

6、HStore中包含1个memStore以及0到多个HFile/StoreFile。HFile落地到HDFS上 (多副本)

Zookeeper

HBase在第一次启动的时候自动在Zookeeper上注册一个持久节点/hbase
Active HMaster启动的时候,会自动在Zookeeper上注册一个临时节点/hbase/master。Zookeeper会监控这个节点,当这个临时节点消失之后(说明HMaster死掉了),Zookeeper就会从Backup HMaster中选择一个最早启动的切换为Active状态(故障切换)
当Backup HMaster备用HMaster节点启动的时候,会自动在Zookeeper的/hbase/backup-masters节点下注册子节点,把自己的信息注册到zk上

zk的作用:中间人,协调者,统计信息,有什么事情找zk
/hbase/master节点上是谁的信息,谁就是老大。谁就对外提供服务。

HMaster

1、HMaster的状态:Active(活跃)和Backup(备份)
2、在HBase中,HMaster的个数不做限定 - 可以在任意一个安装了HBase的节点上来启动HMaster - 通过命令:sh hbase-daemon.sh start master
3、HMaster的状态由启动顺序决定:先启动的HMaster是Active状态,后启动的HMaster是Backup
可以查看zk查看主Master,或者通过web页面查看
4、当Active HMaster接收到操作之后需要和Backup HMaster之间进行同步备份 - Active HMaster先监控/hbase/backup-masters的子节点的变化,然后根据这个节点下子节点来进行同步
5、元数据:在HBase中,namespace名、表名、列族名等属于元数据
HMaster的职责:
负责管理HRegionServer,主要负责是HRegion在HRegionServer之间的转移(需要知道那个HRS管理了哪个HR)
负责namespace以及ddl(对表结构操作,例如create、drop等)操作,dml(对表数据操作,put、get等)操作不经过HMaster - 需要修改元数据的操作才会经过HMaster

DML操作

以下步骤不经过HMaster
1、在HBae0.96之前

  • 发起DML请求,先找到zk;请求获取 -ROOT-文件的位置
  • 返回文件的位置
  • -ROOT-文件存储在DataNode上,读取这个文件
  • 文件的内容返回.meta. 元数据文件的位置
  • 读取.meta.文件的内容,返回HRegion的位置,知道我要操作的数据是哪个HRegion进行管理的
  • 访问具体的HRegion进行数据的增删改查

2、从HBase0.96开始
客户端在第一次访问Zookeeper之后,会缓存.meta.文件的位置
客户端在读取.meta.文件的信息之后,会缓存文件中的内容

在zk上的/hbase/meta-region-server节点就是存储的.meta.文件的location

因为元数据也没有怎么多,所以就不需要一个总账一样的东西——-ROOT-文件了,简化了过程

HRegionServer

HRegionServer用于管理HRegion,每一个HRegionServer能够管理1000个HRegion(10T的数据)
一个HRegionServer由1个WAL,1个BlockCache以及0到多个HRegion来组成:

  • WAL/HLog - Write Ahead Log(写前日志)
    1) 作用:记录写操作,保证数据的可靠性(不丢失,先写日志再操作数据)
    2)当HRegionSever接收到写请求之后(put ‘person’,‘p1’,‘info:age’,17),会先将这个写请求记录到WAL中,如果记录成功,则将数据更新到对应HRegion中HStore中的memStore中。
    在HBase0.94版本之前,WAL只允许串行写(一条一条请求的写);从HBase0.94版本开始,引入了NIO的Channel机制,允许对WAL进行并行写(zk上有一个节点叫做splitWAL,将一个WAL文件切成了好几个小的,分别写,不是真正意义上的对一个文件并行写)
    3)当WAL达到一定条件的时候,会被自动清理
    因为memStore已经被冲刷成HFile了,不需要用这个来进行数据恢复了

  • BlockCache - 块缓存
    读缓存,维系在内存中,默认是128M(用户读取的数据会缓存再BlockCache中)
    采用了"局部性"原理 - 本质上就是一个"猜"的过程,提高"猜"的命中率:

    时间局部性:在HBase中,如果一条数据被读取过,那么会认为这条数据再次被读取的概率要高于其他没有被读取过的数据,那么会将这条数据放到读缓存中

    空间局部性:在HBase中,如果一条数据被读取过,那么 会认为与这条数据相邻的数据被读取的概率要高于其他不相邻的数据,那么会将这条数据相邻的数据放到读缓存中
    采用了LRU策略:抛弃最长时间不用的数据 (Redis也有这个,保证热点数据)

  • HRegion
    1)一个HRegion包含1到多个HStore,每一个HStore对应一个列族
    2)一个HStore中包含1个memStore以及0到多个HFile/StoreFile
    ①memStore:写缓存,将数据记录到内存中,默认大小是128M,当memStore达到一定条件的时候,冲刷出一个HFile(也就是数据先写到了内存当中的)【如果这部分内存中的数据挂了,就从WAL里面的操作重新执行】
    ②HFile:0到多个。HFile落地到HDFS上 -> HBase中的数据最终写到HFile中,HFile存储在DataNode上

    memStore的冲刷条件:

    1)当memStore用满之后,会自动冲刷出一个HFile
    2)当WAL的大小达到 1G 的时候,会冲刷这个HRegionServer上所有的memStore(因为WAL上记录了多个memStore的操作信息),同时WAL会滚动产生一个新的WAL,原来的WAL就会变成OLD_WAL,OLD_WAL会被清理掉
    3)当所有的memStore所占用的内存之和/总内存>0.35,那么会自动冲刷几个最大的memStore - 这种方案会产生大量的小文件 (运行时间越长,第三个条件越来越容易达到)

写流程

1、当HRegionServer收到写请求的时候,会默认先将这个写操作记录到WAL中,然后会将数据更新到memStore中 (保证操作不丢失)
2、数据在memStore(写缓存中,内存中)中会进行排序:行键字典序升序 -> 列族名字典序升序 -> 列名字典序升序 -> 时间戳倒序
3、当memStore达到一定条件之后,会自动进行冲刷,冲刷出HFile - 因为数据在memStore中已经排序,所以HFile是局部有序整体无序的,各个HFile自己把持的起始行键和结束行键-HFile落地到HDFS上,一个HFile上只有一个列族的信息。

4、HFile的格式Version1:

  • DataBlock:存储数据
    DataBlock是HBase中数据存储的最小单位
    一个DataBlock默认大小是64KB:小的DataBlock利于查询(get),大的DataBlock利于遍历(scan)
    BlockCache的空间局部性是以DataBlock为单位存储:只要DataBlock中有一条数据被读取,那么整个DataBlock都会放到BlockCache中

datablock的大小设置是有取舍的。查询快和遍历快

例如查询行键p32时,找到dataIndex,取出相应的dataBlock数据,然后从里面取出键值对。

每一个DataBlock细分为1个Magic和多个KeyValue
Magic称之为魔数,本质上就是一个随机数,作用对数据进行校验,只要改变过数据那么这个随机数就会发生变化
KeyValue用于存储数据,键值对的结构如下,有个印象就行

key type 数据类型
key和value加起来就是一个cell

  • MetaBlock:存储元数据,绝大部分HFile中不包含这一部分,一般只有 .meta. 文件中会包含,.meta. 也是一个HFILE文件。
  • FileInfo:对文件信息的描述,文件大小,修改时间,创建时间等
  • DataIndex:记录DataBlock在文件中的存储位置(记录每个datablock是从哪里到哪里,字节偏移量)
  • MetaIndex:记录MetaBlock在文件中的存储位置 (作用同上)
  • Trailer:在文件末尾,固定4个字节大小,其中前2个字节记录DataIndex的起始位置,后2个字节记录MetaIndex的起始位置 ,还有文件信息的位置。

5、HFile在第二个版本中新添了布隆过滤器:
实现思路简单,设置一个字节数组即可。

对行键使用三个hash函数映射到字节数组的三个位置上。判断x元素是否存在,同样对x进行映射,对应位置是1的话就说明可能存在,但是如果是0的话说明一定不存在。例如p和m两个元素都映射到了同样的三个位置,所以是1不一定存在,但是是0一定不存在。通过布隆过滤器快速确定行键一定不存在。

读流程

当HRegionServer接收到读请求之后,会先从BlockCache中读取
如果BlockCache读取不到,则会从memStore(一个HStore有一个memStore)中读取;如果memStore中也读不到,那么就需要从HFile中

会先根据HFile的行键范围进行筛选(筛选哪些HFile可能包含数据,只是可能),筛选完成之后会再利用布隆过滤器来进行筛选,被筛掉的文件中是一定没有要查询的数据,但是剩下的文件中不能保证一定有要查询的数据。
尽可能的缩小需要读取的HFile的范围。

Compaction机制-合并机制,解决小文件问题,由于冲刷的问题

HBase提供了2种Compaction机制:
minor compact:会将相邻的几个小的HFile合并成一个大的HFile,如果原来就是大的HFile那么不合并,合并完成之后存在多个相对较大的HFile
major compact:将所有的HFile合并成一个HFile,所以合并完成之后存在一个HFile

在HBase中,如果不指定,默认使用minor compact;实际开发中,一般会在周末的凌晨进行major compact,major compact一般是几个小时
在合并过程中,会舍弃被标记删除的数据或者舍弃过时的数据,例如一个数据指定保留3个版本,但是在合并过程中发现了5个版本,那么就会舍弃最早的2个版本的数据 。舍弃掉不要的键值对。数据真正的修改是在合并过程中发生的。

HBASE扩展

参考预习资料

HBASE索引

关系型数据库可以在多列上建立索引,但是HBase只能在Rowkey上建立索引。(可以通过ES为Hbase的列建立索引),怎么整合?

Hbase的优化

调节数据块(data block)的大小

设置小重get,设置大重scan
HFile数据块大小可以在列族层次设置。这个数据块不同于之前谈到的HDFS数据块,其默认值是65536字节,或64KB。数据块索引存储每个HFile数据块的起始键。数据块大小的设置影响数据块索引的大小。数据块越小,索引越大,从而占用更大内存空间。同时加载进内存的数据块越小,随机查找性能更好。但是,如果需要更好的序列扫描性能,那么一次能够加载更多HFile数据进入内存更为合理,这意味着应该将数据块设置为更大的值。相应地,索引变小,将在随机读性能上付出更多的代价
可以在表实例化时设置数据块大小:hbase(main):002:0> create 'mytable',{NAME => 'colfam1', BLOCKSIZE => '65536'}

适当时机关闭数据块缓存

scan偏多,遍历偏多时,建议关闭缓存
把数据放进读缓存,并不是一定能够提升性能。如果一个表或表的列族只被顺序化扫描访问或很少被访问,则Get或Scan操作花费时间长一点是可以接受的。在这种情况下,可以选择关闭列族的缓存
关闭缓存的原因在于:如果只是执行很多顺序化扫描,会多次使用缓存,并且可能会滥用缓存,从而把应该放进缓存获得性能提升的数据给排挤出去,所以如果关闭缓存,不仅可以避免上述情况发生,而且可以让出更多缓存给其他表和同一表的其他列族使用。数据块缓存默认是打开的
可以在新建表或更改表时关闭数据块缓存属性:hbase(main):002:0> create 'mytable', {NAME => 'colfam1', BLOCKCACHE => 'false'}

开启布隆过滤器

布隆过滤器(Bloom Filter)允许对存储在每个数据块的数据做一个反向测验。当查询某行时,先检查布隆过滤器,看看该行是否不在这个数据块。布隆过滤器要么确定回答该行不在,要么回答不知道。因此称之为反向测验。布隆过滤器也可以应用到行内的单元格上,当访问某列标识符时先使用同样的反向测验
使用布隆过滤器也不是没有代价,相反,存储这个额外的索引层次占用额外的空间。布隆过滤器的占用空间大小随着它们的索引对象数据增长而增长,所以行级布隆过滤器比列标识符级布隆过滤器占用空间要少。当空间不是问题时,它们可以压榨整个系统的性能潜力
可以在列族上打开布隆过滤器: create 'mytable', {NAME => 'colfam1', BLOOMFILTER => 'ROWCOL'}
布隆过滤器参数的默认值是NONE。另外,还有两个值:ROW表示行级布隆过滤器;ROWCOL表示列标识符级布隆过滤器。行级布隆过滤器在数据块中检查特定行键是否不存在,列标识符级布隆过滤器检查行和列标识符联合体是否不存在。ROWCOL布隆过滤器的空间开销高于ROW布隆过滤器。

开启数据压缩

一般用得不多,这种优化方式
HFile可以被压缩并存放在HDFS上,这有助于节省硬盘I/O,但是读写数据时压缩和解压缩会抬高CPU利用率。压缩是表定义的一部分,可以在建表或模式改变时设定。除非确定压缩不会提升系统的性能,否则推荐打开表的压缩。只有在数据不能被压缩,或者因为某些原因服务器的CPU利用率有限制要求的情况下,有可能需要关闭压缩特性
HBase可以使用多种压缩编码,包括LZO、SNAPPY和GZIP,LZO和SNAPPY是其中最流行的两种
当建表时可以在列族上打开压缩:create 'mytable', {NAME => 'colfam1', COMPRESSION => 'SNAPPY'}
注意,数据只在硬盘上是压缩的,在内存中(MemStore或BlockCache)或在网络传输时是没有压缩的

设置Scan缓存

实际开发中使用不多。客户单将scan结果缓存起来
HBase的Scan查询中可以设置缓存,定义一次交互从服务器端传输到客户端的行数,设置方法是使用Scan类中setCaching()方法,这样能有效地减少服务器端和客户端的交互,更好地提升扫描查询的性能
HTable table = new HTable(config, Bytes.toBytes(tableName));

Scan scanner = new Scan();
/* batch and caching */
scanner.setBatch(0);
scanner.setCaching(10000);
ResultScanner rsScanner = table.getScanner(scanner);
for (Result res : rsScanner) {
final List list = res.list();
String rk = null;
StringBuilder sb = new StringBuilder();
for (final KeyValue kv : list) {
sb.append(Bytes.toStringBinary(kv.getValue()) + “,”);
rk = getRealRowKey(kv);
}
if (sb.toString().length() > 0)
sb.setLength(sb.toString().length() - 1);
System.out.println(rk + “\t” + sb.toString());
}
rsScanner.close();

显式地指定列

查询时最好指定需要哪一列的数据
当使用Scan或Get来处理大量的行时,最好确定一下所需要的列。因为服务器端处理完的结果,需要通过网络传输到客户端,而且此时,传输的数据量成为瓶颈,如果能有效地过滤部分数据,使用更精确的需求,能够很大程度上减少网络I/O的花费,否则会造成很大的资源浪费。如果在查询中指定某列或者某几列,能够有效地减少网络传输量,在一定程度上提升查询性能。下面代码是使用Scan类中指定列的addColumn()方法
HTable table = new HTable(config, Bytes.toBytes(tableName));

Scan scanner = new Scan();
/* 指定列 */
scanner.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column));
ResultScanner rsScanner = table.getScanner(scanner);
for (Result res : rsScanner) {
final List list = res.list();
String rk = null;
StringBuilder sb = new StringBuilder();
for (final KeyValue kv : list) {
sb.append(Bytes.toStringBinary(kv.getValue()) + “,”);
rk = getRealRowKey(kv);
}
if (sb.toString().length() > 0)
sb.setLength(sb.toString().length() - 1);
System.out.println(rk + “\t” + sb.toString());
}
rsScanner.close();

关闭ResultScanner

ResultScanner类用于存储服务端扫描的最终结果,可以通过遍历该类获取查询结果。但是,如果不关闭该类,可能会出现服务端在一段时间内一直保存连接,资源无法释放,从而导致服务器端某些资源的不可用,还有可能引发RegionServer的其他问题。所以在使用完该类之后,需要执行关闭操作。这一点与JDBC操作MySQL类似,需要关闭连接。代码的最后一行rsScanner.close()就是执行关闭ResultScanner。

使用批量读

通过调用HTable.get(Get)方法可以根据一个指定的行键获取HBase表中的一行记录。同样HBase提供了另一个方法,通过调用HTable.get(List<Get>)方法可以根据一个指定的行键列表,批量获取多行记录。使用该方法可以在服务器端执行完批量查询后返回结果,降低网络传输的速度,节省网络I/O开销,对于数据实时性要求高且网络传输RTT高的场景,能带来明显的性能提升。

使用批量写

通过list<Put>
通过调用HTable.put(Put)方法可以将一个指定的行键记录写入HBase,同样HBase提供了另一个方法,通过调用HTable.put(List<Put>)方法可以将指定的多个行键批量写入。这样做的好处是批量执行,减少网络I/O开销。

关闭写WAL日志

如果允许数据丢失,可以关闭这个。如果不能容忍,就不能关
在默认情况下,为了保证系统的高可用性,写WAL日志是开启状态。写WAL开启或者关闭,在一定程度上确实会对系统性能产生很大影响,根据HBase内部设计,WAL是规避数据丢失风险的一种补偿机制,如果应用可以容忍一定的数据丢失的风险,可以尝试在更新数据时,关闭写WAL。该方法存在的风险是,当RegionServer宕机时,可能写入的数据会出现丢失的情况,且无法恢复。关闭写WAL操作通过Put类中的writeToWAL()设置。可以通过在代码中添加:put.setWriteToWAL(false);

设置AutoFlush

用得很少
HTable有一个属性是AutoFlush,该属性用于支持客户端的批量更新。该属性默认值是true,即客户端每收到一条数据,立刻发送到服务端。如果将该属性设置为false,当客户端提交Put请求时,将该请求在客户端缓存,直到数据达到某个阈值的容量时(该容量由参数hbase.client.write.buffer决定)或执行hbase.flushcommits()时,才向RegionServer提交请求。这种方式避免了每次跟服务端交互,采用批量提交的方式,所以更高效。
但是,如果还没有达到该缓存而客户端崩溃,该部分数据将由于未发送到RegionServer而丢失。这对于有些零容忍的在线服务是不可接受的。所以,设置该参数的时候要慎重。
可以在代码中添加:table.setAutoFlush(false);table.setWriteBufferSize(12*1024*1024);

预创建Region

在你能够预估数据量大小的前提下,创建表的时候,就直接创建好HRegion,就不需要分裂转移了。在HBase中创建表时,该表开始只有一个Region,插入该表的所有数据会保存在该Region中。随着数据量不断增加,当该Region大小达到一定阈值时,就会发生分裂(Region Splitting)操作。并且在这个表创建后相当长的一段时间内,针对该表的所有写操作总是集中在某一台或者少数几台机器上,这不仅仅造成局部磁盘和网络资源紧张,同时也是对整个集群资源的浪费。这个问题在初始化表,即批量导入原始数据的时候,特别明显。为了解决这个问题,可以使用预创建Region的方法
Hbase内部提供了RegionSplitter工具:${HBASE_HOME}/bin/hbase  org.apache.hadoop.hbase.util.RegionSplitter test2  HexStringSplit  -c 10 -f cf1
其中,test2是表名,HexStringSplit表示划分的算法,参数-c 10表示预创建10个Region,-f cf1表示创建一个名字为cf1的列族。

调整ZooKeeper Session的有效时长

zk和HMaster之间有心跳,用来检测HMaster是否还活着。默认每隔180秒发一次心跳。一般建议心跳间隔时间修改为10s参数zookeeper.session.timeout用于定义连接ZooKeeper的Session的有效时长,这个默认值是180秒。这意味着一旦某个RegionServer宕机,HMaster至少需要180秒才能察觉到宕机,然后开始恢复。或者客户端读写过程中,如果服务端不能提供服务,客户端直到180秒后才能觉察到。在某些场景中,这样的时长可能对生产线业务来讲不能容忍,需要调整这个值
此参数在HBase-site.xml中,通过<property></property>

架构师

对框架的源代码做有效的基本改动,才叫做架构入门。比如springmvc,android,spring,使得视图的跳转效率更高这样的。
比如nginx,提供一个更好的负载均衡方案。架构,加油,高程。
架构代表努力,沉淀,天赋。有能力修改源代码。才能称之为架构。沉淀,沉淀,沉淀,努力。有天赋不一定能成为架构,我感觉我不是一个聪明的人,我只能努力了。

努力+沉淀+天赋 三中其二 才能做到架构

hbase:汇总知识相关推荐

  1. Notion Like 笔记软件使用教程·学习资源汇总·知识管理方案

    Notion Like 笔记软件使用教程·学习资源汇总·知识管理方案:深度评测.辅助工具.信息管理.时间管理.任务管理.思维管理.项目管理.文件管理.笔记方法.记忆方法.写作方法 关于 Notion ...

  2. hbase 核心知识

    Hbase 负载均衡 Hbase全局计划 Hbase全局计划执行的流程--估算 Hbase随机分配计划 Hbase 批量启动分配计划 Hbase 通过shell控制负载均衡 何时使用HBase 转载于 ...

  3. HBase核心知识和应用案例

    Hbase 热点问题? Hbase 预分区 Hbase Rowkey 设计原则 Hbase 常见避免热点问题方法 Hbase 总结 Hbase 连续查询的Rowkey设计 Hbase 随机查询的Row ...

  4. ① Hbase 基础知识

    简介 Hbase 数据模型 ROW KEY 决定一行数据 字典序 最多只能64k 列族和列 时间戳 cell

  5. 2018年HBase生态社群画像 +最全资料汇总下载

    HBaseCon 亚洲大会全部PPT:下载 钉群直播全部资料下载:下载 9届Meetup视频和PPT下载:下载 <58HBase平台实践和应用 -平台建设篇> 何良均/张祥 58同城 查看 ...

  6. 知识管理软件厂商汇总

    知识管理软件厂商汇总知识管理软件厂商汇总: 1. TRS信息技术有限公司:公司提出内容管理的战略,拥有自主核心技术和知识产权的内容管理产品.在信息检索.中文自然语言处理和知识管理等领域,TRS进行了多 ...

  7. 2018年HBase生态社群画像

    HBaseCon 亚洲大会PPT下载:https://yq.aliyun.com/articles/626119?spm=a2c4e.11153940.blogcont684011.9.60e7464 ...

  8. 2018年12月云栖技术活动最全资料汇总:50+直播与Meetup分享

    云栖君导读:应开发者建议,云栖社区特别将线下沙龙和直播活动进行汇总,一键分享给大家.当然,从2019年1月起,我们还有预告版. 云栖社区12月份技术活动资料下载大全:技术直播.系列公开课.Meetup ...

  9. 修改HBase的rowkey设计把应用的QPS从5W提升到50W

    摘要: 正确设计Hbase的rowkey可以让你的应用飞起来,前提是你需要了解一些Hbase的存储机制. UTT是Aliexpress的营销消息运营平台,运营希望促销活动时APP消息推送的QPS达到3 ...

最新文章

  1. 趣谈网络协议笔记-二(第十一讲)
  2. PAT甲级1116 Come on! Let‘s C:[C++题解]哈希表、素数
  3. 如何入门技术、进阶技术(技术开发人员)
  4. 使用Jmeter压力测试工具测试
  5. TreeSet集合中的自定义比较器
  6. GTK实现:俄罗斯方块小游戏源代码(RussiaCube.c)
  7. 二维数组,字符串,字符数组
  8. 《OpenGL编程指南》一1.2 初识OpenGL程序
  9. php制作闹钟,简易闹钟 - 按键精灵资源站 按键精灵教程,学习脚本制作,脚本大全,视频教程...
  10. SpringMVC工作原理概述
  11. 实习(光条中心提取,灰度重心法)
  12. springMVC 生成Excel和PDF
  13. 小强学AI之 - 2你患癌症的概率(朴素贝叶斯)
  14. 校园IPTV数字电视教学直播系统方案-淮安生态文旅区实验小学
  15. TN.STN液晶屏常见问题及解决办法
  16. [RK3399] [Android7.1] UAC配置,使用USB转音频喇叭播放声音
  17. php扩展ts和nts,浅谈php的TS和NTS的区别
  18. WPF自学手册-读书笔记(二)心法
  19. html模仿原生ios通讯录制作国家展示页(手机端)
  20. 利用vpython实现秋千模型

热门文章

  1. Google输入法 or Outlook2003 Bug
  2. 测试基础 10 问(上​)
  3. 笔记本不能更换显卡为何又叫独立显卡?
  4. 百度竞价开户推广之关键词的优化大揭秘
  5. 银联网被黑 金融网安全陷信任危机
  6. 人人影视:不可能再恢复或重启,App的尸体可以删除了
  7. 爱思唯尔的ESWA——模板、投稿、返修、接收的总结
  8. 语音聊天工具 android,Android 即时语音聊天工具 开发
  9. 【生活工作经验 三】天津学区买房初探
  10. 防护之盾|金融数据安全思考