HBase

HBase

概念

HBase是一种分布式、可扩展、支持海量数据存储的NoSQL(non-relational或者not only sql)数据库

​ NoSQL泛指非关系型数据库,与关系型数据库不同的是,它们不保证ACID特性

​ NoSQL易扩展,具有较高的读写性能

结构

有列族、row key、region、store等概念。

  • 每行数据都由一个RowKey和多个列组成,数据按RowKey的字典顺序存储

  • 每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定

  • HBase可以比较方便地添加列

  • HBase支持对数据的增删改(HDFS是不可以的)

    • 通过追加的方式,把修改/删除的信息放到末尾。用TimeStamp记录和Type
    • 先“骗”用户,让用户感觉已经修改成功了,再在之后适当的时机,把数据修改过
  • StoreFile保存实际物理文件,以Hfile的形式存储在HDFS上,数据在StoreFile中都是有序的

  • MeMStore是写缓存,HFile有序,所以数据先存储在MeMStorez中,排好序后再刷写进HFile,每次刷写都生成一个新的HFile

  • WAL(Write Ahead Log),在数据写入HFile前,数据都在内存中,为了避免数据丢失的问题,数据要先写在WAL日志文件中,再写入MeMStore.系统出现故障时,可根据日志文件重建

  • BlockCache,读缓存,每次查询的数据都会缓存在BlockCache中。

写流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ma9LUwTn-1680437254986)(.\Hbase写流程.png)]

  1. Client先访问zookeeeper,获取hbase:meta表位于哪个RegionServer
  2. 访问对应的RegionServer,获取hbase:meta表,根据读请求的namespace:table/rowkey查询出目标数据位于哪个RegionServer中的哪个Region。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache
  3. 与目标regionServer进行通讯
  4. 将数据顺序写入(追加)到WAL
  5. 将数据写入对应的MemStore,数据会在MemStore进行排序
  6. 向客户端发送ack
  7. 等达到MemStore的刷写时机后,把数据刷写到HFile
MemStore刷写时机
  1. 当某个memstore的大小达到了hbase.hregion.memstore.flush.size(默认128M),其所在region的所有memstore都会刷写

    当memstore的大小达到了:hbase.hregion.memstore.flush.size(默认128M)、hbase.hregion.memstore.block.multiplier(默认4)时,回阻止继续向该memstore写数据

  2. 当region server中的memstore的总大小达到了java_heapsize, hbase.regionserver.global.memstore.size(默认值0.4),hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)

    region会按其所有memstore的大小顺序(从大到小)依次进行刷写,直到region server中所有memstore的总大小减小到小于上面两个值。

    当region server中的memstore总大小达到java_heapsize, hbase.regionserver.global.memstore.size(默认值0.4)时,阻止继续往所有的memstore写数据

  3. 到达自动刷新时间,也会触发memstore flush。自动刷新时间间隔由hbase.regionserver.optionalcacheflushinterval(默认1小时)配置

  4. 当WAL文件数量达到hbase.regionserver.max.logs时,region会按时间顺序依次进行刷写,知道WAL文件数量减小到hbase.regionserver.max.logs以下(现在不用设置了,最大值32)

读流程
  1. Client先访问zookeeper,获取hbase:meta表位于哪个RegionServer
  2. 访问对应的RegionServer,获取hbase:meta表,根据读请求的namespace:table.rowker,查询出目标数据位于哪个region server中的哪个region。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问
  3. 与目标RegionServer进行通讯
  4. 分别在MemStore和StoreFile(HFile)中查询目标数据,并将查到的数据(同一条数据的不同版本或者不同类型)进行合并
  5. 将查询到的新的数据块(Block,HFile数据存储单元,默认大小64KB)缓存到Block Cache
  6. 将合并后的最终数据返回到客户端
StoreFile Compaction

每次memstore刷写都会生成一个新的HFile,且每个字段的不同版本和不同类型有可能分布在不同的HFile中,因此查询时需要遍历所有HFile。为减少HFile的个数以及清理掉过期和删除的数据,会进行StoreFile Compaction。

Store Compaction分为:Minor Compaction和Major Compaction。Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,并清理掉部分过期和删除的数据。Major Compaction会将一个Store下的所有HFile合并成一个大的HFile,并清理掉所有过期和删除的数据

Region Split

