HBase

Hbase

概述

概述

  • 一种分布式、可扩展、支持海量数据存储的NoSQL数据库

基本结构

  • Master

    • Table:create,delete,alter
    • RegionServer:分配Regions到每个RegionServer,监控RegionServer状态
  • RegionServer

    • Data:get,put,delete
    • Region:splitRegion,compactRegion

数据模型

  • Name Space
    • 类似于database
    • 自带命名空间
      • hbase:HBase内置的表
      • default:用户默认使用的命名空间
  • Table
    • 类似于表
    • 字段可以动态、按需指定
  • Row
    • 每行数据都由一个RowKey和多个Column(列)组成
    • 按RowKey的字典顺序存储,原则上只能根据RowKey进行检索查询数据
  • Column
    • 由Column Family(列族)和Column Qualifier(列限定符)进行限定
  • Time Stamp
    • 用于标识数据的不同版本
  • Cell
    • {rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元

安装

  • 环境变量

    1
    2
    3
    4
    # vim /etc/profile.d/env.sh
    # HBASE_HOME
    export HBASE_HOME=/hbase
    export PATH=$PATH:$HBASE_HOME/bin
  • 修改hbase-env.sh

    1
    2
    3
    4
    # 是否用内置Zookeeper
    export HBASE_MANAGES_ZK=false
    # 打开下面的注释行
    export HBASE_DISABLE_HADOOP_CLASSPATH_LOOKUP="true"
  • 修改hbase-site.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <configuration>
    <property>
    <name>hbase.rootdir</name>
    <!-- 单点集群 -->
    <value>hdfs://bigdata1:8020/hbase</value>
    <!-- 高可用,来自于hdfs-site.xml的配置dfs.nameservices -->
    <value>hdfs://bigdata:8020/hbase</value>
    </property>

    <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
    </property>

    <property>
    <name>hbase.zookeeper.quorum</name>
    <value>bigdata1,bigdata2,bigdata3</value>
    </property>
    </configuration>
  • 配置regionservers

    1
    2
    3
    bigdata1
    bigdata2
    bigdata3
  • 同步文件

  • 启动

    • 单点启动

      1
      2
      3
      4
      hbase-daemon.sh start master
      hbase-daemon.sh stop master
      hbase-daemon.sh start regionserver
      hbase-daemon.sh stop regionserver
      • 如果集群之间的节点时间不同步,会导致regionserver无法启动,抛出ClockOutOfSyncException异常

        1
        2
        3
        4
        5
        6
        <!-- 设置更大值 -->
        <property>
        <name>hbase.master.maxclockskew</name>
        <value>180000</value>
        <description>Time difference of regionserver from master</description>
        </property>
    • 集群启动

      1
      2
      start-hbase.sh
      stop-hbase.sh
  • 查看页面http://bigdata1:16010

  • 高可用(可用)

    • 在conf目录下创建backup-masters文件,并配置高可用HMaster节点

      1
      2
      3
      bigdata1
      bigdata2
      bigdata3
    • 同步文件

Shell操作

基本操作

1
2
# 进入HBase客户端命令行
hbase shell

namespcae操作

  • 查看

    1
    2
    3
    4
    5
    # 查看当前Hbase中有哪些namespace
    list_namespace

    # 查看namespace
    describe_namespace "namespace名"
  • 增加

    1
    2
    3
    # 创建namespace
    create_namespace "namespace名"
    create_namespace "namespace名", {"属性key"=>"属性value", "属性key"=>"属性value"}
  • 修改

    1
    2
    3
    # 修改namespace的信息(添加或者修改属性)
    alter_namespace "namespace名", {METHOD => 'set', '属性key' => '属性value'}
    # 可以添加或者修改属性
  • 删除

    1
    2
    3
    4
    5
    # 删除属性         
    alter_namespace 'namespace名', {METHOD => 'unset', NAME => '属性key'}

    # 删除namespace,要删除的namespace必须是空的,其下没有表
    drop_namespace "namespace名"

表操作

  • 查看

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 查看当前数据库中有哪些表
    list
    # 扫描查看表数据
    scan 'table名'
    scan 'table名',{STARTROW => 'rowkey值'}
    # [), 若想取1到2之间的数据:start=1,stop=2!
    scan 'table名',{STARTROW => 'rowkey值', STOPROW => 'rowkey值'}

    # 查看表结构
    desc 'namespace名:table名'

    # 查看“指定行”或“指定列族:列”的数据
    get 'namespace名:table名','rowkey值'
    get 'namespace名:table名','rowkey值','列族名:列名'

    # 统计表数据行数
    count 'namespace名:table名'
  • 增加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 创建表,不指定namespace则使用默认namespace
    create 'namespace名:table名',{NAME => '列族名1', VERSION => '要保留的版本数'}
    create 'namespace名:table名',{NAME => '列族名1'},{NAME => '列族名2'}
    create 'namespace名:table名','列族名1','列族名2'

    # 插入数据到表
    put 'namespace名:table名','rowkey值','列族名:列名','值'
    put 'student','1001','info:age','18'
    put 'student','1002','info:name','Janna'
    put 'student','1002','info:sex','female'
    put 'student','1002','info:age','20'
  • 修改

    1
    2
    3
    4
    5
    6
    7
    # 更新指定字段的数据
    put 'namespace名:table名','rowkey值','列族名:列名','值'
    put 'student','1001','info:age','100'

    # 将info列族中的数据存放3个版本:
    alter 'namespace名:table名',{NAME =>'列族名',VERSIONS => '要保留的版本数'}
    get 'student','1001',{COLUMN => 'info:name', VERSIONS => 3}
  • 删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 删除某列族名
    alter 'namespace名:table名', 'delete' => '列族名'

    # 删除某rowkey的全部数据
    deleteall 'namespace名:table名','rowkey值'
    deleteall 'namespace名:table名','rowkey值','列族名:列名'

    # 删除某rowkey的某一列数据,若不指定时间戳则删除最新的一条数据
    delete 'namespace名:table名','rowkey值','列族名:列名'
    delete 'namespace名:table名','rowkey值','列族名:列名','时间戳'

    # 清空表数据
    truncate 'namespace名:table名'
    # 提示:清空表的操作顺序为先disable,然后再truncate。
    # 删除表,首先需要先让该表为disable状态
    disable 'namespace名:table名'
    # 然后才能drop这个表
    drop 'namespace名:table名'
    # 如果直接drop表,会报错:ERROR: Table student is enabled. Disable it first.

原理

RegionServer架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
+------------------------------------------------------------+
| Regin Server |
| +-------------------------------------------------+ +----+ |
| | Regin | |WAL | |
| | +---------------------+ +---------------------+ | | | |
| | | +-----------+ | | +-----------+ | | | | |
| | | store | MemStore | | | store | MemStore | | | | | |
| | | +-----------+ | | +-----------+ | | | | |
| | | +-----------+ | | +-----------+ | | | | |
| | | | StoreFile | | | store | StoreFile | | | | | |
| | | +-----------+ | | +-----------+ | | | | |
| | +---------------------+ +---------------------+ | | | |
| +-------------------------------------------------+ | | |
| +-------------------------------------------------+ | | |
| | Regin | | | |
| | +---------------------+ +---------------------+ | | | |
| | | +-----------+ | | +-----------+ | | | | |
| | | store | MemStore | | | store | MemStore | | | | | |
| | | +-----------+ | | +-----------+ | | | | |
| | | +-----------+ | | +-----------+ | | | | |
| | | | StoreFile | | | store | StoreFile | | | | | |
| | | +-----------+ | | +-----------+ | | | | |
| | +---------------------+ +---------------------+ | | | |
| +-------------------------------------------------+ +----+ |
| +--------------------------------------------------------+ |
| |Block Cache | |
| +--------------------------------------------------------+ |
+------------------------------------------------------------+
  • StoreFile
    • 保存实际数据的物理文件,StoreFile以Hfile的形式存储在HDFS上。每个Store会有一个或多个StoreFile(HFile),数据在每个StoreFile中都是有序的
  • MemStore
    • 写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile
  • WAL
    • 由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中
  • BlockCache
    • 读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询
  • 一个RegionServer可服务于多个Region,有多个Store,一个WAL和一个BlockCache
  • 一个Store对应一个列族,包含MemStore和StoreFile

写流程

  1. Client访问zookeeper,获取hbase:meta表位于哪个Region Server
  2. 访问对应的Region Server获取hbase:meta表,根据读请求的namespace:table/rowkey查询出目标数据位于哪个Region Server中的哪个Region中,将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问
  3. 与目标Region Server进行通讯
  4. 将数据顺序写入(追加)到WAL
  5. 将数据写入对应的MemStore,数据会在MemStore进行排序
  6. 向客户端发送ack
  7. 等达到MemStore的刷写时机后,将数据刷写到HFile

MemStore Flush

  • 每次刷写都会产生新文件

  • 手动刷写命令

    1
    flush 'table名'
  • 刷写机制

    • 当某个memstore的大小达到以下值时,所在region的所有memstore都会刷写

      • hbase.hregion.memstore.flush.size:默认128M

      当memstore的大小达到以下值相乘时,阻止继续往该memstore写数据,直到大小小于以下值相乘的值以下

      • hbase.hregion.memstore.flush.size:默认128M
      • hbase.hregion.memstore.block.multiplier:默认4
    • 当region server中memstore的总大小达到以下值相乘时,region会按照其所有memstore的大小顺序(由大到小)依次进行刷写,直到region server中所有memstore的总大小减小到上述值以下

      • java_heapsize
      • hbase.regionserver.global.memstore.size:默认0.4
      • hbase.regionserver.global.memstore.size.lower.limit:默认0.95

      当region server中memstore的总大小达到以下值时,阻止继续往所有的memstore写数据

      • java_heapsize
      • hbase.regionserver.global.memstore.size:默认0.4
    • 到达自动刷写的时间触发memstore flush。由以下属性进行配置

      • hbase.regionserver.optionalcacheflushinterval:默认1小时

读流程

  1. Client访问zookeeper,获取hbase:meta表位于哪个Region Server
  2. 访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey查询出目标数据位于哪个Region Server中的哪个Region中,并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问
  3. 与目标Region Server进行通讯
  4. 分别在MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并
    • 所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)
    • 使用时间戳范围,RowKey范围,布隆过滤器
  5. 将查询到的新的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache
  6. 将合并后的最终结果返回给客户端

