Hbase整体架构
master:
主要进程,具体实现类为HMaster,通常部署在namenode上。功能:负责通过ZK监控RegionServer进程状态,同时是所有元数据变化的接口。内部启动监控执行region的故障转移和拆分的线程。
RegionServer:
主要进程,具体实现类为HRegionServer,部署在datanode上。功能:主要负责数据cell的处理。同时在执行区域的拆分和合并的时候,由RegionServer来实际执行。
hbase本身还是基于主从模式,一主多从;
HBase 的架构通常包括以下几个主要组件:
- HMaster: 负责管理集群的元数据、Region 的分配和负载均衡。
- RegionServer: 实际存储数据的节点,每个 RegionServer 管理多个 Region。
- Region: 数据的基本单位,负责存储实际的行数据,Hbase的region默认是10G,如果当一个Region的数据量达到阈值时,自动进行splitting操作,生成新的Region。
- HFile: 底层存储格式,Region 中的数据最终以 HFile 的形式存储在 HDFS 上。
- Zookeeper: 提供分布式协调和服务发现,HBase 利用 Zookeeper 管理 HMaster 和 RegionServer 的状态。
- WAL(WriteAheadLog): 写前日志,确保数据在写入时的持久性,比如创建一张表,在创建的过程中,如果master挂掉了,backup master会转正为master,并读这个预日志,预写日志会存在hdfs上面(/hbase/MasterData/WALS),当操作执行到meta表之后删除WAL。
RegionServer不能直接跟master通信(这涉及到分布式一致性问题),每一个server把自己的信息注册到zookpeeper上面,Zookeeper和RegionServer之间有心跳机制。
著名的 HBase 也是属于 CP 系统,以 HBase 集群为例,假如某个 RegionServer(HBase通过HMaster检测) 宕机了,这个RegionServer持有的所有键值范围都将离线,直到数据恢复过程完成为止,这个过程要消耗的时间是无法预先估计的。
meta表中有列簇,叫Info,master需要跟hdfs文件系统,主要跟meta表进行交互,meta表不能修改,通过读取meta表了解Region的分配,通过连接zk了解RS的启动情况。5分钟调控一次分配平衡。
HBase中Zookeeper的作用
Hbase适用场景
写密集型应用,每天写入量巨大,而相对读数量较小的应用,比如互联网公司的社交软件的历史消息,大型系统的各种日志、评论系统等。
对性能(点查)和可靠性要求非常高的应用,由于HBase本身没有单点故障,可用性非常高。数据量较大,而且增长量无法预估的应用(比如互联网评论),HBase支持在线扩展,即使在一段时间内数据量呈井喷式增长,也可以通过HBase横向扩展(动态列) 来满足功能。但当你面对每天数十亿数据,数据量接近PB级时,如果也只是通过rowkey去查数据,那么对于上PB级的数据,都非常适合用Hbase来查询,性能也是非常高的。
例:
Facebook用Hbase进行社交信息的存储、查询与分析,主要存储在线消息,每天数据量近百亿,每月数据量超过200T。
网易的哨兵监控系统,云信历史数据,日志归档数据等一系列重要应用底层都由HBase提供服务。
京东用Hbase存储卖家操作日志,即几十万商家时时刻刻进行的各种操作。以便进行分析,并且保证商家可以精确查询自己的各种操作。卖家操作日志的特点是:数据量大、实时性强、增多查少。
互联网公司还需要收集和存储海量用户的操作行为,比如转发、评论和点赞,通过这些行为来分析用户的特征,形成用户画像,精准投放广告,提升广告收入。
所以但当你面对每天数十亿数据,数据量接近PB级时,如果也只是通过rowkey去查数据,那么对于上PB级的数据,都非常适合用Hbase来查询,性能也是非常高的。
Hbase的数据模型
HBase 的数据模型是分布式的、多维的、持久的,并且是一个按列键、行键和时间戳索引的排序放大器,这也是 Apache HBase 也被称为键值存储系统的原因。
以下是 Apache HBase 中使用的数据模型术语。
1. 表
Apache HBase 将数据组织成表,表由字符组成,易于与文件系统一起使用。
2. 行
Apache HBase 基于行存储其数据,每一行都有其唯一的行键。行键表示为字节数组。
3. 列族
列族用于存储行,它还提供了在 Apache HBase 中存储数据的结构。它由字符和字符串组成,可以与文件系统路径一起使用。表中的每一行都将具有相同的列族,但一行不需要存储在其所有列族中。
4. 列限定符( Qualifier )
列限定符用于指向存储在列族中的数据。它始终表示为一个字节。
5. Cell
单元格是列族、行键、列限定符的组合,一般称为单元格的值。
6. 时间戳
存储在单元中的值是版本化的,每个版本都由在创建期间分配的版本号标识。如果我们在写入数据时不提及时间戳,则考虑当前时间。
7.namespace
Hbase自带两个命名空间,分别是hbase和default ,hbase中存放的是HBase内置的表,default表是用户默认使用的命名空间。 Namespace可以帮助用户在多租户场景下做到更好的资源和数据隔离。用户也可以建立自定义的namespace空间。
RowKey的设计原则
RowKey是唯一标识表中一行数据的关键字。它是一个字节数组,可以是任何数据类型。RowKey的设计非常重要,因为它直接影响了数据的分布和查询性能。较好的RowKey设计可以实现数据的均衡存储和高效的查询。
1、Rowkey是以字典序从大到小
原生Hbase只支持从小到大排序,要想实现从大到小,可以采用 Rowkey=Integer.MAX_VALUE-Rowkey的方式,在应用层再转回来完成需求。
2、Rowkey尽量散列
Rowkey要尽量散列,这样可以保证数据不在一个Region(分区)上,从而避免了读写的集中。
比如我们可以设计 userid_videoid 拼接字符串 这样的话user就会不均匀。
有三种办法解决: 反转userid 散列userid 将userid取模后进行MD5加密 取前6位加入userid中。
3、Rowkey长度要尽量短
Rowkey过长,存储开销会大。100字节以内,8的倍数最好,因为操作系统大多为64位,8的倍数,充分利用操作系统的最佳性能。;
Rowkey过长,会导致内存的利用率降低,进而降低索引命中率。
4、唯一性原则
RowKey应该唯一,避免数据覆盖(是使用Put方法的时候,如果rowkey一样,那么会把之前的数据全部overwrite掉)
Hbase写入数据流程
1:像zk发送请求创建链接
2:读取zk存储meta表是由哪个Region Server管理的,然后根据rowkey(解析rowkey)查出目标数据位于哪个region,同时将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问;
3:与目标Region Server进行通讯,并将数据顺序写入(追加)到WAL,并且抓紧落盘到hdfs中,同时写到MemStore中;
4:将数据写入对应的MemStore,数据会在MemStore进行排序,等达到MemStore的刷写时机后,每次刷写会生成一个HFile文件。
Hbase读取数据流程
创建连接同写流程。
(1) 创建 Table 对象发送 get 请求;
(2) 优先访问 Block Cache,查找是否之前读取过,并且可以读取 HFile 的索引信息和布隆过滤器;
(3) 不管读缓存中是否已经有数据了(可能已经过期了),都需要再次读取写缓存和store 中的文件;
(4) 最终将所有读取到的数据合并版本,按照 get 的要求返回即可。
Hbase性能调优
读取优化
调整BlockCache大小:
BlockCache读缓存,每一个region Server只会有一个读缓存,把读到的数据都放到内存中的,另外:BlockCache 的大小必须是内存页大小的整数倍(目前常用页面大小4KB)。默认值0.2(即 20% 的堆内存),增加此值可以提高读取性能,尤其是在读取热点数据时。通常,如果你的系统有大量的读取请求且数据访问存在热点(即某些数据频繁访问),可以增大 BlockCache 的大小。高读负载系统:如果你的应用有较高的读取负载,尤其是热点数据(频繁访问的列或行),可以增大 BlockCache 的大小。通过hfile.block.cache.size。当然,,如果频繁读取的数据,可以使用数据预加载在MemCacheed或者Redis中。
调整 Region 大小 :
Region 大小对查询性能有直接影响。较大的 Region 可以减少 RegionServer 之间的负载均衡,但如果 Region 太大,可能会导致查询延迟和内存压力过大。通常,Region 大小应该在几十 MB 到几百 MB 之间。可以通过调整 hbase.hregion.max.filesize 参数来控制 Region 的最大大小;
调整RegionServer数量
写入优化
批量写入 :批量写入Put对象,减少RPC通讯开销,从而降低网络延迟,提高写入吞吐量。
禁用写前日志 :HBase 默认开启了 WAL 机制,它在每次写入时都会将数据先写入日志文件,从而确保数据不丢失。但如果是非关键数据或临时数据,可以在写入时暂时禁用 WAL,从而提升性能。
调整缓冲区大小(MemStore) :MemStore 是存储数据的内存区域,负责缓存写入的数据,直到这些数据被刷写到磁盘上的HFile。当数据被写入HBase时,首先会被写入到 MemStore 中,然后在 MemStore达到一定大小后,数据会被刷写到磁盘上。调整 MemStore 的配置对于优化写入性能、内存使用和系统吞吐量非常重要。hbase.regionserver.memstore.flush.size,默认128 MB,根据实际负载进行调整。如果你的写入负载很高,可以考虑增大此值以减少刷写次数,减少磁盘I/O的压力。hbase.regionserver.global.memstore.upperLimit 默认 0.4,设置 MemStore 占用内存的上限百分比。如果 MemStore 使用的内存超过此值,RegionServer 会触发 flush 操作,将 MemStore 中的数据刷写到磁盘。
异步写入( AsyncBufferedMutator ) :异步操作以提升大批量数据写入的性能,减少等待和同步的开销。
wal
HBase 的 WAL(Write Ahead Log)是一个重要的日志机制,用于确保数据的可靠性和一致性。当数据被写入 HBase 时,首先会记录到 WAL 中,只有在数据成功写入 WAL 后,HBase 才会将数据写入内存中的 MemStore。这种机制可以防止数据丢失,因为即使系统崩溃或发生故障,WAL 中的记录可以用来恢复未持久化的数据。
WAL 主要有以下几个作用:
- 数据恢复:在发生故障时,可以通过 WAL 中的日志恢复未写入磁盘的数据。
- 数据一致性:保证数据写入的顺序性,避免因操作顺序错误导致的数据不一致。
- 高性能:WAL 的异步写入和顺序写入特性,能够提升写入性能。
通常,WAL 会定期进行归档和清理,以释放存储空间,当然,是基于写入数据没问题的基础上。
一张表中定义多少个Column Family最合适
- 每个 CF 对应一个 Store 文件过多时,会导致 RegionServer 内存和磁盘压力激增。HBase 的数据是按列族存储的,每个列族对应一个磁盘存储区域,因此列族数过多会导致多个 HFile 存储区的竞争,增加 I/O 操作,影响读取性能
HBase中region(分区)
在Hbase中,分区是基本的存储单元,当数据量较大时,HBase 会自动( 默认情况下,当一个 Region 达到 10MB 的大小 )将数据分成多个 Region,每个 Region 存储数据的一部分。每个 Region 会映射到一个 RegionServer 上进行管理。合理地分配和管理 Region 可以帮助提高性能、避免热点问题以及实现更好的负载均衡,每个 Region 包含一个连续的行范围(startRowKey,endRowKey)。
HBase 会根据 RowKey 的顺序将行数据分配到不同的 Region中,同时,HBase 会将拆分后的 Region 分配给不同的 RegionServer。
预分区
在创建表时,可以通过 create 命令或 Java API 提供一个初始的分区范围。例如,如果知道 RowKey 的分布规则,可以根据这个规则设计初始的分区,通过指定一个排序好的 RowKey 数组(splitKeys)来告诉 HBase 如何分割 Region。这样可以确保初期的数据负载分布均匀,避免所有数据集中在一个 Region 中;
以上图片内容相当于以下代码,表示HBase 会在 RowKey 小于 a、b、c 、 d 的数据之间创建 4 个 Region。
create 'my_table', {NAME => 'cf', SPLITS => ['a', 'b', 'c', 'd']}
LSM树
什么是LSM树
LSM树在前互联网时代并未得到很好的重视,传统的关系型数据库的存储和索引结构依然以基于页面(Page)的B+树和HashTable为主。随着互联网规模的扩大和普及,在面对十亿级的用户接入,以及PB规模数据的写入,传统的关系型数据库已经难以支撑,它的核心思想在于将写入推迟 (Defer) 并转换为批量 (Batch) 写,首先将大量写入缓存在内存,当积攒到一定程度后(到达阈值),将他们批量写入文件中,这要一次 I/O 可以进行多条数据的写入,充分利用每一次I/O,从而实现高效地顺序写入数据。顺序写入的速度比随机写入的速度快很多,即追加内容,非就地更 新,类似普通的日志写入方式以 append 的模式追加,不覆盖旧条目,LSM 树是一种针对写入密集型应用优化的数据结构。它的设计目标是提高磁盘写入吞吐量,并且适合用于日志型数据库、键值存储系统等。LSM 树是一个分层的结构,由内存中的 MemTable 和磁盘中的 SSTable(Sorted String Table)构成。
工作原理
MemTable:
当数据被写入时,它首先被写入到内存中的 MemTable(通常是一个跳表(skipTable)或红黑树结构)。如果 MemTable 满了,它会被刷新到磁盘形成一个不可变的 SSTable 文件。
SSTable:
SSTable 是一个按键排序的文件,存储在磁盘中。它不支持随机写操作,因此所有的写操作都先发生在 MemTable 中。SSTable 是不可变的,一旦写入就不会更改。
Compaction(todo):
随着 SSTable 文件越来越多,需要定期执行合并操作(Compaction),太多SSTable会导致数据查询IO次数增多,Compaction是LSM树实现中最复杂的部分,因为其持续对IO以及CPU资源的使用,会对系统的负载造成很大影响,影响上层业务的稳定性,将多个 SSTable 文件合并成一个更小的文件,减少磁盘的浪费和查询效率的提升
HBase 的底层实现基于 LSM 树的设计理念。比如Hbase写数据时的MemStore和MemTable就类似,所有的写操作,,都会先保存在内存中,达到一定的大小后,会直接刷盘到磁盘上,并且生成一个HFile文件,并且是持久化存储
Hbase接口&实现类
Maven依赖:
<dependency>
<groupId>org.apache.hbase.thirdparty</groupId>
<artifactId>hbase-shaded-protobuf</artifactId>
<version>2.2.1</version>
</dependency>
<!--增加alihbase-client的依赖-->
<dependency>
<groupId>com.alibaba.hbase</groupId>
<artifactId>alihbase-client</artifactId>
<version>2.1.0</version>
</dependency>
<!--${connector-version} 需要替换成hbase-client对应的版本,版本对应关系见最下面表格的依赖-->
<dependency>
<groupId>com.alibaba.hbase</groupId>
<artifactId>alihbase-connector</artifactId>
<version>2.1.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</exclusion>
</exclusions>
</dependency>
接口:
package com.example.core.service;
import com.example.core.entity.Tuple;
import org.apache.hadoop.hbase.client.BufferedMutator;
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.Scan;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Hbase服务
*
* @author Kevin
* @since 2024-01-08
*/
public interface HbaseService {
/**
* 获取family
*
* @return family名称
*/
String getDefaultFamily();
/**
* 创建表
*
* @param columnFamily 默认列簇列表
* @param tableName 表名
* @return 是否成功
* @throws IOException io异常
*/
boolean createTable(String tableName, List<String> columnFamily)
throws IOException;
/**
* 获取缓冲区
*
* @param tableName 表名
* @param columnFamily 列簇
* @param numRegions 分区数
* @return 是否成功
* @throws IOException IO异常
*/
boolean createTable(String tableName, List<String> columnFamily,
Integer numRegions) throws IOException;
/**
* 获取缓冲区
*
* @param tableName 表名
* @return 缓冲区对象
* @throws IOException IO异常
*/
BufferedMutator getBufferedMutator(String tableName)
throws IOException;
Map<String, Long> multiGetColumnValuesForIdMapping(String tableName,
List<String> rowKeys, String columnName);
/**
* 获取指定列的所有键值对
*
* @param tableName 表名
* @param rowKey table scanner
* @param columnNames 列的集合
* @return 查询列的名称和值的键值对
* @throws IOException IO异常
*/
Map<String, Tuple<String, Long>> getColumnValues(String tableName,
String rowKey, List<String> columnNames) throws IOException;
/**
* 获取指定列的键值对
*
* @param tableName 表名
* @param rowKey table scanner
* @param family 列簇
* @param columnNames 列的集合
* @return 查询列的名称和值的键值对
*/
Map<String, Object> getColumnValues(String tableName, String rowKey,
String family, List<String> columnNames);
<T, R> List<T> findByStartAndEnd(String tableName, String family,
R startRow, R endRow, int batch, int cache, final RowMapper<T> mapper);
<R> List<Map<String, String>> findByStartAndEnd(String tableName,
String family, R startRow, R endRow, int batch, int cache);
<T> T find(String tableName, final Scan scan,
final ResultsExtractor<T> action);
<T> List<T> find(String tableName, final Scan scan,
final RowMapper<T> action);
<T> T execute(String tableName, TableCallback<T> action);
/**
* 获取指定列的所有键值对
*
* @param tableName 表名
* @param rowKey table scanner
* @param family 列簇
* @return 查询列的名称和值的键值对
*/
Map<String, String> getColumnValues(String tableName, String rowKey,
String family);
/**
* 按表名批量插入Put对象包含的数据
*
* @param tableName 表名,必要时可指定命名空间,比如:“default:user”
* @param puts 要插入的数据对象集合
* @throws IOException 有异常抛出,由调用者捕获处理
*/
void batchPut(String tableName, List<Put> puts) throws IOException;
/**
* 根据表名和rowKey数组批量查询结果(包含全部列簇和列)
*
* @param tableName 表名,必要时可指定命名空间,比如:“default:user”
* @param rowKeys 查询rowKey数组
* @return 查询结果Result数组
* @throws IOException 有异常抛出,由调用者捕获处理
*/
Result[] get(String tableName, String[] rowKeys) throws IOException;
/**
* 根据表名和rowKey查询结果(包含全部列簇和列)
*
* @param tableName 表名,必要时可指定命名空间,比如:“default:user”
* @param rowKey 查询rowKey
* @return 查询结果Result
* @throws IOException 有异常抛出,由调用者捕获处理
*/
Result get(String tableName, String rowKey) throws IOException;
/**
* 根据rowKey生成一个查询的Get对象
*
* @param rowKey rowKey
* @return Get 对象
*/
Get createGet(String rowKey);
/**
* 根据表名和Get对象查询结果
*
* @param tableName 表名,必要时可指定命名空间,比如:“default:user”
* @param get Hbase查询对象
* @return 查询结果Result
* @throws IOException 有异常抛出,由调用者捕获处理
*/
Result get(String tableName, Get get) throws IOException;
/**
* 根据表名和Get对象数组查询结果
*
* @param tableName 表名,必要时可指定命名空间,比如:“default:user”
* @param gets 多个Hbase查询对象组成的数组
* @return 查询结果Result数组
* @throws IOException 有异常抛出,由调用者捕获处理
*/
Result[] get(String tableName, List<Get> gets) throws IOException;
void putOneData(String tableName, String rowKey, String columnFamily,
Map<String, String> map) throws IOException;
Map<String, Map<String, Tuple<String, Long>>> getColumnValues(
String tableName, List<String> rowKeys, List<String> columnNames);
}
实现
package com.example.core.service;
import com.example.core.cache.LocalCacheComponent;
import com.example.core.config.HbaseConfig;
import com.example.core.entity.Tuple;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.BufferedMutator;
import org.apache.hadoop.hbase.client.BufferedMutatorParams;
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.PageFilter;
import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.RegionSplitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* Hbase服务具体实现
*
* @author Kevin
* @since 2024-01-10
*/
@Service("rta.server.hbase.service")
public class HbaseServiceImpl implements HbaseService {
@Resource
private LocalCacheComponent localCacheComponent;
private Logger logger = LoggerFactory.getLogger(HbaseServiceImpl.class);
public HbaseConfig hbaseConfig;
public Connection connection = null;
public Admin admin = null;
public Map<String, BufferedMutator> bufferedMutatorMap =
new ConcurrentHashMap<>();
public Map<String, Boolean> tableExistSet = new ConcurrentHashMap<>();
/**
* 默认列簇
*/
public String DEFAULT_FAMILY = "cf";
@Value("${hbase.config.write.poolSize}")
private Integer poolSize;
@Value("${hbase.config.write.aliHbase}")
private boolean aliHbase;
@Value("${hbase.config.write.userName}")
private String userName;
@Value("${hbase.config.write.password}")
private String password;
@Value("${hbase.config.write.endpoint}")
private String endPoint;
@Value("${hbase.config.write.defaultNamespace}")
private String nameSpace;
@Value("${hbase.config.write.hbaseAlias}")
private String hbaseAlias;
public static HbaseServiceImpl hbaseService;
@PostConstruct
public void initHbaseConfig() {
if (hbaseService == null) {
hbaseConfig = new HbaseConfig();
hbaseConfig.setPoolSize(poolSize);
hbaseConfig.setAliHbase(aliHbase);
hbaseConfig.setEndpoint(endPoint);
hbaseConfig.setUserName(userName);
hbaseConfig.setPassword(password);
hbaseConfig.setDefaultNamespace(nameSpace);
init();
}
}
public void init() {
try {
logger.info("开始获取hbase:" + hbaseAlias + "连接");
Configuration configuration = hbaseConfig.init();
connection = ConnectionFactory.createConnection(configuration);
admin = connection.getAdmin();
logger.info("获取hbase:" + hbaseAlias + "连接成功");
this.DEFAULT_FAMILY = hbaseConfig.getDefaultFamily();
Runtime
.getRuntime()
.addShutdownHook(
new Thread() {
@Override
public void run() {
logger.info("开始执行hbase:" + hbaseAlias + "资源清理 ...");
for (String key : bufferedMutatorMap.keySet()) {
try {
bufferedMutatorMap.get(key).flush();
} catch (Throwable e) {
logger.error("刷新hbase mutator失败", e);
}
}
}
}
);
} catch (Exception e) {
logger.error("获取HBase:" + hbaseAlias + "连接失败", e);
throw new RuntimeException(e);
}
}
@Override
public String getDefaultFamily() {
return DEFAULT_FAMILY;
}
@Override
public boolean createTable(String tableName, List<String> columnFamily)
throws IOException {
try {
tableName = checkNamespace(tableName);
if (admin.tableExists(TableName.valueOf(tableName))) {
logger.info("表{}已经存在", tableName);
return true;
}
List<ColumnFamilyDescriptor> familyDescriptors = new ArrayList<>(
columnFamily.size()
);
columnFamily.forEach(cf ->
familyDescriptors.add(
ColumnFamilyDescriptorBuilder
.newBuilder(Bytes.toBytes(cf))
.setCompressionType(Compression.Algorithm.SNAPPY)
.setDataBlockEncoding(DataBlockEncoding.DIFF)
.build()
)
);
TableDescriptor tableDescriptor = TableDescriptorBuilder
.newBuilder(TableName.valueOf(tableName))
.setColumnFamilies(familyDescriptors)
.build();
admin.createTable(tableDescriptor);
logger.info("创建表{}成功, 列族:{}", tableName, columnFamily);
} catch (IOException e) {
logger.error("创建表{}失败", tableName, e);
throw e;
}
return true;
}
@Override
public boolean createTable(
String tableName,
List<String> columnFamily,
Integer numRegions
) throws IOException {
try {
tableName = checkNamespace(tableName);
if (admin.tableExists(TableName.valueOf(tableName))) {
logger.info("表{}已经存在", tableName);
return true;
}
List<ColumnFamilyDescriptor> familyDescriptors = new ArrayList<>(
columnFamily.size()
);
columnFamily.forEach(cf -> {
familyDescriptors.add(
ColumnFamilyDescriptorBuilder
.newBuilder(Bytes.toBytes(cf))
.setCompressionType(Compression.Algorithm.SNAPPY)
.setDataBlockEncoding(DataBlockEncoding.DIFF)
.build()
);
});
TableDescriptor tableDescriptor = TableDescriptorBuilder
.newBuilder(TableName.valueOf(tableName))
.setColumnFamilies(familyDescriptors)
.build();
byte[][] splitKeys = new RegionSplitter.HexStringSplit()
.split(numRegions);
admin.createTable(tableDescriptor, splitKeys);
logger.info("创建表{}成功, 列族:{}", tableName, columnFamily);
} catch (IOException e) {
logger.error(
"创建表{}失败, 列族:{},错误:{}",
tableName,
columnFamily.toString(),
e.getMessage()
);
throw e;
}
return true;
}
@Override
public Map<String, String> getColumnValues(
String tableName,
String rowKey,
String family
) {
// 获取表
Table table = null;
try {
byte[] familyByte = Bytes.toBytes(family);
tableName = checkNamespace(tableName);
table = getTable(tableName);
Get get = new Get(Bytes.toBytes(rowKey));
Result result = table.get(get);
if (result != null && !result.isEmpty()) {
Map<String, String> columnValueMap = new HashMap<>();
for (Cell cell : result.rawCells()) {
if (
Bytes.equals(
cell.getFamilyArray(),
cell.getFamilyOffset(),
cell.getFamilyLength(),
Bytes.toBytes(family),
0,
Bytes.toBytes(family).length
)
) {
// 将列名转换为字符串
String columnName = Bytes.toString(
cell.getQualifierArray(),
cell.getQualifierOffset(),
cell.getQualifierLength()
);
// 默认情况下,将列值作为字节数组转换为字符串
String value = Bytes.toString(CellUtil.cloneValue(cell));
// 如果需要更复杂的处理(如转换为其他类型),可以在这里添加逻辑
columnValueMap.put(columnName, value);
}
}
return columnValueMap;
}
} catch (IOException e) {
logger.error(
"getColumnValues数据失败,tableName:{},rowKeys:{},familyName:{},exception:{}",
tableName,
rowKey,
DEFAULT_FAMILY,
e.getMessage()
);
throw new RuntimeException("getColumnValues数据失败");
} finally {
close(table);
}
return new HashMap<>();
}
@Override
public Map<String, Object> getColumnValues(
String tableName,
String rowKey,
String family,
List<String> columnNames
) {
// 获取表
Table table = null;
try {
byte[] familyByte = Bytes.toBytes(family);
tableName = checkNamespace(tableName);
table = getTable(tableName);
Get get = new Get(Bytes.toBytes(rowKey));
for (String columnName : columnNames) {
byte[] columnByte = Bytes.toBytes(columnName);
get.addColumn(familyByte, columnByte);
}
Result hTableResult = table.get(get);
if (hTableResult != null) {
Map<String, Object> columnValueMap = new HashMap<>();
for (String columnName : columnNames) {
byte[] columnByte = Bytes.toBytes(columnName);
String columnValue = Bytes.toString(
hTableResult.getValue(familyByte, columnByte)
);
if (StringUtils.isNotBlank(columnValue)) {
columnValueMap.put(columnName, columnValue);
}
}
return columnValueMap;
}
} catch (IOException e) {
logger.error(
"getColumnValues数据失败,tableName:{},rowKeys:{},familyName:{},columnName:{},exception:{}",
tableName,
rowKey,
DEFAULT_FAMILY,
columnNames,
e.getMessage()
);
throw new RuntimeException("getColumnValues数据失败");
} finally {
close(table);
}
return new HashMap<>();
}
public void putOneData(
String tableName,
String rowKey,
String columnFamily,
String column,
String data
) throws IOException {
Table table = getTable(tableName);
Put put = new Put(Bytes.toBytes(rowKey));
put.addColumn(
Bytes.toBytes(columnFamily),
Bytes.toBytes(column),
Bytes.toBytes(data)
);
table.put(put);
}
@Override
public void putOneData(
String tableName,
String rowKey,
String columnFamily,
Map<String, String> map
) throws IOException {
Table table = getTable(tableName);
Put put = new Put(Bytes.toBytes(rowKey));
for (String column : map.keySet()) {
put.addColumn(
Bytes.toBytes(columnFamily),
Bytes.toBytes(column),
Bytes.toBytes(map.get(column))
);
}
table.put(put);
}
@Override
public Map<String, Long> multiGetColumnValuesForIdMapping(String tableName, List<String> rowKeys, String columnName) {
//返回数据
Map<String, Long> result = new HashMap<>();
// 获取表
Table table = null;
try {
byte[] familyByte = Bytes.toBytes(DEFAULT_FAMILY);
byte[] columnByte = Bytes.toBytes(columnName);
tableName = checkNamespace(tableName);
table = getTable(tableName);
List<Get> multiGet = new ArrayList<>();
for (String rowKey : rowKeys) {
Get get = new Get(Bytes.toBytes(rowKey));
get.addColumn(familyByte, columnByte);
multiGet.add(get);
}
Result[] hTableResults = table.get(multiGet);
if (hTableResults != null) {
for (Result hTableResult : hTableResults) {
String rowKey = Bytes.toString(hTableResult.getRow());
String columnValue = Bytes.toString(
hTableResult.getValue(familyByte, columnByte)
);
if (
StringUtils.isNotBlank(rowKey) &&
StringUtils.isNotBlank(columnValue)
) {
try {
Long columnLongValue = Long.parseLong(columnValue);
result.put(rowKey, columnLongValue);
} catch (Throwable e) {
logger.error(
"查询多个单元格的数据失败,tableName:{},rowKey:{},familyName:{},columnName:{},exception:{}",
tableName,
rowKey,
DEFAULT_FAMILY,
columnName,
e.getMessage()
);
}
}
}
}
} catch (IOException e) {
logger.error(
"getColumnValuesForIdMapping数据失败,tableName:{},rowKeys:{},familyName:{},columnName:{},exception:{}",
tableName,
rowKeys,
DEFAULT_FAMILY,
columnName,
e.getMessage()
);
throw new RuntimeException("getColumnValuesForIdMapping数据失败");
} finally {
close(table);
}
return result;
}
@Override
public Map<String, Tuple<String, Long>> getColumnValues(
String tableName,
String rowKey,
List<String> columnNames
) throws IOException {
//返回数据
Map<String, Tuple<String, Long>> result = Maps.newHashMap();
// 获取表
Table table = null;
try {
tableName = checkNamespace(tableName);
table = getTable(tableName);
Get get = new Get(Bytes.toBytes(rowKey));
columnNames.forEach(columnName ->
get.addColumn(Bytes.toBytes(DEFAULT_FAMILY), Bytes.toBytes(columnName))
);
Result hTableResult = table.get(get);
if (hTableResult != null && !hTableResult.isEmpty()) {
for (Cell cell : hTableResult.listCells()) {
String value = Bytes.toString(
cell.getValueArray(),
cell.getValueOffset(),
cell.getValueLength()
);
String key = Bytes.toString(
cell.getQualifierArray(),
cell.getQualifierOffset(),
cell.getQualifierLength()
);
long ts = cell.getTimestamp();
result.put(key, Tuple.of(value, ts));
}
}
} catch (IOException e) {
logger.error(
"查询多个单元格的数据失败,tableName:{},rowKey:{},familyName:{},columnNames:{},exception:{}",
tableName,
rowKey,
DEFAULT_FAMILY,
columnNames,
e.getMessage()
);
throw e;
} finally {
close(table);
}
return result;
}
@Override
public BufferedMutator getBufferedMutator(String tableName)
throws IOException {
tableName = checkNamespace(tableName);
//如果不存在同步创建表
if (!tableExistSet.containsKey(tableName)) {
synchronized (this) {
if (!tableExistSet.containsKey(tableName)) {
if (
createTable(
tableName,
Collections.singletonList(DEFAULT_FAMILY),
hbaseConfig.getDefaultNumRegions()
)
) {
tableExistSet.put(tableName, true);
}
}
}
}
BufferedMutator bufferedMutator = this.bufferedMutatorMap.get(tableName);
if (bufferedMutator == null) {
synchronized (this) {
bufferedMutator = this.bufferedMutatorMap.get(tableName);
if (bufferedMutator == null) {
bufferedMutator = initBufferedMutator(tableName);
this.bufferedMutatorMap.put(tableName, bufferedMutator);
}
}
}
return bufferedMutator;
}
private BufferedMutator initBufferedMutator(String tableName)
throws IOException {
BufferedMutatorParams params = new BufferedMutatorParams(
TableName.valueOf(tableName)
);
params.listener((e, bufferedMutator) -> {
if (e != null) {
throw e;
}
});
return this.connection.getBufferedMutator(params);
}
private void close(Table table) {
if (table != null) {
try {
table.close();
} catch (IOException e) {
logger.error("关闭Table失败", e);
}
}
}
private String checkNamespace(String tableName) {
if (!tableName.contains(":")) {
return hbaseConfig.getDefaultNamespace() + ":" + tableName;
}
return tableName;
}
public Boolean checkCreateTable(String tableName) throws IOException {
if (admin.tableExists(TableName.valueOf(tableName))) {
return true;
}
synchronized (this) {
createTable(
tableName,
Collections.singletonList(DEFAULT_FAMILY),
hbaseConfig.getDefaultNumRegions()
);
}
return true;
}
@Override
public <R> List<Map<String, String>> findByStartAndEnd(
String tableName,
String family,
R startRow,
R endRow,
int batch,
int cache
) {
return this.findByStartAndEnd(
tableName,
family,
startRow,
endRow,
batch,
cache,
(result, rowNum) -> {
Cell[] cells = result.rawCells();
Map<String, String> map = Maps.newHashMap();
for (Cell cell : cells) {
map.put("row", Bytes.toString(CellUtil.cloneRow(cell)));
map.put(
new String(CellUtil.cloneQualifier(cell)),
Bytes.toString(CellUtil.cloneValue(cell))
);
}
return map;
}
);
}
@Override
public <T, R> List<T> findByStartAndEnd(
String tableName,
String family,
R startRow,
R endRow,
int batch,
int cache,
final RowMapper<T> mapper
) {
final Scan scan = new Scan();
scan.setMaxVersions(1);
scan.setBatch(batch);
scan.setCaching(cache);
if (ObjectUtils.isNotEmpty(startRow)) {
scan.withStartRow(HBaseUtils.transform(startRow));
}
if (ObjectUtils.isNotEmpty(endRow)) {
scan.withStopRow(HBaseUtils.transform(endRow));
}
// 设置PageFilter
PageFilter pageFilter = new PageFilter(batch);
scan.setFilter(pageFilter);
scan.addFamily(Bytes.toBytes(family));
return find(tableName, scan, mapper);
}
@Override
public <T> T find(
String tableName,
final Scan scan,
final ResultsExtractor<T> action
) {
return execute(
tableName,
table -> {
ResultScanner scanner = table.getScanner(scan);
try {
return action.extractData(scanner);
} finally {
scanner.close();
}
}
);
}
@Override
public <T> List<T> find(
String tableName,
final Scan scan,
final RowMapper<T> action
) {
return find(tableName, scan, new RowMapperResultsExtractor<>(action));
}
@Override
public <T> T execute(String tableName, TableCallback<T> action) {
if (org.springframework.util.StringUtils.isEmpty(tableName)) {
throw new IllegalArgumentException("hbase tableName is not null");
}
if (action == null) {
throw new IllegalArgumentException(
"hbase Callback object must not be null"
);
}
Table table = null;
try {
table = getTable(tableName);
return action.doInTable(table);
} catch (Throwable th) {
if (th instanceof Error) {
throw ((Error) th);
}
if (th instanceof RuntimeException) {
throw ((RuntimeException) th);
}
throw convertHBaseAccessException((Exception) th);
} finally {
releaseTable(table);
}
}
private void releaseTable(Table table) {
HBaseUtils.releaseTable(table);
}
public RuntimeException convertHBaseAccessException(Exception ex) {
return HBaseUtils.convertHBaseException(ex);
}
private Table getTable(String tableName) throws IOException {
return connection.getTable(TableName.valueOf(tableName));
}
@Override
public void batchPut(String tableName, List<Put> puts) throws IOException {
try (Table table = getTable(tableName)) {
table.put(puts);
}
}
@Override
public Result[] get(String tableName, String[] rowKeys) throws IOException {
List<Get> gets = Lists.newArrayList();
for (String rowKey : rowKeys) {
gets.add(createGet(rowKey));
}
return get(tableName, gets);
}
@Override
public Result get(String tableName, String rowKey) throws IOException {
Get get = createGet(rowKey);
return get(tableName, get);
}
@Override
public Result get(String tableName, Get get) throws IOException {
try (Table table = getTable(tableName)) {
return table.get(get);
}
}
@Override
public Result[] get(String tableName, List<Get> gets) throws IOException {
try (Table table = getTable(tableName)) {
return table.get(gets);
}
}
@Override
public Get createGet(String rowKey) {
return new Get(Bytes.toBytes(rowKey));
}
String getRedis(String key) {
return localCacheComponent.get(key);
}
@Override
public Map<String, Map<String, Tuple<String, Long>>> getColumnValues(
String tableName,
List<String> rowKeys,
List<String> columnNames
) {
//返回数据
Map<String, Map<String, Tuple<String, Long>>> results = new HashMap<>();
// 获取表
Table table = null;
try {
byte[] familyByte = Bytes.toBytes(DEFAULT_FAMILY);
List<byte[]> columnBytes = columnNames
.stream()
.map(Bytes::toBytes)
.collect(Collectors.toList());
tableName = checkNamespace(tableName);
table = getTable(tableName);
List<Get> multiGet = new ArrayList<>();
for (String rowKey : rowKeys) {
Get get = new Get(Bytes.toBytes(rowKey));
columnBytes.forEach(columnByte -> {
get.addColumn(familyByte, columnByte);
});
multiGet.add(get);
}
Result[] hTableResults = table.get(multiGet);
if (hTableResults != null) {
for (Result hTableResult : hTableResults) {
if (!hTableResult.isEmpty()) {
Map<String, Tuple<String, Long>> result = new HashMap<>();
for (Cell cell : hTableResult.listCells()) {
String value = Bytes.toString(
cell.getValueArray(),
cell.getValueOffset(),
cell.getValueLength()
);
String key = Bytes.toString(
cell.getQualifierArray(),
cell.getQualifierOffset(),
cell.getQualifierLength()
);
long ts = cell.getTimestamp();
result.put(key, Tuple.of(value, ts));
}
String rowKey = Bytes.toString(hTableResult.getRow());
results.put(rowKey, result);
}
}
}
} catch (IOException e) {
logger.error(
"查询多个单元格的数据失败,tableName:{},rowKeys:{},familyName:{},columnNames:{}",
tableName,
rowKeys,
DEFAULT_FAMILY,
columnNames,
e
);
throw new RuntimeException("getColumnValues数据失败");
} finally {
close(table);
}
return results;
}
}