默认情况下,每个Table起初只有一个Region,随着数据不断写入,Region自动进行拆分。刚拆分时,两个子Region都位于当前RegionServer,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server

Region Split时机:一个region中的某个store下所有StoreFile的总大小超过Min(initialSize*R^3 ,hbase.hregion.max.filesize"),该Region就会进行拆分。其中initialSize的默认值为2*hbase.hregion.memstore.flush.size,R为当前Region Server中属于该Table的Region个数

具体的切分策略为:

第一次split:1^3 * 256 = 256MB

第二次split:2^3 * 256 = 2048MB

第三次split:3^3 * 256 = 6912MB

第四次split:4^3 * 256 = 16384MB > 10GB,因此取较小的值10GB

后面每次split的size都是10GB了。

Hbase 2.0引入了新的split策略:如果当前RegionServer上该表只有一个Region,按照2 * hbase.hregion.memstore.flush.size分裂,否则按照hbase.hregion.max.filesize分裂

使用

  • 在启动前,要保证集群间的时间节点时间同步,否则regionserver可能无法启动(或者修改hbase.master.maxlockskew的值,将其增大)

  • 在启动HBase前需要先启动Zookeeper。Zookeeper为HBase提供了master高可用,RegionServer监控、元数据入口以及集群配置的维护等工作

  • HDFS为HBase提供最终的底层数据存储服务

  • HBase中用HMaster监控HRegionServer的生命周期,均衡RegionServer的负载,需要配置HMaster的高可用,两种方式:

    • 1.启动多个节点时,都单点启动master(hbase-daemon.sh start master),后起的就是backup
    • 2.在conf目录下创建backup-masters文件,在里面写后备节点,如hadoop103
基本操作

进入HBase客户端:hbase shell

查看namespace:list_namespace

创建namespace:create_namespace “test01”, {“author”=>“jojo”, “create_time”=>“2023-02-13 08:08:08”}

查看namespace:describe_namespace “test01”

修改namespace(只能修改属性):alter_namespace “test01”, {“create_time”=>“2023-03-13 08:08:08”}

删除namespace:drop_namespace “test01”(只有namespace下没有表时可以删除)

创建表create ‘student’,‘info’ create ‘mydb:student’,‘info’

插入数据

​ put ‘mydb:student’,‘1001’,‘info:sex’,‘female’

​ put ‘mydb:student’,‘1001’,‘info:age’,‘20’

​ put ‘mydb:student’,‘1002’,‘info:age’,‘23’

​ put ‘mydb:student’,‘1002’,‘info:name’,‘jojo’

​ put ‘mydb:student’,‘1002’,‘info:sex’,‘male’

查看数据:scan ‘mydb:student’

ROW COLUMN+CELL
1001 column=info:age, timestamp=1676288716119, value=20
1001 column=info:sex, timestamp=1676288695862, value=female
1002 column=info:age, timestamp=1676288725028, value=23
1002 column=info:name, timestamp=1676288743436, value=jojo
1002 column=info:sex, timestamp=1676288754795, value=male
2 row(s)

查看表结构:describe ‘mydb:student’

更新表数据:put ‘mydb:student’,‘1002’,‘info:name’,‘yoyo’

查看指定行/列数据:get ‘mydb:student’,‘1002’ get ‘mydb:student’,‘1001’,‘info:name’

统计行数:count ‘mydb:student’ 2

删除某rowkey的某列数据:delete ‘mydb:student’,‘1002’,‘info:name’

删除某rowkey的全部数据:deleteall ‘mydb:student’,‘1001’

清空表数据:truncate ‘mydb:student’【步骤:Disabling table… ===> Truncating table…】

disable 表:disable ‘mydb:student’

删除表之前需要先disable,再drop ‘mydb:student’

API调用
DDL
public class Hbase_DDL {
     private static Connection connection;

    static {
        Configuration configuration = HBaseConfiguration.create();
//        和hbase-site.xml里的配置一个套路
        configuration.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");
        try {
            connection = ConnectionFactory.createConnection(configuration);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
//        createNameSpace("mydb1");
//        createTable("mydb1","t1","info1","info2");
      dropTable("mydb1","t1");
    }


//    创建namespace
    public static void createNameSpace(String namespace) throws IOException {
//      基本判空操作
        if (namespace==null||namespace.equals(""))
            System.err.println("name space 名字不能为空");
        //     获取admin对象
        Admin admin = connection.getAdmin();
        NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);
        NamespaceDescriptor namespaceDescriptor = builder.build();
        //        调方法
        try {
            admin.createNamespace(namespaceDescriptor);
            System.out.println(namespace+"创建成功");
        }catch (NamespaceExistException e){
            System.err.println(namespace+"已存在");
        }

    }

//    判断表是否存在
    public static boolean isTableExist(String nameSpaceName,String tableName) throws IOException {
        boolean b;
        Admin admin = connection.getAdmin();
        b= admin.tableExists(TableName.valueOf(nameSpaceName,tableName));
        admin.close();
        System.out.println(nameSpaceName+tableName+"是否存在:"+b);
        return b;
    }

//  创建表
    public static void createTable(String nameSpace,String tableName,String ...cfs) throws IOException {

        if (isTableExist(nameSpace,tableName)){
            System.out.println(nameSpace+tableName+"表已存在");
            return;
        }
        Admin admin = connection.getAdmin();
        TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(nameSpace,tableName));

        if (cfs==null || cfs.length<=1){
            System.err.println("至少指定一个列族");
            return;
        }
        for (String cf:cfs){
            ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder=ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf));
            ColumnFamilyDescriptor build = columnFamilyDescriptorBuilder.build();
            tableDescriptorBuilder.setColumnFamily(build);
        }
        TableDescriptor tableDescriptor = tableDescriptorBuilder.build();
        admin.createTable(tableDescriptor);
        System.out.println(nameSpace+tableName+"创建成功");
        admin.close();
    }