StoreFile Compaction

  • Minor Compaction
    • 将临近的若干个较小的HFile合并成一个较大的HFile,并清理部分过期和删除的数据
    • 每次Flush后尝试
  • Major Compaction
    • 将一个Store下的所有的HFile合并成一个大HFile,并清理所有过期和删除的数据
    • 周期性

Region Split

  • 默认情况,每个Table起初只有一个Region

切分时机

  • Hbase 0.94-:当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize:默认10G
  • Hbase 0.94+:当1个region中的某个Store下所有StoreFile的总大小超过Min(initialSize*R^3, hbase.hregion.max.filesize)
    • 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
      • 第n次split:10GB
  • Hbase 2.0:若当前RegionServer上该表只有一个Region,按2*hbase.hregion.memstore.flush.size分裂,否则按hbase.hregion.max.filesize分裂
    • 第一次按256M拆分,后续按10G拆分

API

环境

1
2
3
4
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
</dependency>

DDL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* Connect:通过ConnectionFactory获取,重量级实现
* Table:DML操作
* Admin:DDL操作
*/
public class HBaseDDLDemo {

private static final Connection connection;
private static final Logger log = LoggerFactory.getLogger(HBaseDemo.class);

static {
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "bigdata1,bigdata2,bigdata3");
try {
connection = ConnectionFactory.createConnection(conf);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* 创建NameSpace
*
* @param nameSpace
* @throws IOException
*/
public static void createNameSpace(String nameSpace) throws IOException {
// 基本判断操作
if (nameSpace == null || "".equals(nameSpace)) {
log.error("NameSpace不能为空");
return;
}
// 获取Admin对象
try (Admin admin = connection.getAdmin()) {
// 调用方法
NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create(nameSpace).build();
admin.createNamespace(namespaceDescriptor);
} catch (IOException e) {
log.error("NameSpace\"{}\"已存在", nameSpace);
}
}

/**
* 添加表
*
* @param nameSpace
* @param table
* @param columnFamilies
* @throws IOException
*/
public static void createTable(String nameSpace, String table, String... columnFamilies) throws IOException {
if (columnFamilies == null || columnFamilies.length == 0) {
log.error("ColumnFamilies不能为空");
return;
}
if (existsTable(nameSpace, table)) {
log.error("Table\"{}:{}\"已存在", nameSpace == null || "".equals(nameSpace) ? "default" : nameSpace, table);
return;
}
// 获取Admin对象
Admin admin = connection.getAdmin();
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(nameSpace, table));
for (String columnFamily : columnFamilies) {
ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily)).build();
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
}
admin.createTable(tableDescriptorBuilder.build());
admin.close();
}

