HBase知识总结

1.HBase简介

hbase是基于Google BigTable模型开发的,典型的key/value系统。是建立在hdfs之上,提供高可靠性、高性能、列存储、可伸缩、实时读写nosql的数据库系统。

https://yuepengfei-1258421470.cos.ap-shanghai.myqcloud.com/hbase

1.1 集群的安装

上传、解压、修改配置

注意:要把hadoop的hdfs-site.xml和core-site.xml 放到hbase/conf下

(1)修改hbase-env.sh

	export JAVA_HOME=/export/servers/jdk
	//告诉hbase使用外部的zk
	export HBASE_MANAGES_ZK=false

(2)修改hbase-site.xml

	<configuration>
		<!-- 指定hbase在HDFS上存储的路径 -->
        <property>
                <name>hbase.rootdir</name>
                <value>hdfs://node01:8020/hbase</value>
        </property>
		<!-- 指定hbase是分布式的 -->
        <property>
                <name>hbase.cluster.distributed</name>
                <value>true</value>
        </property>
		<!-- 指定zk的地址,多个用“,”分割 -->
        <property>
                <name>hbase.zookeeper.quorum</name>
                <value>node01:2181,node02:2181,node93:2181</value>
        </property>
	</configuration>

(3)修改regionservers文件

node02
node03

(4)创建backup-master(备用master)

node02

(5)配置环境变量

vim /etc/profile

export HBASE_HOME=/export/servers/hbase 
export PATH=$PATH:$HBASE_HOME/bin

(6)启动

启动zookeeper、启动hbase,查看node01:16010

2. HBase shell

进入hbase命令行
./hbase shell

显示hbase中的表
list

创建user表,包含info、data两个列族
create 'user', 'info', 'data'
或者
create 'user', {NAME => 'info', VERSIONS => '3'},{NAME => 'data'}

向user表中插入信息,row key为rk0001,列族info中添加name列标示符,值为zhangsan
put 'user', 'rk0001', 'info:name', 'zhangsan'

向user表中插入信息,row key为rk0001,列族info中添加gender列标示符,值为female
put 'user', 'rk0001', 'info:gender', 'female'

向user表中插入信息,row key为rk0001,列族info中添加age列标示符,值为20
put 'user', 'rk0001', 'info:age', 20

向user表中插入信息,row key为rk0001,列族data中添加pic列标示符,值为picture
put 'user', 'rk0001', 'data:pic', 'picture'

获取user表中row key为rk0001的所有信息
get 'user', 'rk0001'

获取user表中row key为rk0001,info列族的所有信息
get 'user', 'rk0001', 'info'

获取user表中row key为rk0001,info列族的name、age列标示符的信息
get 'user', 'rk0001', 'info:name', 'info:age'

获取user表中row key为rk0001,info、data列族的信息
get 'user', 'rk0001', 'info', 'data'
get 'user', 'rk0001', {COLUMN => ['info', 'data']}

get 'user', 'rk0001', {COLUMN => ['info:name', 'data:pic']}

获取user表中row key为rk0001,列族为info,版本号最新5个的信息
get 'user', 'rk0001', {COLUMN => 'info', VERSIONS => 2}
get 'user', 'rk0001', {COLUMN => 'info:name', VERSIONS => 5}
get 'user', 'rk0001', {COLUMN => 'info:name', VERSIONS => 5, TIMERANGE => [1392368783980, 1392380169184]}

获取user表中row key为rk0001,cell的值为zhangsan的信息
get 'people', 'rk0001', {FILTER => "ValueFilter(=, 'binary:zhangsan')"}

获取user表中row key为rk0001,列标示符中含有a的信息
get 'people', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}

put 'user', 'rk0002', 'info:name', 'fanbingbing'
put 'user', 'rk0002', 'info:gender', 'female'
put 'user', 'rk0002', 'info:nationality', '中国'
get 'user', 'rk0002', {FILTER => "ValueFilter(=, 'binary:中国')"}

查询user表中的所有信息
scan 'user'

