HBase

HBase

https://www.cnblogs.com/zhh567/p/17275625.html

用于存储数十亿行数百万列的大数据的kv数据库,基于Google的bigtable论文。Bigtable是一个稀疏的分布式的持久的多维排序map。该map以行键、列键、时间戳作为索引,对应的值为一个序列化的字节数组。

数据存储的逻辑架构:不同Region放在不同节点,不同store用于拆分为不同文件夹

物理结构:以KV形式存储在hdfs,使用时间戳标记版本,不断追加新数据

  • namespace
    hbase使用namespace区分数据,默认自带habse和default两个命名空间。hbase命名空间用于存放HBase内置表,default是用户默认使用命名空间
  • Table
    类似RMDB的表概念,hbase定义表时只要声明列族即可,无需声明具体的列。因为数据稀疏,所以写数据时字段可动态指定
  • Row
    每行数据有一个RowKey和多个Column组成,按照字典序存储,查询时只能根据RowKey检索,所以RowKey设计很重要
  • Column
    每个列都有 Column Family 和 Column Qualifier,建表时指定列族,存储数据时再指定列名
  • TimeStamp
    标识数据不同版本
  • Cell
    {rowKey, column Family: column Qualifer, timestamp} 组成的唯一单元,以字节码形式存储,对应逻辑表中的一个小格

Master,实现类为HMaster:

  1. 监控集群中所有RegionServer
  2. 管理元数据表格 hbase:meta,接收用户对表格创建修改删除等命令
  3. 监控region是否需要负载均衡,故障转移,region拆分
  4. 启动多个监控线程:
    1. LoadBalancer:周期性监控region分布再regionServer上是否均衡
    2. CatalogJanitor 元数据管理器:定期检查和清理 hbase:meta 数据
    3. MasterProcWAL master 预写日志处理器:将master要执行的任务记录到预写日志WAL中,master宕机时让backupMaster读取并继续

Region Server,实现类为 hRegionServer:

  1. cell的处理
  2. 拆分合并 region 的实际执行者,由master监控

zookeeper:
实现master高可用、记录RegionServer信息、存储meta位置信息。对数据读写操作直接访问zookeeper,2.3版本后推出Master Registry,客户端可直接访问master以减轻zookeeper压力

安装

  1. 下载,解压
  2. 配置环境变量 export HBASE_HOME=/...
  3. 修改配置文件 conf/hbase-env.sh
    export JAVA_HOME=/usr/local/jdk8 # 手动指定JAVA
    export HBASE_MANAGES_ZK=false    # 不使用hbase自带的zk
    export HBASE_PID_DIR=/usr/local/hbase-2.5.3-hadoop3/pids # 可能需要
  4. 修改文件 conf/hbase-site.xml
    <property>
        <name>hbase.cluster.distributed</name>
        <value>true</value>
      </property>
      <property>
        <name>hbase.zookeeper.quorum</name>
        <value>ubuntu01:2181</value>
      </property>
      <!-- 默认存储在zk的临时文件夹,重启后丢失,要指定目录 -->
      <property>
        <name>hbashe.zookeeper.property.dataDir</name>
        <value>/hbase</value>
      </property>
      <property>
        <name>hbase.rootdir</name>
        <value>hdfs://ubuntu01:9000/hbase</value> <!--hdfs的链接-->
      </property>
  5. 修改 conf/regionservers
    ubuntu01
    ubuntu02
    ubuntu03
  6. 解决HBase和Hadoop的log4j冲突问题,修改HBase jar包名,使其自动调用hadoop的
    mv lib/client-facing-thirdparty/slf4j-api-1.7.33.jar lib/client-facing-thirdparty/slf4j-api-1.7.33.jar.bak
  7. 分发到不同节点

启动停止:

bin/hbase-daemon.sh start master      # 启动master节点
bin/hbase-daemon.sh start regionserver# 启动region节点
bin/start-hbase.sh   # 启动整个集群
bin/stop-hbase.sh    # 关闭整个集群

