Hbase入门

发布时间 2023-06-01 14:28:13作者: strongmore

Hbase简介

Hbase是一种NoSQL数据库,这意味着它不像传统的RDBMS数据库那样支持SQL作为查询语言。Hbase是一种分布式存储的数据库,技术上来讲,它更像是分布式存储而不是分布式数据库,它缺少很多RDBMS系统的特性,比如列类型,辅助索引,触发器,和高级查询语言等待。那Hbase有什么特性呢?如下:

  • 强读写一致,但不是“最终一致性”的数据存储,这使得它非常适合高速的计算聚合
  • 自动分片,通过Region分散在集群中,当行数增长的时候,Region也会自动的切分和再分配
  • 自动的故障转移
  • Hadoop/HDFS集成,和HDFS开箱即用,不用太麻烦的衔接
  • 丰富的“简洁,高效”API,Thrift/REST API,Java API
  • 块缓存,布隆过滤器,可以高效的列查询优化
  • 操作管理,Hbase提供了内置的web界面来操作,还可以监控JMX指标

什么时候用Hbase?

Hbase不适合解决所有的问题:

  • 首先数据库量要足够多,如果有十亿及百亿行数据,那么Hbase是一个很好的选项,如果只有几百万行甚至不到的数据量,RDBMS是一个很好的选择。因为数据量小的话,真正能工作的机器量少,剩余的机器都处于空闲的状态
  • 其次,如果你不需要辅助索引,静态类型的列,事务等特性,一个已经用RDBMS的系统想要切换到Hbase,则需要重新设计系统。
  • 最后,保证硬件资源足够,每个HDFS集群在少于5个节点的时候,都不能表现的很好。因为HDFS默认的复制数量是3,再加上一个NameNode。

Hbase在单机环境也能运行,但是请在开发环境的时候使用。

  • 存储业务数据:车辆GPS信息,司机点位信息,用户操作信息,设备访问信息。。。
  • 存储日志数据:架构监控数据(登录日志,中间件访问日志,推送日志,短信邮件发送记录。。。),业务操作日志信息
  • 存储业务附件:UDFS系统存储图像,视频,文档等附件信息

Hbase架构

image

  • Zookeeper,作为分布式的协调。RegionServer也会把自己的信息写到ZooKeeper中。
  • HDFS是Hbase运行的底层文件系统
  • RegionServer,理解为数据节点,存储数据的。
  • Master RegionServer要实时的向Master报告信息。Master知道全局的RegionServer运行情况,可以控制RegionServer的故障转移和Region的切分。

Hbase数据模型

在Hbase中,有一些术语需要提前了解。如下:

  • Table:Hbase的table由多个行组成
  • Row:一个行在Hbase中由一个或多个有值的列组成。Row按照字母进行排序,因此行健的设计非常重要。这种设计方式可以让有关系的行非常的近,通常行健的设计是网站的域名反转,比如(org.apache.www, org.apache.mail, org.apache.jira),这样的话所有的Apache的域名就很接近。
  • Column:列由列簇加上列的标识组成,一般是“列簇:列标识”,创建表的时候不用指定列标识
  • Column Family:列簇在物理上包含了许多的列与列的值,每个列簇都有一些存储的属性可配置。例如是否使用缓存,压缩类型,存储版本数等。在表中,每一行都有相同的列簇,尽管有些列簇什么东西也没有存。
  • Column Qualifier:列簇的限定词,理解为列的唯一标识。但是列标识是可以改变的,因此每一行可能有不同的列标识
  • Cell:Cell是由row,column family,column qualifier包含时间戳与值组成的,一般表达某个值的版本
  • Timestamp:时间戳一般写在value的旁边,代表某个值的版本号,默认的时间戳是你写入数据的那一刻,但是你也可以在写入数据的时候指定不同的时间戳

HBase 是一个稀疏的、分布式、持久、多维、排序的映射,它以行键(row key),列键(column key)和时间戳(timestamp)为索引。

Hbase与关系型数据库对比

Hbase RDBMS
数据类型 只有字符串 丰富的数据类型
数据操作 增删改查,不支持join 各种各样的函数与表连接
存储模式 基于列式存储 基于表结构和行式存储
数据保护 更新后仍然保留旧版本 替换
可伸缩性 轻易增加节点 需要中间层,牺牲性能

Hbase设计时要考虑的因素

Hbase关键概念:表,rowkey,列簇,时间戳

  • 这个表应该有多少列簇
  • 列簇使用什么数据
  • 每个列簇有有多少列
  • 列名是什么,尽管列名不必在建表时定义,但读写数据是要知道的
  • 单元应该存放什么数据
  • 每个单元存储多少时间版本
  • 行健(rowKey)结构是什么,应该包含什么信息

设计要点

行健设计

关键部分,直接关系到后续服务的访问性能。如果行健设计不合理,后续查询服务效率会成倍的递减。

  • 避免单调的递增行健,因为Hbase的行健是有序排列的,这样可能导致一段时间内大部分写入集中在某一个Region上进行操作,负载都在一台节点上。可以设计成: [metric_type][event_timestamp],不同的metric_type可以将压力分散到不同的region上
  • 行健短到可读即可,因为查询短键不必长键性能好多少,所以设计时要权衡长度。
  • 行健不能改变,唯一可以改变的方式是先删除后插入