查询user表中列族为info的信息
scan 'user', {COLUMNS => 'info'}
scan 'user', {COLUMNS => 'info', RAW => true, VERSIONS => 5}
scan 'person', {COLUMNS => 'info', RAW => true, VERSIONS => 3}

查询user表中列族为info和data的信息
scan 'user', {COLUMNS => ['info', 'data']}
scan 'user', {COLUMNS => ['info:name', 'data:pic']}

查询user表中列族为info、列标示符为name的信息
scan 'user', {COLUMNS => 'info:name'}

查询user表中列族为info、列标示符为name的信息,并且版本最新的5个
scan 'user', {COLUMNS => 'info:name', VERSIONS => 5}

查询user表中列族为info和data且列标示符中含有a字符的信息
scan 'user', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}

查询user表中列族为info,rk范围是[rk0001, rk0003)的数据
scan 'people', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}

查询user表中row key以rk字符开头的
scan 'user',{FILTER=>"PrefixFilter('rk')"}

查询user表中指定范围的数据
scan 'user', {TIMERANGE => [1392368783980, 1392380169184]}

删除数据
删除user表row key为rk0001,列标示符为info:name的数据
delete 'people', 'rk0001', 'info:name'
删除user表row key为rk0001,列标示符为info:name,timestamp为1392383705316的数据
delete 'user', 'rk0001', 'info:name', 1392383705316

清空user表中的数据
truncate 'people'

修改表结构
首先停用user表
disable 'user'

添加两个列族f1和f2
alter 'people', NAME => 'f1'
alter 'user', NAME => 'f2'

启用表
enable 'user'

删除一个列族:
alter 'user', NAME => 'f1', METHOD => 'delete' 或 alter 'user', 'delete' => 'f1'

添加列族f1同时删除列族f2
alter 'user', {NAME => 'f1'}, {NAME => 'f2', METHOD => 'delete'}

将user表的f1列族版本号改为5
alter 'people', NAME => 'info', VERSIONS => 5
启用表
enable 'user'

删除表
disable 'user'
drop 'user'

查询数据
get 'person', 'rk0001', {FILTER => "ValueFilter(=, 'binary:中国')"}
get 'person', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}
scan 'person', {COLUMNS => 'info:name'}
scan 'person', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}
scan 'person', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}

scan 'person', {COLUMNS => 'info', STARTROW => '20140201', ENDROW => '20140301'}
scan 'person', {COLUMNS => 'info:name', TIMERANGE => [1395978233636, 1395987769587]}
delete 'person', 'rk0001', 'info:name'

alter 'person', NAME => 'ffff'
alter 'person', NAME => 'info', VERSIONS => 10


get 'user', 'rk0002', {COLUMN => ['info:name', 'data:pic']}

3. HBase数据结构

(1)列式存储

行式和列式存储的区别

优点:适合数据分析和数据的压缩

(2)数据的存储方式

hbase的存储模式

这里时以info为单位的列式存储

4.HBase原理

4.1 架构

https://yuepengfei-1258421470.cos.ap-shanghai.myqcloud.com/hbase

从图中可以看出Hbase是由Client、Zookeeper、Master、HRegionServer、HDFS等几个组建组成

(1)Client

Client包含了访问Hbase的接口,另外Client还维护了对应的cache来加速Hbase的访问,比如cache的.META.元数据的信息。(所以有时,HMaster死了,我们依然可以完成读写)

(2)zookeeper

通过Zoopkeeper来保证集群中只有1个master在运行,如果master异常,会通过竞争机制产生新的master提供服务

通过Zoopkeeper来监控RegionServer的状态,当RegionSevrer有异常的时候,通过回调的形式通知Master RegionServer上下限的信息

通过Zoopkeeper存储元数据的统一入口地址(记录了.META.表所在机器)

(3)Hmaster

为RegionServer分配Region,维护整个集群的负载均衡(核心作用)

维护集群的元数据信息

发现失效的Region,并将失效的Region分配到正常的RegionServer上

当RegionSever失效的时候,协调对应Hlog的拆分