访问 http://master:16010 看到hbase的web界面

高可用

支持HMaster的备份以实现高可用。

  1. 关闭HBase集群
  2. conf/ 下建立 backup-masters 文件
  3. 写入高可用 HMaster 节点hostname,注意要在hosts中配置ip
  4. 复制到所有节点

Shell 操作

/bin/hbase shell 进入shell界面,help '命令' 查看帮助。

# namespace
list_namespace
create_namespace 'ns01'

# ddl
create 'namespace1:table1', {NAME => 'f1', VERSIONS => 3},'f2' # 列族f1维护3个版本,列族f2默认保留一个版本
describe 'namespace1:table1'
list
alter 'ns1:t1', {NAME => 'f1', VERSION => 4} # 增加修改列族都使用覆盖的方法
alter 'ns1:t1', {METHOD => 'delete', NAME => 'f1'}
alter 'ns1:t1', 'delete'  => 'f1'
disable 'ns:t1'
drop 'ns1:t1'

# DML
# 同一行数据put多次会覆盖,这也是hbase中修改数据的方法
put 'ns1:t1', 'rowKey', 'columnFamily:coumnName', 'value' [, 'timestamp'] # 建议使用默认的当前时间戳而非手动指定
# 读一行数据
get 'ns1:t1', 'rowKey'
get 'ns1:t1', 'rowKey', {COLUMN => ['columnFamily:columnName'], VERSIONS => 6}
# scan 扫描读取多行数据,不建议扫描过多数据,应当指定范围
scan 'ns1:t1', {STARTROW => '1001', STOPROW => '1002'}
# 删除一个版本,默认最新,可指定版本
delete 'ns1:t1', 'rowKey', '列族:列名'
# 删除所有版本
deleteall 'ns1:t1', 'rowKey', '列族:列名'

API

代码DDL
 package com.example.hbaseproject.dir01;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

class Main{
    /************************* 获取 hbase 单线程连接的方法,其相当重,适合一个进程共用一个 *************************/
    public void fun01() throws IOException {
        Configuration conf = new Configuration();
        // 等于 hbase-site.xml 文件中的配置项
        conf.set("hbase.zookeeper.quorum", "ubuntu01,ubuntu02,ubuntu03");
        // 异步连接
        // CompletableFuture<AsyncConnection> asyncConn = ConnectionFactory.createAsyncConnection();
        // 默认同步连接,记得 close
        try(Connection conn = ConnectionFactory.createConnection(conf);){

        }
    }