/**
* 删除表
*
* @param nameSpace
* @param table
* @throws IOException
*/
public static void dropTable(String nameSpace, String table) throws IOException {
if (!existsTable(nameSpace, table)) {
log.error("Table\"{}:{}\"不存在", nameSpace == null || "".equals(nameSpace) ? "default" : nameSpace, table);
return;
}
Admin admin = connection.getAdmin();
TableName tableName = TableName.valueOf(nameSpace, table);
admin.disableTable(tableName);
admin.deleteTable(tableName);
admin.close();
}

public static boolean existsTable(String nameSpace, String table) throws IOException {
return connection.getAdmin().tableExists(TableName.valueOf(nameSpace, table));
}

}

DML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
public class HBaseDMLDemo {

private static final Connection connection;

static {
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "bigdata1,bigdata2,bigdata3");
try {
connection = ConnectionFactory.createConnection(conf);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* 添加/修改数据
*
* @param nameSpace
* @param tableName
* @param rowKey
* @param colFamily
* @param col
* @param val
* @throws IOException
*/
public static void putData(String nameSpace, String tableName, String rowKey, String colFamily, String col, String val) throws IOException {
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
Put put = new Put(Bytes.toBytes(rowKey));
put.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(col), Bytes.toBytes(val));
table.put(put);
table.close();
}

/**
* 删除数据 DELETE FAMILY/DELETE COLUMN/DELETE
*
* @param nameSpace
* @param tableName
* @param rowKey
* @param colFamily
* @param col
* @throws IOException
*/
public static void deleteData(String nameSpace, String tableName, String rowKey, String colFamily, String col) throws IOException {
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
Delete delete = new Delete(Bytes.toBytes(rowKey));
if (colFamily != null && !"".equals(colFamily)) {
if (col != null && !"".equals(col)) {
// DELETE
delete.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(col));
// DELETE COLUMN
delete.addColumns(Bytes.toBytes(colFamily), Bytes.toBytes(col));
} else {
// DELETE FAMILY
delete.addFamily(Bytes.toBytes(colFamily));
}
}
table.delete(delete);
table.close();
}