这里注意一点:RegionServer本身不存储数据,数据还是在HDFS上,他只是维护了数据存储的信息,所以当一个RegionServer死掉,Hmaster之需要将其任务交给其他RegionServer就好,将原理的存储指向改为新的RegionServer

(4)HregionServer

HregionServer直接对接用户的读写请求,是真正的“干活”的节点。

管理master为其分配的Region

处理来自客户端的读写请求

负责和底层HDFS的交互,存储数据到HDFS

负责Region变大以后的拆分

负责Storefile的合并工作

(5)HDFS

HDFS为Hbase提供最终的底层数据存储服务,同时为Hbase提供高可用(Hlog存储在HDFS)的支持

(6)region

https://yuepengfei-1258421470.cos.ap-shanghai.myqcloud.com/hbase

1)老的Region寻址方式

在Hbase 0.96版本以前,Hbase有两个特殊的表,分别是-ROOT-表和.META.表,其中-ROOT-的位置存储在ZooKeeper中,-ROOT-本身存储了 .META. Table的RegionInfo信息,并且-ROOT-不会分裂,只有一个region。而.META.表可以被切分成多个region。读取的流程如下图所示:

https://yuepengfei-1258421470.cos.ap-shanghai.myqcloud.com/hbase

2)新的Region寻址方式

2层结构其实完全能满足业务的需求,因此0.96版本以后将-ROOT-表去掉了。如下图所示:

https://yuepengfei-1258421470.cos.ap-shanghai.myqcloud.com/hbase

3)Region的拆分策略

  • ConstantSizeRegionSplitPolicy

ConstantSizeRegionSplitPolicy策略是0.94版本之前的默认拆分策略,这个策略的拆分规则是:当region大小达到hbase.hregion.max.filesize(默认10G)后拆分。

这种拆分策略对于小表不太友好,按照默认的设置,如果1个表的Hfile小于10G就一直不会拆分。注意10G是压缩后的大小,如果使用了压缩的话。

如果1个表一直不拆分,访问量小也不会有问题,但是如果这个表访问量比较大的话,就比较容易出现性能问题。这个时候只能手工进行拆分。还是很不方便。

  • IncreasingToUpperBoundRegionSplitPolicy

IncreasingToUpperBoundRegionSplitPolicy策略是Hbase的0.94~2.0版本默认的拆分策略,这个策略相较于ConstantSizeRegionSplitPolicy策略做了一些优化,该策略的算法为:min(r^2*flushSize,maxFileSize ),最大为maxFileSize 。

从这个算是我们可以得出flushsize为128M、maxFileSize为10G的情况下,可以计算出Region的分裂情况如下:

第一次拆分大小为:min(10G,11128M)=128M

第二次拆分大小为:min(10G,33128M)=1152M

第三次拆分大小为:min(10G,55128M)=3200M

第四次拆分大小为:min(10G,77128M)=6272M

第五次拆分大小为:min(10G,99128M)=10G

第五次拆分大小为:min(10G,1111128M)=10G

从上面的计算我们可以看到这种策略能够自适应大表和小表,但是这种策略会导致小表产生比较多的小region,对于小表还是不是很完美。

  • SteppingSplitPolicy

SteppingSplitPolicy是在Hbase 2.0版本后的默认策略,,拆分规则为:If region=1 then: flush size * 2 else: MaxRegionFileSize。

还是以flushsize为128M、maxFileSize为10场景为列,计算出Region的分裂情况如下:

第一次拆分大小为:2*128M=256M

第二次拆分大小为:10G

从上面的计算我们可以看出,这种策略兼顾了ConstantSizeRegionSplitPolicy策略和IncreasingToUpperBoundRegionSplitPolicy策略,对于小表也肯呢个比较好的适配。

4.2 数据的写入

HBase的写入速度大于读取速度

https://yuepengfei-1258421470.cos.ap-shanghai.myqcloud.com/hbase

第1步:Client获取数据写入的Region所在的RegionServer

第2步:请求写Hlog

第3步:请求写MemStore