//    删除表
    public static void dropTable(String nameSpaceName,String tableName) throws IOException {
        if (!isTableExist(nameSpaceName,tableName)){
            System.err.println(nameSpaceName+tableName+"表不存在,无法删除");
            return;
        }
        Admin admin = connection.getAdmin();
        admin.disableTable(TableName.valueOf(nameSpaceName,tableName));
        admin.deleteTable(TableName.valueOf(nameSpaceName,tableName));
        System.out.println(nameSpaceName+tableName+"已删除");
        admin.close();
    }
}
DML
public class HBase_DML {
    private static Connection connection;
    static {
        Configuration configuration = HBaseConfiguration.create();
//        和hbase-site.xml里的配置一个套路
        configuration.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");
        try {
            connection = ConnectionFactory.createConnection(configuration);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        scanData("mydb","student");
    }

//插入数据
    public static void insertData(String nameSpace,String tableName,String rowKey,
                                  String cf,String cl,String value) throws IOException {
//         put 'mydb:student','1001','info:sex','female'
        Admin admin = connection.getAdmin();

        boolean b =admin.tableExists(TableName.valueOf(nameSpace,tableName));
        if (b){
            Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
            Put put = new Put(Bytes.toBytes(rowKey));
            put.addColumn(Bytes.toBytes(cf),Bytes.toBytes(cl),Bytes.toBytes(value));
            table.put(put);
            table.close();
            System.out.println("已插入数据:"+nameSpace+" "+tableName+" "+cf+" "+cl+" "+value);
        }
        admin.close();
    }

//    删除数据
    public static void delData(String nameSpace,String tableName,String rowKey,
                               String cf,String cl) throws IOException {
//        delete 'mydb:student','1002','info:name'
//        deleteall 'mydb:student','1001'
        Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));

        Delete delete = new Delete(Bytes.toBytes(rowKey));
//        删除某个数据
        delete.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cl));
//        删除某个列族
        delete.addFamily(Bytes.toBytes(cf));

        table.delete(delete);
        table.close();
    }

//    获取数据
    public static void getData(String nameSpace,String tableName,String rowKey,
                               String cf,String cl) throws IOException {
        Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
        Get get = new Get(Bytes.toBytes(rowKey));
        get.addColumn(Bytes.toBytes(cf),Bytes.toBytes(cl));
        Result result = table.get(get);
        Cell[] cells = result.rawCells();
        for (Cell cell : cells) {
            String cellString = Bytes.toString(CellUtil.cloneRow(cell))  + " : " +
                    Bytes.toString(CellUtil.cloneFamily(cell)) + " : " +
                    Bytes.toString(CellUtil.cloneQualifier(cell))+ " : " +
                    Bytes.toString(CellUtil.cloneValue(cell));
            System.out.println(cellString);
        }
        table.close();

    }