列簇设计

列簇是一些列的集合,一个列簇的成员有相同的前缀,以冒号(:)作为分隔符。

  • 现在Hbase不能很好处理2~3个以上的列簇,所以尽可能让列簇少一些,如果表有多个列簇,列簇A有100万行数据,列簇B有10亿行,那么列簇A会分散到很多的Region导致扫描列簇A的时候效率底下。
  • 列簇名的长度要尽量小,一个为了节省空间,另外加快效率,比如d表示data,v表示value

列簇属性配置

HFile数据块,默认是64KB,数据库的大小影响数据块索引的大小。数据块大的话一次加载进内存的数据越多,扫描查询效果越好。但是数据块小的话,随机查询性能更好

create 'mytable',{NAME => 'cf1', BLOCKSIZE => '65536'}

数据块缓存,数据块缓存默认是打开的,如果一些比较少访问的数据可以选择关闭缓存

create 'mytable',{NAME => 'cf1', BLOCKCACHE => 'FALSE'}

数据压缩,压缩会提高磁盘利用率,但是会增加CPU的负载,看情况进行控制

create 'mytable',{NAME => 'cf1', COMPRESSION => 'SNAPPY'}

Hbase表设计是和需求相关的,但是遵守表设计的一些硬性指标对性能的提升还是很有帮助的,这里整理了一些设计时用到的要点。

Hbase安装

下载地址,这里我们使用2.2.6版本。

HBase支持单机安装和分布式安装,这里我们使用伪分布式安装。

修改配置文件

首先修改 hbase-env.sh,在文件末尾直接添加以下配置即可

export JAVA_HOME=/root/test_hadoop/jdk8
export HADOOP_HOME=/root/test_hadoop/hadoop3.2
export HBASE_MANAGES_ZK=false # 表示使用外部的zookeeper
export HBASE_LOG_DIR=/root/test_hbase/logs

接下来修改 hbase-site.xml,修改以下几个参数的值

<property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
</property>
<!-- 本地文件系统tmp目录-->
<property>
    <name>hbase.tmp.dir</name>
    <value>/root/test_hbase/tmp</value>
</property>
<!-- 在分布式情况下, 一定设置为false -->
<property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
</property>xml

还需要向hbase-site.xml中添加下面这些参数

<!--设置HBase表数据,也就是HBase数据在hdfs上的存储根目录-->
<property>
    <name>hbase.rootdir</name>
    <value>hdfs://bigdata01:9000/hbase</value>
</property>
<!--zookeeper集群的URL配置,多个host中间用逗号隔开-->
<property>
    <name>hbase.zookeeper.quorum</name>
    <value>42.192.20.119</value>
</property>
<!--HBase在zookeeper上数据的根目录znode节点-->
<property>
    <name>zookeeper.znode.parent</name>
    <value>/hbase</value>
</property>
<!--设置zookeeper通信端口,不配置也可以,zookeeper默认就是2181-->
<property>
    <name>hbase.zookeeper.property.clientPort</name>
    <value>2181</value>
</property>

启动HBase集群

注意:在启动HBase集群之前一定要确保Hadoop集群和Zookeeper集群已经正常启动了。

bin/start-hbase.sh

执行jps命令,会发现多了一个HMaster进程(HBase集群主节点中的进程)和HRegionServer进程(HBase集群从节点中的进程)。
如果发现HMaster进程和HRegionServer进程都在,说明HBase进程正常启动了

HBase提供有web界面,可以通过浏览器确认集群是否正常启动,端口默认是16010 http://bigdata01:16010/

停止HBase集群

注意:在停止集群进程的时候,要先停HBase集群进程,再停止Zookeeper集群进程,否则HBase停止程序会一直卡住不动。

bin/stop-hbase.sh

Hbase常用Shell命令

进入shell界面

bin/hbase shell 

基础命令

status # 查看集群状态
version # 查看当前版本
whoami # 查看当前用户

DDL命令

create 'student','info','level' # 创建student表,包含info和level两个列簇
list # 查看所有表
exists 'student' # 表是否存在
disable 'student' # 禁用student表,禁用之后不能被使用
enable 'student' # 启用student表
drop 'student' # 删除student表,删除之前必须先禁用
truncate 'student' # 清空表数据
is_disabled 'student' # 检查student表是否被禁用
is_enabled 'student' # 检查student表是否被启用
desc 'student' # 查看表详情信息
alter 'student',{NAME=>'level',VERSIONS=>'3'} # 修改student表的level列簇,VERSIONS改为3,表示可以保存3个版本的数据
alter 'student','about' # 对student表增加列簇about
alter 'student',{NAME='about',METHOD='delete'} # 对student表删除列簇about,一个表最少包含一个列簇

增删改查命令