只有当写Hlog和写MemStore都成功了才算请求写入完成。MemStore后续会逐渐刷到HDFS中。

备注:Hlog存储在HDFS,当RegionServer出现异常,需要使用Hlog来恢复数据。

为了提高Hbase的写入性能,当写请求写入MemStore排序后,不会立即刷盘。而是会等到一定的时候进行刷盘的操作。

注意:写入Hlog日志是顺序写入,效率很高。防止Memstore内的数据丢失。

具体是哪些场景会触发刷盘的操作呢?总结成如下的几个场景:

(1)全局控制

这个全局的参数是控制内存整体的使用情况,当所有memstore占整个heap的最大比例的时候,会触发刷盘的操作。这个参数是hbase.regionserver.global.memstore.upperLimit,默认为整个heap内存的40%。但这并不意味着全局内存触发的刷盘操作会将所有的MemStore都进行输盘,而是通过另外一个参数hbase.regionserver.global.memstore.lowerLimit来控制,默认是整个heap内存的35%。当flush到所有memstore占整个heap内存的比率为35%的时候,就停止刷盘。这么做主要是为了减少刷盘对业务带来的影响,实现平滑系统负载的目的。

(2)MemStore达到上限

当MemStore的大小达到hbase.hregion.memstore.flush.size大小的时候会触发刷盘,默认128M大小

(3)RegionServer的Hlog数量达到上限(hlog是一份,但不是一个)

前面说到Hlog为了保证Hbase数据的一致性,那么如果Hlog太多的话,会导致故障恢复的时间太长,因此Hbase会对Hlog的最大个数做限制。当达到Hlog的最大个数的时候,会强制刷盘。这个参数是hase.regionserver.max.logs,默认是32个。

(4)手工触发

可以通过hbase shell或者java api手工触发flush的操作。

(5)关闭RegionServer触发

在正常关闭RegionServer会触发刷盘的操作,全部数据刷盘后就不需要再使用Hlog恢复数据。

(6)Region使用HLOG恢复完数据后触发

当RegionServer出现故障的时候,其上面的Region会迁移到其他正常的RegionServer上,在恢复完Region的数据后,会触发刷盘,当刷盘完成后才会提供给业务访问。

4.3 数据的读取

(1) 客户端通过zookeeper以及root表和meta表找到目标数据所在的regionserver

(2)联系regionserver查询目标数据

(3)regionserver定位到目标数据所在的region,发出查询请求

(4)region先在memstore中查找,命中则返回

(5)如果在memstore中找不到,则在storefile中扫描(可能会扫描到很多的storefile----bloomfilter布隆过滤器)

说白了,hbase查询为啥那么快:1.寻址方式 2.bloomfilter大大减少了我们扫描的storefile

5. HBase API操作

5.1 基本操作

package com.yuepengfei;

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 org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;

/**
 * @Author day_ue
 * @Date 2019/2/23 20:38
 * @Description TODO
 **/
public class HbaseDemo {

    Configuration conf = null;
    Connection connection = null;

    @Before
    public void init(){

        conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum","node01:2181,node02:2181,node03:2181");

        try {
            connection = ConnectionFactory.createConnection(conf);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("连接创建失败");
        }

    }