//    扫描数据
    public static void scanData(String nameSpace,String tableName) throws IOException {

        Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
        Scan scan = new Scan();
        ResultScanner results = table.getScanner(scan);
        for (Result result : results) {
            for (Cell cell : result.rawCells()) {
                System.out.println(Bytes.toString(CellUtil.cloneRow(cell))+":"+
                        Bytes.toString(CellUtil.cloneFamily(cell))+":"+
                        Bytes.toString(CellUtil.cloneQualifier(cell))+":"+
                        Bytes.toString(CellUtil.cloneValue(cell)));
            }
        }
        table.close();
    }
}
预分区

分为四个分区:

create ‘staff1’,‘info’,SPLITS => [‘1000’,‘2000’,‘3000’,‘4000’]

按16进制预分区为15个分区

create ‘staff2’,‘info’,{NUMREGIONS => 15, SPLITALGO => ‘HexStringSplit’}
Created table staff2

在这里插入图片描述

按文件分区:

建一个split.txt【 aaaa bbbb cccc dddd 】

在该文件所在的目录下启动hbase

create ‘staff3’,‘info’,SPLITS_FILE => ‘splits.txt’

预分区API
    public static void createTableWithRegions(String nameSpaceName, String tableName,String ... cfs ) throws IOException {
        if(existsTable(nameSpaceName, tableName)){
            System.err.println((nameSpaceName == null || nameSpaceName.equals("")? "default" : nameSpaceName)  + ":" + tableName  + "表已经存在");
            return ;
        }
        Admin admin = connection.getAdmin() ;
        TableDescriptorBuilder tableDescriptorBuilder =
                TableDescriptorBuilder.newBuilder(TableName.valueOf(nameSpaceName,tableName));

        if(cfs == null || cfs.length < 1){
            System.err.println("至少指定一个列族");
            return ;
        }

        for (String cf : cfs) {
            ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder =
                    ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf));
            ColumnFamilyDescriptor columnFamilyDescriptor =
                    columnFamilyDescriptorBuilder.build();

            tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
        }

        TableDescriptor tableDescriptor = tableDescriptorBuilder.build();

        byte [][] splitkeys = new byte[4][];

        //['1000','2000','3000','4000']
        splitkeys[0] = Bytes.toBytes("1000");
        splitkeys[1] = Bytes.toBytes("2000");
        splitkeys[2] = Bytes.toBytes("3000");
        splitkeys[3] = Bytes.toBytes("4000");

        admin.createTable(tableDescriptor,splitkeys);

        admin.close();
    }
rowkey的设计

原则:唯一性、散列性、长度

例子:

 1388888888(主叫) 13999999999(被叫) 2021-05-14 12:12:12  360 ......

业务: 查询某个用户 某天 某月 某年 的通话记录

预分区: 预计规划50个分区 .

-∞ ~ 00|
00| ~ 01|
01| ~ 02|

千万不能拿某个用户来分区,因为有的用户通话量大,有的通话量很少,会造成数据不均衡

分析: 假如将某个用户某天的数据存到一个分区中. 查某天的数据只需要扫描一个分区
加入将某个用户某月的数据存到一个分区中. 查某天 某月的数据只需要扫描一个分区. √

rowkey: 01_1388888888_2021-05-14 12:12:12 -> 1388888888_2021-05 % 分区数 = 01
01_1388888888_2021-05-15 12:12:12 -> 1388888888_2021-05 % 分区数 = 01
01_1388888888_2021-05-16 12:12:12
01_1388888888_2021-05-17 12:12:12
先算应该进哪个分区,再把该进的分区拼到前面,能保证它一定能进那个分区

​ 03_1377777777_2021-05-16 12:12:12 -> 1377777777_2021-05 % 分区数 = 03

验证:
查询 1388888888 用户 2020年08月的通话记录

  1. 先计算分区号
    1388888888_2020-08 % 50 = 04
  2. rowkey
    04_1388888888_2020-08-…
  3. scan
    scan “teldata” ,{STARTROW=> ‘04_1388888888_2020-08’ STOPROW=> ‘04_1388888888_2020-08|’}

查询 1388888888 用户 2020年08月08日的通话记录

  1. 先计算分区号
    1388888888_2020-08 % 50 = 04
  2. rowkey
    04_1388888888_2020-08-08…
  3. scan
    scan “teldata” ,{STARTROW=> ‘04_1388888888_2020-08-08’ STOPROW=> ‘04_1388888888_2020-08-08|’}