put 'student','lisi','info:sex','man' # 对student表增加lisi这个行键,对info列簇增加sex列,值为man
put 'student','lisi','info:age','21' # 对student表增加lisi这个行键,对info列簇增加age列,值为21
put 'student','lisi','level:class','A' # 对student表增加lisi这个行键,对level列簇增加class列,值为A
get 'student','lisi' # 查看指定行的信息,包含所有列簇
get 'student','lisi','info' # 查看指定行的信息,仅包含info列簇
get 'student','lisi','info:age' # 查看指定行的信息,仅包含info列簇的age列
count 'student' # 统计行数
scan 'student' # 扫描表数据,就是查询多行
delete 'student','lisi','info:age' # 删除lisi这一行info列簇的age列信息,仅能删除一个列簇
deleteall 'student','lisi' # 删除lisi这一行,可以删除多个列簇

命名空间

  • hbase:系统命名空间,包含 HBase 内部表。
  • default:如果在创建表时没有显式指定命名空间,默认会在此命名空间创建表。
list_namespace # 查询所有命名空间 类似mysql的database
create_namespace 'n1' # 创建命名空间
create 'n1:student','lisi','info' # 在n1命名空间下创建表student
list # 查询所有命名空间下的表
list_namespace_tables 'n1' # 查询n1命名空间下的表

JavaAPI操作

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>2.2.6</version>
</dependency>
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.filter.BinaryComparator;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * 通过Java操作表数据及表的创建删除操作
 */
public class TestHbaseClient {

    public static void main(String[] args) throws IOException {
//        testPut();
//        testGet();
//        testCreateTable();
        testScan();
    }

    private static void testCreateTable() throws IOException {
        Connection connection = createConnection();
        Admin admin = connection.getAdmin();
        ColumnFamilyDescriptor infoColumnFamily = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("info"))
                .setMaxVersions(2)
                .build();
        TableDescriptor userTable = TableDescriptorBuilder.newBuilder(TableName.valueOf("user"))
                .setColumnFamilies(Collections.singletonList(infoColumnFamily))
                .build();
        admin.createTable(userTable);
        admin.close();
        connection.close();
    }

    private static void testScan() throws IOException {
        Connection connection = createConnection();
        Table studentTable = connection.getTable(TableName.valueOf("student"));
        Scan scan = new Scan();
        //指定查询范围,优化查询性能
        scan.withStartRow(Bytes.toBytes(""));
        scan.withStopRow(Bytes.toBytes(""));
        RowFilter filter = new RowFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("lisi")));
        //根据rowkey过滤
        scan.setFilter(filter);
        ResultScanner resultScanner = studentTable.getScanner(scan);
        for (Result result : resultScanner) {
            List<Cell> cells = result.listCells();
            for (Cell cell : cells) {
                byte[] family = CellUtil.cloneFamily(cell);
                byte[] qualifier = CellUtil.cloneQualifier(cell);
                byte[] value = CellUtil.cloneValue(cell);
                System.out.println("列簇:" + new String(family) + ",列:" + new String(qualifier) + ",value:" + new String(value));
            }
        }
        studentTable.close();
        connection.close();
    }

    private static void testGet() throws IOException {
        Connection connection = createConnection();
        Table studentTable = connection.getTable(TableName.valueOf("student"));
        //指定rowkey
        Get getReq = new Get(Bytes.toBytes("jack"));
        //指定列簇 列名和具体值
        getReq.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"));
        Result result = studentTable.get(getReq);
        String rowKey = new String(result.getRow());
        System.out.println("rowKey:" + rowKey);
        List<Cell> cells = result.listCells();
        for (Cell cell : cells) {
            byte[] family = CellUtil.cloneFamily(cell);
            byte[] qualifier = CellUtil.cloneQualifier(cell);
            byte[] value = CellUtil.cloneValue(cell);
            System.out.println("列簇:" + new String(family) + ",列:" + new String(qualifier) + ",value:" + new String(value));
        }
        byte[] infoAgeValue = result.getValue(Bytes.toBytes("info"), Bytes.toBytes("age"));
        System.out.println("infoAgeValue:" + new String(infoAgeValue));
        studentTable.close();
        connection.close();
    }

    private static void testPut() throws IOException {
        Connection connection = createConnection();
        Table studentTable = connection.getTable(TableName.valueOf("student"));
        //指定rowkey
        Put putReq = new Put(Bytes.toBytes("jack"));
        //指定列簇 列名和具体值
        putReq.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("35"));
        studentTable.put(putReq);
        studentTable.close();
        connection.close();
    }

    private static Connection createConnection() throws IOException {
        Configuration conf = HBaseConfiguration.create();
        //zookeeper集群地址
        conf.set("hbase.zookeeper.quorum", "ip:2181");
        //在zookeeper上数据的根目录znode节点
        conf.set("zookeeper.znode.parent", "/hbase");
        return ConnectionFactory.createConnection(conf);
    }

}

参考

入门Hbase,看这一篇就够了
HBase集群安装部署
美团一面:为什么选用 Hbase,Hbase 和 MySQL 的区别是什么?