    @After
    public void close(){
        if (connection!=null){
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    //创建表
    @Test
    public void createTable() throws IOException {
        //获得操作表的对象
        Admin admin = connection.getAdmin();

        //确定表名字
        TableName student1 = TableName.valueOf("student1");
        //获得表描述器
        HTableDescriptor  desc= new HTableDescriptor(student1);
        //表描述器中添加列族
        HColumnDescriptor info1 = new HColumnDescriptor("info1");
        HColumnDescriptor info2 = new HColumnDescriptor("info2");
        desc.addFamily(info1);
        desc.addFamily(info2);
        //判断表是否存在
        if (!admin.tableExists(student1)){
            //创建表
            admin.createTable(desc);
        }
        admin.close();
    }
    //查看所有表
    @Test
    public void listTable() throws IOException {
        //获取操作表的对象
        Admin admin = connection.getAdmin();
        //获取表列表
        TableName[] tableNames = admin.listTableNames();
        for (TableName tableName : tableNames) {
            System.out.println(tableName);
        }
        admin.close();
    }

    //修改表
    @Test
    public void alterTable() throws IOException {
        //创建操作表的对象
        Admin admin = connection.getAdmin();
        TableName student1 = TableName.valueOf("student1");

        if(admin.tableExists(student1)){
            //修改前先将表置为不可用
            admin.disableTable(student1);

            //获取表描述器
            HTableDescriptor hTableDescriptor = admin.getTableDescriptor(student1);
          //不可以在new,只能采用上面的凡是获取
//            HTableDescriptor hTableDescriptor = new HTableDescriptor(student1);
            //移除一个列族
//            hTableDescriptor.remove("info1");
            //添加一个列族
            HColumnDescriptor info4 = new HColumnDescriptor("info4");
            hTableDescriptor.addFamily(info4);
            //提交修改后的表描述器
            admin.modifyTable(student1,hTableDescriptor);
        }
        admin.close();

    }

    //删除表
    @Test
    public void deleteTable() throws IOException {
        //获取操作表的对象
        Admin admin = connection.getAdmin();
        TableName student1 = TableName.valueOf("student1");

        if(admin.tableExists(student1)){
            //将表置为不可用
            admin.disableTable(student1);
            //删除学生表
            admin.deleteTable(student1);
        }

        admin.close();
    }

    //scan
    @Test
    public void scan() throws IOException {
        //获取表操作对象
        TableName student = TableName.valueOf("student");
        Table table = connection.getTable(student);
        Scan scan = new Scan();
        ResultScanner results = table.getScanner(scan);
        for (Result result : results) {
            byte[] row = result.getRow();
            System.out.println(new String(row));
            Cell[] cells = result.rawCells();
            for (Cell cell : cells) {
                System.out.print("列族:"+new String(CellUtil.cloneFamily(cell))+":/t");
                System.out.print(new String(CellUtil.cloneQualifier(cell))+":/t");
                System.out.println(new String(CellUtil.cloneValue(cell)));
            }
        }
        table.close();
    }
    //put
    @Test
    public void put() throws IOException {
        //当然标准的写法应该是先判断表是否存在
        TableName student = TableName.valueOf("student");
        Table table = connection.getTable(student);


        //创建put对象,参数为row kay
        Put put = new Put(Bytes.toBytes("0002"));
        put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("name"),Bytes.toBytes("zhangsanfeng"));
        put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("age"),Bytes.toBytes("30"));

        table.put(put);

        table.close();

    }

    //get
    @Test
    public void get() throws IOException {

        TableName student = TableName.valueOf("student");
        Table table = connection.getTable(student);
        //获取get对象,传入参数为row key
        Get get = new Get(Bytes.toBytes("0002"));

        Result result = table.get(get);
        Cell[] cells = result.rawCells();
        for (Cell cell : cells) {
            System.out.println("RowKey:"+
                    new String(CellUtil.cloneRow(cell))+"/t"+
                    new String(CellUtil.cloneFamily(cell)) +"/t"+
                    new String(CellUtil.cloneQualifier(cell))+"/t"+
                    new String(CellUtil.cloneValue(cell))
            );
        }

        table.close();

    }
    //delete
    @Test
    public void deleteData() throws IOException {
        //获取表操作对象
        TableName student = TableName.valueOf("student");
        Table table = connection.getTable(student);
        //获取delete操作对象
        Delete delete = new Delete(Bytes.toBytes("0002"));
        delete.addColumns(Bytes.toBytes("info"),Bytes.toBytes("age"));//删除所有版本
        //删除操作
        table.delete(delete);

        table.close();
    }
}

5.2 过滤器

6. ROW Key的设计

HBase是三维有序存储的,通过rowkey(行键),column key(column family和qualifier)和TimeStamp(时间戳)这个三个维度可以对HBase中的数据进行快速定位。