/**
* 获取数据
*
* @param nameSpace
* @param tableName
* @param rowKey
* @param colFamily
* @param col
* @throws IOException
*/
public static void getData(String nameSpace, String tableName, String rowKey, String colFamily, String col) throws IOException {
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
Get get = new Get(Bytes.toBytes(rowKey));
if (colFamily != null && !"".equals(colFamily)) {
if (col != null && !"".equals(col)) {
get.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(col));
} else {
get.addFamily(Bytes.toBytes(colFamily));
}
}
Cell[] cells = table.get(get).rawCells();
for (Cell cell : cells) {
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();
}

/**
* 扫描数据
*
* @param nameSpace
* @param tableName
* @param startRow
* @param stopRow
* @throws IOException
*/
public static void scanData(String nameSpace, String tableName, String startRow, String stopRow) throws IOException {
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
Scan scan = new Scan();
if (startRow != null && !"".equals(startRow)) {
scan.withStartRow(Bytes.toBytes(startRow));
if (stopRow != null && !"".equals(stopRow)) {
scan.withStopRow(Bytes.toBytes(stopRow));
}
}
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
System.out.println(Bytes.toString(CellUtil.cloneRow(cell)) + ":" +
Bytes.toString(CellUtil.cloneFamily(cell)) + ":" +
Bytes.toString(CellUtil.cloneQualifier(cell)) + ":" +
Bytes.toString(CellUtil.cloneValue(cell))
);
}
System.out.println();
}
table.close();
}

}

优化