    /************** hbase 是线程安全的,内部的Table和Admin不是线程安全的,也不建议缓存或池化二者 **************/
    public static Connection connection = null;
    static {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum", "ubuntu01,ubuntu02,ubuntu03");
        try {
            // 无参的工厂方法会到 hbase-default.xml hbase-site.xml 中读取配置,那样无需在代码中配置
            connection = ConnectionFactory.createConnection(conf);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static void closeConnection() throws IOException {
        if (connection != null) {
            connection.close();
        }
    }
    /***************************************** DDL *****************************************/
    // 链接的IO异常应当由方法调用者处理
    public static void createNameSpace() throws IOException {
        // 用于操作 DDL,轻量级链接,线程不安全,不推荐池化或缓存
        Admin admin = connection.getAdmin();

        NamespaceDescriptor.Builder builder = NamespaceDescriptor.create("namespace");
        builder.addConfiguration("key", "value");
        // 创建命名空间出现的问题,属于本方法自身的问题,不该抛出
        try {
            admin.createNamespace(builder.build());
        } catch (IOException e) {
            System.out.println("log : 命名空间已存在");
            e.printStackTrace();
        }

        admin.close();
    }
    public static boolean isTableExits(String namespace, String tableName) throws IOException {
        Admin admin = connection.getAdmin();
        boolean exists = false;
        try {
            exists = admin.tableExists(TableName.valueOf(namespace, tableName));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        admin.close();
        return exists;
    }
    public static void createTable() throws IOException {
        Admin admin = connection.getAdmin();
        // 创建对列族的描述
        ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder =
                ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("columnFamily01"));
        columnFamilyDescriptorBuilder.setMaxVersions(3);
        ColumnFamilyDescriptor columnFamilyDescriptor = columnFamilyDescriptorBuilder.build();
        // 创建对表的描述
        // 这种方式得到的是一个新的表描述,没有已存在表的信息,不能用于修改
        TableDescriptorBuilder tableDescriptorBuilder = 
            TableDescriptorBuilder.newBuilder(TableName.valueOf("tablename"));
        tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
        TableDescriptor tableDescriptor = tableDescriptorBuilder.build();

        try {
            admin.createTable(tableDescriptor);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        admin.close();
    }
    public static void modifyTable() throws IOException {
        Admin admin = connection.getAdmin();
        // 通过这个方法获得已存在的表的描述
        TableDescriptor existingTable = admin.getDescriptor(TableName.valueOf("namespace", "ExistingTable"));
        TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(existingTable);

        // 新的列族
        ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = 
            ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("newColumnFamily"));
        ColumnFamilyDescriptor newColumnFamilyDescriptor = columnFamilyDescriptorBuilder.build();

        // 注意:没有在这一行中指定的列族都会被设置为初始值。如果想原有的值不变,需要从 existingTable 获取旧的列并插入
        tableDescriptorBuilder.modifyColumnFamily(newColumnFamilyDescriptor);
        admin.close();
    }
    
    // TableName tableName = TableName.valueOf("namespace", "table");
    // admin.disable(tableName);
    // admin.deleteTable(tableName);
}

代码 DML

代码操作数据
 package com.example.hbaseproject.dir01;

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.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.ColumnValueFilter;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.Arrays;

class Main{
    public static Connection connection = null;
    static {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum", "ubuntu01,ubuntu02,ubuntu03");
        try {
            // 无参的工厂方法会到 hbase-default.xml hbase-site.xml 中读取配置,那样无需在代码中配置
            connection = ConnectionFactory.createConnection(conf);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static void putCell(String namespace, String tableName, String rowKey,
                               String columnFamily, String columnName, String value) throws IOException {
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));
        // 创建一个要插入的数据单元
        Put cell = new Put(Bytes.toBytes(rowKey));
        // 指定命名空间、列族、列名等
        cell.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columnName), Bytes.toBytes(value));
        try {
            table.put(cell);
        } catch (IOException e) {
            e.printStackTrace();
        }
        table.close();
    }
    public static Cell[] getCell(String namespace, String tableName, String rowKey,
                               String columnFamily, String columnName) throws IOException {
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));
        // 创建要查询的行对象
        Get get = new Get(Bytes.toBytes(rowKey));
        // 添加指定列
        get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columnName));
        // 指定读取所有的版本
        get.readAllVersions();

        Cell[] cells = null;
        try {
            Result result = table.get(get);
            // 获得cell数组
            cells = result.rawCells();

            Arrays.stream(cells).forEach(cell -> {
                // 真正的数据包裹在cell内部
                String value = new String(CellUtil.cloneValue(cell));
                System.out.println("value = " + value);
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
        table.close();
        return cells;
    }

    public static void scanRows(String namespace, String tableName,
                                String startRow, String stopRow,
                                String columnFamily, String columnQualifier, String columnValue) throws IOException {
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));
        // 得到 scan 对象,默认扫描整张表
        Scan scan = new Scan();

        // 过滤行
        scan.withStartRow(Bytes.toBytes(startRow) /*, true 默认包含当前这条*/);
        scan.withStopRow(Bytes.toBytes(stopRow) /*,false 默认不包含此条*/);

        // 过滤列
        FilterList filterList = new FilterList();
        // 1. 查询结果只保留指定列的数据
        ColumnValueFilter columnValueFilter = new ColumnValueFilter(Bytes.toBytes(columnFamily),
                Bytes.toBytes(columnQualifier), CompareOperator.EQUAL, Bytes.toBytes(columnValue));
        // 2. 查询结果保留匹配的整行数据,结果也会保留没有当前列的数据
        SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(Bytes.toBytes(columnFamily),
                Bytes.toBytes(columnQualifier), CompareOperator.EQUAL, Bytes.toBytes(columnValue));
        // filterList.addFilter(columnValueFilter);
        filterList.addFilter(singleColumnValueFilter);

        scan.setFilter(filterList);

        try {
            // 读取多行数据
            // result 是一行数据,包含一个 cell 数组
            // ResultScanner 是多行数据,包含一个 result 数组
            ResultScanner resultScanner = table.getScanner(scan);
            resultScanner.forEach(result -> {
                Arrays.stream(result.rawCells()).forEach(cell -> {
                    System.out.printf("%s - %s:%s -%s",
                            new String(CellUtil.cloneRow(cell)),
                            new String(CellUtil.cloneFamily(cell)),
                            new String(CellUtil.cloneQualifier(cell)),
                            new String(CellUtil.cloneValue(cell)));
                });
                System.out.println();
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void deleteCell(String namespace, String tableName, String rowKey,
                                  String columnFamily, String columnQualifier) throws IOException {
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));
        Delete delete = new Delete(Bytes.toBytes(rowKey));

        // 对应命令 delete,删除一个版本
        // delete.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columnQualifier));
        // 对应命令 deleteall,删除所有版本
        delete.addColumns(Bytes.toBytes(columnFamily), Bytes.toBytes(columnQualifier));
        try {
            table.delete(delete);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        table.close();
    }
}

Master 架构

客户端操作元数据时才会连接master,读写数据时直接连接zookeeper读取目录/hbase/meta-region-server 节点信息,记录meta表格位置。直接读取无需访问master,这样减轻master压力。master专注meta表的写操作,客户端直接读取meta表。

HBase 2.3 提供了Master Registry,客户端可直接访问master读取meta表信息,将压力从zookeeper转移到了master。

Region Server

写流程

在写之前先创建连接,期间创建缓存这个步骤消耗巨大。

  1. 想zookeeper发请求
  2. 从zk获取meta表存在哪
  3. 从某个 region server 读取meta表
  4. 将读取的meta表保存在连接中
  5. meta表变化时更新缓存

从缓存的meta表中得到要写数据的位置,如果写入失败说明 region server 发生变化,需要更新meta表缓存。数据先写到磁盘上的WAL中,在放入内存的写缓存进行排序。满足一定条件时写入hdfs,生成一个新的文件。HFile单文件内有序(在写缓存中排序了),文件间无序。

MemStore Flush

多个线程控制,条件相互独立。

  1. 当memstore大小达到 hbase.hregion.memstore.flush.size(默认128M,即hdfs中块大小),其所在region对应的所有memstore都会刷写。同时刷写多个region的目的是尽量使不同列的同一行数据同时写入。当某次检查时发现memstore大小达到阈值的指定倍数时阻止继续向memstore写,并持久化到HFile
  2. MemStoreFlusher线程通过 LOWER_MARK 和 HIGH_MARK 控制,避免写缓存使用过多内存照成OOM
    1. 低水位:java_heapsize * memstore占内存比率(默认0.4)* 低水位(默认0.95)
      当写缓存memstore总大小达到这个值时也会触发刷写,如果存在过多的列族即memstore数量过多,那么可能每次刷写到内存时单个memstore的大小不到128M
    2. 高水位:java_heapsize * memstore占内存比率(默认0.4)
      阻止向所有memstore内写数据
  3. 为防止数据长时间停留在内存,默认5min一次自动刷新
  4. WAL堆积一定长度后刷写(已废弃),刷写到磁盘后对应WAL会删除

HFile 结构

HFile 是存在HDFS上每个store目录下实际存储数据的文件,包括数据本身(KV对)、元数据信息、文件信息、数据索引、元数据索引、固定长度尾部信息(记录文件修改情况)。

  • KV对按照块大小(默认64K)保存在文件
    KV内容包括:
    • key长度
    • key的值
    • 列族长度
    • 列族
    • 列名
    • 时间戳(默认系统时间)
    • keytype:Put
  • 块越多,数据索引就越多
  • 每个HFile维护一个布隆过滤器,读取时判断要get的key是否在HFile中

读流程

优先访问BlockCache,读缓存保存了元数据用于快速查找、通过尾部信息判断数据是否修改。无论缓存是否有数据(判断数据是否过期),都需要读写缓存和store中的文件。读取数据的多个版本,用高版本覆盖低版本。

GC主要清理的就是读缓存的数据

优化合并读取数据

每次读数据都要读三个位置的数据,然后版本合并,效率很低,需要优化:

  • HFile带有索引文件,读取对应RowKey数据会更快
  • Block Cache 缓存之前读取的内容和元数据,如果HFile尾信息表示没有变化,则无需再次读取
  • 使用布隆过滤器快速过滤当前HFile不存在需要读取的RowKey,避免读取文件

文件合并

每次刷写生成一个新的HFile,过多的文件不方便读取,所以需要 storeFile Compaction。分两种:

  • Minor Compaction
    将临近的若干小HFile合并成一个较大HFile,并清理部分过期和删除的数据
  • Major Compaction
    将一个store下所有HFile合并成一个大HFile,并清理部分过期和删除的数据,由 hbase.hregion.majorcompaction 控制,默认7天

预分区

切分Region,避免单个Region中数据量过大

预分区(自定义分区)

每个region维护着startRowKey和endRowKey

# 在创建表时指定切分的点,生成4个区间
create 'staff1', 'info', SPLITS => ['1000', '2000', '3000']

# 在文件中指定切分点
create 'staff1', 'info', SPLITS_FILE => 'splits.txt'
#文件内容:
#aaaa
#bbbb
#cccc

# 使用16进制拆分为15个分区
create 'staff2', 'info', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}

系统分区

系统默认启动,由HRegionServer完成。操作前要通过zk汇报给master,修改对应的Meta表信息,添加两列 info:splitA 和 info:splitB ,之后在hdfs上拆分文件。此时创建文件的引用,不会移动数据。刚完成拆分时两个Region都由原RegionServer管理,之后汇报给master让其写入到meta表。等下一次触发负载均衡机制时,才会修改Region的管理服务者,等到下一次压缩时才会实际移动文件。

RowKey 设计

HBase有两种使用方法:1.当做关系数据库使用(不推荐) 2.作为一个TSDB(推荐)

rowKey是数据的唯一标识,决定了数据位于哪一个预分区的区间,rowKey的设计就是为了让数据尽可能均匀地分布在不同region中防止数据倾斜。常用生成方案:

  1. 随机数,hash
  2. 时间戳反转,用一个最大数字减去时间戳,这样越新的数据得到的rowKey越小,排序越靠前
  3. 字符串拼接

案例

使用hbase存储,要能通过api完成一下两个需求:

  1. 统计张三2023年4月消费总额
  2. 统计所有人2023年4月消费总额

使用扫描全表+过滤的方法并不高效,应当使用scan直接得到结果。数据格式:

user    date    pay
zhangsan    2023-04-22 09:16:00    100
zhangsan    2023-04-30 09:16:00    80
zhangsanfeng    2023-04-30 09:16:00    80
lisi    2021-04-30 09:16:00    50
wangwu    2022-04-30 09:16:00    70

需求一:

数据中zhangsan和zhangsanfeng的长度不同,hbase提供了asiica为1的填充字符,其不可打印,用^A表示。使^A加姓名长度等于定长
^A^A^A^AUsernameDate(yyyy-MM-dd hh:mm:ss ms) 即可实现需求一

scan扫描
startrow => ^A^Azhangsan202304   # 空字符的asiica的值小于字符-
stoprow  => ^A^Azhangsan202304.  # 字符.的asiica值大于-

需求二:

前一种设计只能满足一种需求,如将date放在username前即可实现按时间划分数据。

解决方法:可穷举的放在前面。年月可穷举,而姓名、具体时间都不可,所以得到格式:Date(yyyy-MM)UsernameDate(-dd hh:mm:ss ms)

得到结果:

需求一:
scan:startRow => 2023-04^A^A^A^Azhangsan
      stopRow  =>  2023-04^A^A^A^Azhangsan.
      
需求二:
scan:startRow => 2023-04
      stopRow  =>  2023-04.

预分区优化

预分区的分区号同样遵守scan的扫描原则,在rowKey前添加简单数字组成的前缀。使用用户名和月份组合计算hash得到分区号。单独使用用户名可能造成数据倾斜。

分区:
startKey    stopKey
            001
001         002
002         003
...
119         120

分区号为:hash(username+date(MM)) % 120数字要添加前缀0以保证对应字符串长度为3

此时再考虑两个需求:

需求一:

通过用户名和月份得到分区号,拼接上原rowKey得到区间

分区号 => hash(zhangsan+04) % 120 得到分区号 015
scan:startRow => 0152023-04^A^A^A^Azhangsan
      stopRow  =>  0152023-04^A^A^A^Azhangsan.

需求二:

每个分区都有4月的数据,无法精准扫描,只能每个分区都去扫描
为此,可将分区和月份进行绑定

分区 000~009 存储1月份数据
分区 010~019 存储2月份数据
分区 020~029 存储3月份数据
...
分区 110~119 存储12月份数据

将分区号的计算方式改为 hash(username+MM) % 10 + (MM-1)*10 如此在处理需求二时,只要扫描 030~039 分区的数据即可。

Phoenix

HBase的开源SQL接口,用 JDBC 代替原生客户端API,下载解压后将内部 phoenix-server-hbase-xxx.jar 放到集群所有节点的 HBASE_HOME/lib 目录下,然后在某个节点上配置环境变量,之后重启HBase:

export PHOENIX_HOME=/usr/local/phoenix
export PHOENIX_CLASSPATH=$PHOENIX_HOME
export PATH=$PATH:$PHOENIX_HOME/bin

运行方法:$PHOENIX_HOME/bin/sqlline.py zkserver01,zkserver02,zkserver03:2181,如果报错可尝试删除home目录下历史记录 .sqlline/ 目录

语法:https://phoenix.apache.org/language/index.html

注意:没有双引号的表名自动转为大写,默认对列名进行编码使用更短的字符串存储

全局索引

使用phoenix的二级索引需要添加 hbase-site.xml 配置

<property>
    <name>hbase.regionserver.wal.code</name>
    <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCode</value>
</property>

创建全局索引时会在HBase中创建一张新表,适用于读多写少的场景。写数据时会产生大量消耗,因为索引表也要更新,而且索引表时分布在不同数据节点上的,跨节点的数据传输带来了较大的性能消耗。

CREATE INDEX my_index ON my_table(my_col) 使用explain执行查询计划可看见索引是否起效

但如果查询的字段不是索引字段,全局索引表不会被使用,也不会带来性能提升。可通过包含索引或本地索引解决。

包含索引

携带了其他字段的全局索引,本质还是全局索引。

create index my_index on student1(age) include (addr);

本地索引

适用于写操作频繁的场景。索引数据和数据表存放在同一张表同一个Region,避免了写操作往不同服务器索引表写索引的开销。

create local index my_index on my_table(my_column);

Hive 集成 HBase

推荐使用phoenix和hbase搭配,但当HBase上已经有大量数据时,phoenix不适合复杂的SQL处理,可使用hive映射hbase的表格编写HQL进行分析处理。

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

<property>
    <name>hive.zookeeper.quorem</name>
    <value>zkserver1,zkserver2,zkserver3</value>
</property>
<property>
    <name>hive.zookeeper.client.prot</name>
    <value>2181</value>
</property>
posted @ 2023-04-22 15:58  某某人8265  阅读(87)  评论(0编辑  收藏  举报