HBase中rowkey可以唯一标识一行记录,在HBase查询的时候,有以下几种方式:

  1. 通过get方式,指定rowkey获取唯一一条记录
  2. 通过scan方式,设置startRow和stopRow参数进行范围匹配
  3. 全表扫描,即直接扫描整张表中所有行记录

6.1 设计原则

(1)长度原则

rowkey是一个二进制码流,可以是任意字符串,最大长度64kb,实际应用中一般为10-100bytes,以byte[]形式保存,一般设计成定长。

建议越短越好,不要超过16个字节,十二三个字节最好,原因如下:

数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;

MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。

(2)散列原则

如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。

(3)唯一原则

必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。

6.2 热点处理

HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。

热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。

设计良好的数据访问模式以使集群被充分,均衡的利用。为了避免写热点,设计rowkey使得不同行在同一个region,但是在更多数据情况下,数据应该被写入集群的多个region,而不是一个。下面是一些常见的避免热点的方法以及它们的优缺点:

(1)加盐

这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。

(2)哈希

哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据。

(3)反转

第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。

反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题

1351023------>3201531

1369301------>1039631

(4)时间戳反转

一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用 Long.Max_Value - timestamp 追加到key的末尾,例如 [key][reverse_timestamp] , [key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。

6.3 常用设计方案

一般来说,为了防止region的分布不均,一般我们要根据数据量进行预分区,一般来说一个regionserver上分一个表的2-3region比较好。key的最前面可以是分区号+|。

7.HBase优化

  • 写入

Auto Flash
通过调用HTable.setAutoFlushTo(false)方法可以将HTable写客户端自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存的时候,才会向HBase服务端发起写请求。默认情况下auto flush是开启的。

Write Buffer
通过调用HTable.setWriteBufferSize(writeBufferSize)方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根基实际写入数据量的多少来设置该值。

WAL Flag
在HBase中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会写到WAL(Write Ahead Log)日志,即HLog,一个RegionServer上的所有Region共享一个HLog,只有当WAL日志写成功后,再接着写MemStore,然后客户端被通知提交数据成功,如果写WAL日志失败,客户端被告知提交失败,这样做的好处是可以做到RegionServer宕机后的数据恢复。对于不太重要的数据,可以在Put/Delete操作时,通过调用Put.setWriteToWAL(false)或Delete.setWriteToWAL(false)函数,放弃写WAL日志,以提高数据写入的性能。
注:如果关闭WAL日志,一旦RegionServer宕机,Put/Delete的数据将会无法根据WAL日志进行恢复。

Compression 压缩
数据量大,边压边写也会提升性能的,毕竟IO是大数据的最严重的瓶颈,哪怕使用了SSD也是一样。众多的压缩方式中,推荐使用SNAPPY。从压缩率和压缩速度来看,性价比最高。HColumnDescriptor hcd = new HColumnDescriptor(familyName);hcd.setCompressionType(Algorithm.SNAPPY);

批量写
通过调用HTable.put(Put)方法可以将一个指定的row key记录写入HBase,同样HBase提供了另一个方法:通过调用HTable.put(List)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高,网络传输RTT高的情景下可能带来明显的性能提升。

多线程并发写
在客户端开启多个 HTable 写线程,每个写线程负责一个 HTable 对象的 flush 操作,这样结合定时 flush 和写 buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被 flush(如1秒内),同时又保证在数据量大的时候,写 buffer 一满就及时进行 flush。

  • 读取

批量读
通过调用 HTable.get(Get) 方法可以根据一个指定的 row key 获取一行记录,同样 HBase 提供了另一个方法:通过调用 HTable.get(List) 方法可以根据一个指定的 row key 列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络 I/O 开销,这对于对数据实时性要求高而且网络传输 RTT 高的情景下可能带来明显的性能提升。

缓存查询结果
对于频繁查询 HBase 的应用场景,可以考虑在应用程序中做缓存,当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不再查询 HBase;否则对 HBase 发起读请求查询,然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑 LRU 等常用的策略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值