预分区

  • 每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护

  • 手动设定预分区

    1
    2
    3
    create 'NameSpace名:Table名','Family名',SPLITS => ['预分区范围','预分区范围','预分区范围','预分区范围']
    # (-∞,1000], (1000,2000], (2000,3000], (3000,4000], (4000,+∞)
    create 'staff1','info',SPLITS => ['1000','2000','3000','4000']
  • 生成16进制序列预分区

    1
    2
    3
    create 'NameSpace名:Table名','Family名',{NUMREGIONS => 分区数, SPLITALGO => 'HexStringSplit'}
    # 00000000 ~ ffffffff 分成15份
    create 'staff2','info',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
  • 按照文件中设置的规则预分区

    1
    2
    3
    4
    5
    6
    7
    8
    9
    create 'NameSpace名:Table名','Family名',SPLITS_FILE => '文件名'
    # 创建splits.txt文件内容如下
    aaaa
    bbbb
    cccc
    dddd
    # 执行
    # (-∞,aaaa], (aaaa,bbbb], (bbbb,cccc], (cccc,dddd], (dddd,+∞)
    create 'staff3','info',SPLITS_FILE => 'splits.txt'
  • 使用JavaAPI创建预分区

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 自定义算法,产生一系列Hash散列值存储在二维数组中
    byte[][] splitKeys = 某个散列值函数
    // 创建HbaseAdmin实例
    HBaseAdmin hAdmin = new HBaseAdmin(HbaseConfiguration.create());
    // 创建HTableDescriptor实例
    HTableDescriptor tableDesc = new HTableDescriptor(tableName);
    // 通过HTableDescriptor实例和散列值二维数组创建带有预分区的Hbase表
    hAdmin.createTable(tableDesc, splitKeys);

    // 预分区:['1000','2000','3000','4000']
    byte[][] splitKeys = new byte[4][];
    splitKeys[0] = Bytes.toBytes(1000);
    splitKeys[1] = Bytes.toBytes(2000);
    splitKeys[2] = Bytes.toBytes(3000);
    splitKeys[3] = Bytes.toBytes(4000);
    admin.createTable(tableDescriptorBuilder.build(), splitKeys);

RowKey设计

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

  • 案例

    • 场景:大量的运营商的通话数据

      1
      1388888888(主叫) 13999999999(被叫) 2021-05-14 12:12:12  360 ......
    • 业务:查询某个用户 某天 某月 某年 的通话记录

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

      1
      2
      3
      4
      (-∞  ~  00]
      (00 ~ 01]
      (01 ~ 02]
      .......
    • 分析

      • 若将某个用户某天的数据存到一个分区中. 查某天的数据只需要扫描一个分区,若将某个用户某月的数据存到一个分区中. 查某天某月的数据只需要扫描一个分区

        1
        2
        # 若1388888888_2021-05 % 分区数 = 01
        rowkey01_1388888888_2021-05-14 12:12:12
    • 验证

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      # 查询 1388888888 用户 2020年08月的通话记录
      # 先计算分区号
      1388888888_2020-08 % 50 = 04
      # rowkey
      04_1388888888_2020-08-........
      # scan
      scan "teldata" ,{STARTROW=> '04_1388888888_2020-08' STOPROW=> '04_1388888888_2020-08|'}

      # 查询 1388888888 用户 2020年08月08日的通话记录
      # 先计算分区号
      1388888888_2020-08 % 50 = 04
      # rowkey
      04_1388888888_2020-08-08........
      # scan
      scan "teldata" ,{STARTROW=> '04_1388888888_2020-08-08' STOPROW=> '04_1388888888_2020-08-08|'}

      # 查询 1388888888 用户 2020年08月 和 09月的通话记录
      # 先计算分区号
      1388888888_2020-08 % 50 = 04
      1388888888_2020-09 % 50 = 06
      # rowkey
      04_1388888888_2020-08-........
      06_1388888888_2020-09-........
      # 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日的通话记录
      # 先计算分区号
      1388888888_2020-08 % 50 = 04
      # rowkey
      04_1388888888_2020-08-09........
      04_1388888888_2020-08-09........
      04_1388888888_2020-08-10........
      # scan
      scan "teldata" ,{STARTROW=> '04_1388888888_2020-08-09' STOPROW=> '04_1388888888_2020-08-10|'}