查询 1388888888 用户 2020年08月 和 09月的通话记录

  1. 先计算分区号
    1388888888_2020-08 % 50 = 04
    1388888888_2020-09 % 50 = 06
  2. rowkey
    04_1388888888_2020-08-…
    06_1388888888_2020-09-…
  3. scan
    scan “teldata” ,{STARTROW=> ‘04_1388888888_2020-08’ STOPROW=> ‘04_1388888888_2020-08|’}
    scan “teldata” ,{STARTROW=> ‘06_1388888888_2020-09’ STOPROW=> ‘06_1388888888_2020-09|’}

查询 1388888888 用户 2020年08月09日 和 10日的通话记录

  1. 先计算分区号
    1388888888_2020-08 % 50 = 04
  2. rowkey
    04_1388888888_2020-08-09…
    04_1388888888_2020-08-09…
    04_1388888888_2020-08-10…
  3. scan
    scan “teldata” ,{STARTROW=> ‘04_1388888888_2020-08-09’ STOPROW=> ‘04_1388888888_2020-08-10|’}
内存优化

1.Zookeeper会话超时时间

hbase-site.xml

属性:zookeeper.session.timeout

解释:默认值为90000毫秒(90s)。当某个RegionServer挂掉,90s之后Master才能察觉到。可适当减小此值,以加快Master响应,可调整至60000毫秒。

2.设置RPC监听数量

hbase-site.xml

属性:hbase.regionserver.handler.count 解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。

3.手动控制Major Compaction

hbase-site.xml

属性:hbase.hregion.majorcompaction

解释:默认值:604800000秒(7天), Major Compaction的周期,若关闭自动Major Compaction,可将其设为0

4.优化HStore文件大小

hbase-site.xml

属性:hbase.hregion.max.filesize 解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。

5.优化HBase客户端缓存

hbase-site.xml

属性:hbase.client.write.buffer 解释:默认值2097152bytes(2M)用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。

6.指定scan.next扫描HBase所获取的行数

hbase-site.xml

属性:hbase.client.scanner.caching 解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。

7.BlockCache占用RegionServer堆内存的比例

hbase-site.xml

属性:hfile.block.cache.size

解释:默认0.4,读请求比较多的情况下,可适当调大

8.MemStore占用RegionServer堆内存的比例

hbase-site.xml

属性:hbase.regionserver.global.memstore.size

解释:默认0.4,写请求较多的情况下,可适当调大

Hive和HBase对比

Hive
  1. 数据分析工具,本质上是将HDFS中已经存储的文件在MySql中做了一个双射关系,以方便使用HQL去管理查询
  2. 适合于离线的数据分析与清洗,延迟较高
  3. Hive的存储依旧在DataNode上,编写的HQL语句会转化为MR代码执行(Hive基于HDFS和MR)
HBase
  1. 数据库, 面向列族存储的非关系型数据库
  2. 用于存储结构化和非结构化的数据,适合于单表非关系型数据存储,不适合做关联查询
  3. 数据持久化存储的体现形式是HFile,存放于DataNode,被RegionServer以region的形式进行管理(HBase基于HDFS)
  4. 延迟较低,接入在线业务使用

hbase-site.xml

属性:hfile.block.cache.size

解释:默认0.4,读请求比较多的情况下,可适当调大

8.MemStore占用RegionServer堆内存的比例

hbase-site.xml

属性:hbase.regionserver.global.memstore.size

解释:默认0.4,写请求较多的情况下,可适当调大

Hive和HBase对比

Hive
  1. 数据分析工具,本质上是将HDFS中已经存储的文件在MySql中做了一个双射关系,以方便使用HQL去管理查询
  2. 适合于离线的数据分析与清洗,延迟较高
  3. Hive的存储依旧在DataNode上,编写的HQL语句会转化为MR代码执行(Hive基于HDFS和MR)
HBase
  1. 数据库, 面向列族存储的非关系型数据库
  2. 用于存储结构化和非结构化的数据,适合于单表非关系型数据存储,不适合做关联查询
  3. 数据持久化存储的体现形式是HFile,存放于DataNode,被RegionServer以region的形式进行管理(HBase基于HDFS)
  4. 延迟较低,接入在线业务使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值