基础优化

  • Zookeeper会话超时时间
    • hbase-site.xml:zookeeper.session.timeout
    • 默认90000毫秒(90s)。当某个RegionServer挂掉,90s之后Master才能察觉到。可适当减小此值,以加快Master响应,可调整至60000毫秒
  • 设置RPC监听数量
    • hbase-site.xml:hbase.regionserver.handler.count
    • 默认30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值
  • 手动控制Major Compaction
    • hbase-site.xml:hbase.hregion.majorcompaction
    • 默认604800000秒(7天), Major Compaction的周期,若关闭自动Major Compaction,可将其设为0
  • 优化HStore文件大小
    • hbase-site.xml:hbase.hregion.max.filesize
    • 默认10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile
  • 优化HBase客户端缓存
    • hbase-site.xml:hbase.client.write.buffer
    • 默认2097152bytes(2M)用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的
  • 指定scan.next扫描HBase所获取的行数
    • hbase-site.xml:hbase.client.scanner.caching
    • 用于指定scan.next方法获取的默认行数,值越大,消耗内存越大
  • BlockCache占用RegionServer堆内存的比例
    • hbase-site.xml:hfile.block.cache.size
    • 默认0.4,读请求比较多的情况下,可适当调大
  • MemStore占用RegionServer堆内存的比例
    • hbase-site.xml:hbase.regionserver.global.memstore.size
    • 默认0.4,写请求较多的情况下,可适当调大

整合

Phoenix

概述

  • 是HBase的开源SQL皮肤。可以使用标准JDBC API代替HBase客户端API来创建表,插入数据和查询HBase数据
  • 特点
    • 容易集成:如Spark,Hive,Pig,Flume和Map Reduce
    • 操作简单:DML命令以及通过DDL命令创建和操作表和版本化增量更改
    • 支持HBase二级索引创建
  • 在phoenix中,schema名,表名,字段名等会自动转换为大写,若要小写,使用双引号,如”student”

安装

  • 官网地址:http://phoenix.apache.org/

  • 解压tar包,复制server包并拷贝到各个节点的hbase/lib

  • 配置环境变量

    1
    2
    3
    4
    # PHOENIX_HOME
    export PHOENIX_HOME=/phoenix
    export PHOENIX_CLASSPATH=$PHOENIX_HOME
    export PATH=$PATH:$PHOENIX_HOME/bin
  • 重启HBase

  • 连接Phoenix

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 厚客户端
    # 默认找本机的zookeeper
    sqlline.py
    sqlline.py bigdata1,bigdata2,bigdata3
    sqlline.py bigdata1,bigdata2,bigdata3:2181
    python3 /phoenix/bin/sqlline.py bigdata1,bigdata2,bigdata3
    # 薄客户端
    # 默认找本机的zookeeper
    queryserver.py start
    sqlline-thin.py
    sqlline-thin.py bigdata1:8765

Shell

  • schema

    1
    2
    3
    4
    5
    <!--默认情况下,在phoenix中不能直接创建schema。需要将如下的参数添加到Hbase中conf目录下的hbase-site.xml   和  phoenix中bin目录下的 hbase-site.xml中-->
    <property>
    <name>phoenix.schema.isNamespaceMappingEnabled</name>
    <value>true</value>
    </property>
    1
    2
    3
    4
    5
    6
    7
    # 创建schema(库)
    create schema if not exists mydb;
    # 在phoenix中,schema名,表名,字段名等会自动转换为大写,若要小写,使用双引号,如"student"
    create schema if not exists "mydb3";

    # 删除schema
    drop schema if exists "mydb3";
  • 1
    2
    3
    4
    5
    6
    # 创建表,必须指定主键对应RowKey
    CREATE TABLE IF NOT EXISTS student(
    id VARCHAR primary key,
    name VARCHAR,
    addr VARCHAR
    );
  • 数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 插入(修改)数据
    upsert into student (id, name, addr) values('1001','zhangsan','beijing');
    upsert into student (id, name, addr) values('1002','lisi','shanghai');
    upsert into student (id, name, addr) values('1002','lixiaosi','shanghai');
    upsert into student (id, name, addr);
    # 会添加空字段,使插入合法
    values('1003','wangwu','shanghai');
    upsert into student (id, name, addr) values('1004',null,null);

    # 查询数据
    select id, name, addr from student;
    select id, name, addr from student where name = 'lixiaosi';

    # 删除数据
    delete from student where id = '1001';
  • 联合主键

    1
    2
    3
    4
    5
    6
    7
    8
    9
    CREATE TABLE IF NOT EXISTS us_population (
    State CHAR(2) NOT NULL,
    City VARCHAR NOT NULL,
    Population BIGINT
    CONSTRAINT my_pk PRIMARY KEY (state, city)
    );

    upsert into us_population values('NY','New York',8143197) ;
    upsert into us_population values('CA','Los Angeles',3844829) ;
  • 表的映射

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    # Hbase中没有表,phoenix中创建表会同时在hbase中也创建表
    # Hbase中有表, 可以在phoenix中创建视图(只读)进行映射
    create 'emp','info'
    put 'emp','1001','info:name','zhangsan'
    put 'emp','1001','info:addr','beijing'

    create view "emp"(
    id varchar primary key,
    "info"."name" varchar,
    "info"."addr" varchar
    );
    # 可以直接查到内容
    select * from "emp";
    select id , "name","addr" from "emp";
    # 只读表修改会报错
    upsert into "emp" values('1002','lisi','shanghai');

    drop view "emp";

    # Hbase中有表, 可以在phoenix中创建表进行映射(读写)
    create table "emp"(
    id varchar primary key ,
    "info"."name" varchar,
    "info"."addr" varchar
    ) COLUMN_ENCODED_BYTES = NONE;
    # 需要关闭编码才能查到内容
    select * from "emp";
    select id , "name","addr" from "emp" ;

    drop table "emp";
  • 数值问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    # phoenix存,phoenix查.没有问题
    # hbase存,hbase查,没有问题
    # phoenix存,hbase查.有问题
    # hbase存,phoenix查,有问题
    # phoenix存/取有符号数值时会取反最高位
    # 若要用数字则用无符号数字或不要穿插操作

    # 有问题
    create table test (
    id varchar primary key,
    name varchar,
    salary integer
    )COLUMN_ENCODED_BYTES = NONE;

    upsert into test values('1001','zs',123456);

    put 'TEST','1002','0:NAME','ls';
    # Shell方式默认Long类型
    put 'TEST','1002','0:SALARY',Bytes.toBytes(456789);

    # 解决
    create table test1 (
    id varchar primary key,
    name varchar,
    # 无符号数字
    salary UNSIGNED_INT
    )COLUMN_ENCODED_BYTES = NONE;

    upsert into test1 values('1001','zs',123456);

    put 'TEST1','1002','0:NAME','ls';
    # Shell方式默认Long类型
    put 'TEST1','1002','0:SALARY',Bytes.toBytes(456789);

API

  • 依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <!-- 薄客户端 -->
    <dependency>
    <groupId>org.apache.phoenix</groupId>
    <artifactId>phoenix-queryserver-client</artifactId>
    <version>6.0.0</version>
    </dependency>
    <!-- 厚客户端 -->
    <dependency>
    <groupId>org.apache.phoenix</groupId>
    <artifactId>phoenix-core</artifactId>
    <version>5.1.2</version>
    <exclusions>
    <exclusion>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    </exclusion>
    </exclusions>
    </dependency>

    <dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.1-b12</version>
    </dependency>
  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public static void main(String[] args) throws SQLException {
    // 薄客户端获取连接
    String url = "jdbc:phoenix:thin:url=http://bigdata1:8765;serialization=PROTOBUF";
    Connection connection = DriverManager.getConnection(url);
    // 厚客户端获取连接
    String url = "jdbc:phoenix:bigdata1,bigdata2,bigdata3:2181";
    Properties props = new Properties();
    props.put("phoenix.schema.isNamespaceMappingEnabled","true");
    Connection connection = DriverManager.getConnection(url, props);
    // 获取连接
    Connection connection = DriverManager.getConnection(url);
    // 编写SQL
    String sql = "select id,name,addr from student";
    // 预编译
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    // 执行SQL
    ResultSet resultSet = preparedStatement.executeQuery();
    // 封装结果
    while (resultSet.next()) {
    System.out.println(resultSet.getString("id") + " : " +
    resultSet.getString("name") + " : " +
    resultSet.getString("addr")
    );
    }
    // 关闭连接
    resultSet.close();
    preparedStatement.close();
    connection.close();
    }

二级索引

  • 二级索引:给RowKey之外的列建索引

  • 配置文件:HBase的HRegionserver节点的hbase-site.xml

    1
    2
    3
    4
    5
    <!-- phoenix regionserver 配置参数-->
    <property>
    <name>hbase.regionserver.wal.codec</name>
    <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
    </property>
  • 全局二级索引

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    # 索引会创建一张索引表,在索引表中将索引列与原表中的rowkey组合起来作为索引表的rowkey
    CREATE TABLE IF NOT EXISTS student(
    id VARCHAR primary key,
    name VARCHAR,
    addr VARCHAR
    );

    # FULL SCAN
    explain select id from student;
    # POINT LOOKUP(必须唯一)
    explain select id from student where id = '1002';
    # FULL SCAN
    explain select id from student where name = 'lixiaosi';

    # 给name字段建索引
    create index idx_student_name on student(name);
    # RANGE SCAN
    explain select id from student where name = 'lixiaosi';
    # POINT LOOKUP
    explain select id ,name from student where id ='1001';
    # RANGE SCAN
    explain select id ,name from student where name = 'lixiaosi';
    # FULL SCAN
    explain select id ,name ,addr from student where name ='lixiaosi';

    # 给name addr建复合索引
    drop index idx_student_name on student;
    create index idx_student_name on student(name,addr);
    # RANGE SCAN
    explain select id ,name ,addr from student where name ='lixiaosi';
    # RANGE SCAN
    explain select id ,name ,addr from student where name ='lixiaosi' and addr = 'beijing';
    # FULL SCAN:需要先查询name
    explain select id ,name ,addr from student where addr = 'beijing';
    # RANGE SCAN:会被优化
    explain select id ,name ,addr from student where addr = 'beijing' and name ='lixiaosi';

    #给name列建索引包含addr列:只需要查,不需要过滤的情况
    drop index idx_student_name on student;
    create index idx_student_name on student(name) include(addr);
    # RANGE SCAN
    explain select id, name, addr from student where name = 'lixiaosi';
  • 本地二级索引

    1
    2
    3
    4
    5
    # 把全局二级索引的表放到原表中
    drop index idx_student_name on student;
    create local index idx_student_name on student(name);
    # RANGE SCAN
    explain select id ,name ,addr from student where name = 'lixiaosi';

Hive

  • 对比

    • Hive
      • 数据分析工具:相当于将HDFS中已经存储的文件在Mysql中做了一个双射关系,以方便使用HQL去管理查询
      • 用于数据分析、清洗:适用于离线的数据分析和清洗,延迟较高
      • 基于HDFS、MapReduce:存储的数据依旧在DataNode上,编写的HQL语句终将是转换为MapReduce代码执行
    • HBase
      • 数据库:是一种面向列族存储的非关系型数据库
      • 用于存储结构化和非结构化的数据:适用于单表非关系型数据的存储,不适合做关联查询,类似JOIN等操作
      • 基于HDFS:数据持久化存储的体现形式是HFile,存放于DataNode中,被ResionServer以region的形式进行管理
      • 延迟较低,接入在线业务使用:可以直线单表大量数据的存储,同时提供了高效的数据访问速度
  • 集成

    • 在hive-site.xml中添加zookeeper的属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <property>
      <name>hive.zookeeper.quorum</name>
      <value>bigdata1,bigdata2,bigdata3</value>
      </property>

      <property>
      <name>hive.zookeeper.client.port</name>
      <value>2181</value>
      </property>
    • 案例1:在hive中建表,对应着在hbase中也建表

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      CREATE TABLE hive_hbase_emp_table(
      empno int,
      ename string,
      job string,
      mgr int,
      hiredate string,
      sal double,
      comm double,
      deptno int)
      -- 存储格式
      STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
      -- HBase字段名映射
      WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno")
      -- HBase表名映射
      TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");

      -- 使用中间表导入数据
      CREATE TABLE emp(
      empno int,
      ename string,
      job string,
      mgr int,
      hiredate string,
      sal double,
      comm double,
      deptno int)
      row format delimited fields terminated by '\t';

      LOAD DATA LOCAL INPATH '/../..' INTO TABLE emp;

      INSERT INTO hive_hbase_emp_table SELECT * FROM emp;
    • 案例2:Hbase中已经有表, hive建表进行关联

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      -- 只能创建外部表
      CREATE EXTERNAL TABLE relevance_hbase_emp(
      empno int,
      ename string,
      job string,
      mgr int,
      hiredate string,
      sal double,
      comm double,
      deptno int)
      STORED BY
      'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
      WITH SERDEPROPERTIES ("hbase.columns.mapping" =
      ":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno")
      TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");

HBase
http://docs.mousse.cc/HBase/
作者
Mocha Mousse
发布于
2025年5月26日
许可协议