Apache HBase基础知识

Posted 终回首

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Apache HBase基础知识相关的知识,希望对你有一定的参考价值。

你想要什么?你在做什么?它们一样吗?你今天比昨天更好吗?

我的hbase笔记


一、简介

Apache HBase是一个分布式、可扩展的、列式存储的、支持大数据量的实时开源数据库。HBase来自Google三篇论文中的BigTable。适合存储结构化和半结构化数据。随机读写性能优秀。支持增删改查,不支持sql,不过可通过Phoenix或Hive映射表的形式间接的用SQL操作HBase。

二、数据模型

数据模型示例图

  • Table
    表,一个表包括多个行(Row)。上图即为一张表的示意图
  • Row
    行,一行由一个Rowkey、一个或多个列簇(一般不超过3个)、一个或多个列与列对应的值组成。存储的时候是按照Rowkey字典序排序存储的。因此Rowkey的设计非常重要。
  • Column Family
    列簇,一个列簇包括多个列。
  • Column
    列,一个列由一个列名和列值组成
  • Column Qualifier
    列名又叫列限定符
  • Cell
    单元格,由Rowkey、Column Family、Column、Timestamp确定一个Cell。Cell中的数据都以字节码形式存储。
  • Timestamp
    时间戳,根据时间戳的不同来区分Version(版本)

类似如下模式:

(Table, Rowkey, Column Family, Column, Timestamp) -> value

用更像编程语言的风格表示如下:

SortedMap<
	Rowkey, List<
		SortedMap<
			Column, List<
				Value, Timestamp
			>
		>
	>
>

或者用一行来表示:

SortedMap<Rowkey, List<SortedMap<Column, List<Value, Timestamp>>>>

第一个SortedMap代表一张表,包含一个列族的List,列族中包含了另一个SortedMap存储列和相应的值。这些值在最后的List中,存储了值和该值被设置的时间戳。

三、架构与原理

  • Client
    1 提供访问hbase的API。
    2 查询hbase:meta元数据表,根据元数据找到对应region所在的RegionServer,联系RegionServer发出读写请求。
    3 有过读写操作的元数据信息会缓存到Client,加速后续访问。
  • Zookeeper
    1 存储hbase的元数据表hbase:meta
    2 通过Zoopkeeper来保证集群中只有1个master在运行,如果master异常,会通过竞争机制产生新的master提供服务
    3 通过Zoopkeeper来监控RegionServer的状态,当RegionSevrer有异常的时候,通过回调的形式通知Master RegionServer上下线的信息
  • HMaster
    1 监控RegionServer状态,通常和NameNode运行在同一节点。
    2 处理RegionServer故障转移
    3 处理元数据的变更,处理表级别的增删改查(ddl)
    4 处理region的分配、拆分或转移
    5 在空闲时间进行数据的负载均衡
    6 通过Zookeeper发布自己的位置给客户端
  • RegionServer
    1 负责服务和管理地区,通常和DataNode运行在同一节点。
    2 负责存储HBase的实际数据,负责表内数据的增删改查(dml)
    3 刷新缓存到HDFS
    4 维护Hlog
    5 执行压缩
    6 处理Region分片
  • Region
    表会根据配置的大小根据Rowkey分为多个Region。
Table                    (HBase table)
    Region               (Regions for the table)
        Store            (Store per ColumnFamily for each Region for the table)
            MemStore     (MemStore for each Store for each Region for the table)
            StoreFile    (StoreFiles for each Store for each Region for the table)
                Block    (Blocks within a StoreFile within a Store for each Region for the table) 

1 写流程

  1. Client 访问zookeeper读取元数据,找到要写入表对应的RegionServer地址
  2. 在RegionServer上发起写操作
  3. 找到写入请求对应的Region,写入Region的MemStore
  4. 写入操作达到阈值后会刷写到磁盘,成为一个StoreFile
  5. 随着StoreFile不断增多,增长到一定阈值时会触发minor_compaction操作(多个小文件会降低性能),将多个StoreFile合并成一个大的StoreFile。同时会进行版本合并和数据删除
  6. 不断合并StoreFile会生成越来越大的StoreFile,当超过一定阈值会进行split,会先将大Region下线,将大Region切割成2个Region,2个新Region会被分配到RegionServer上,减少大Region的读写压力。

2 读流程

  1. Client访问Zookeeper获取元数据
  2. 从元数据表查找查询目标表的Region及RegionServer信息
  3. 去RegionServer找到对应的Region
  4. 先从写缓存MemStore找,找不到就从读缓存BlockCache找,还找不到就遍历StoreFile找
  5. 找到数据后先将数据保存到读缓存BlockCache,再将数据返回。blockcache逐渐满了之后,会采用LRU的淘汰策略。

3 RegionServer故障转移流程

  1. 某个RegionServer挂掉,该RegionServer上的Region都变为不可用
  2. HMaster会根据其他RegionServer的负载情况,将Region分配到较为空闲的机器上
  3. 接手的RegionServer根据元数据里的信息重新组织内存结构,重新执行WAL就可以最大程度恢复到之前RegionServer、宕机前的状态了

转移后只丢失了还在写缓存MemStore里未写到StoreFile的部分数据。

四、元数据

元数据hbase:meta表保存系统中所有 region 的列表,hbase:meta被存储在 ZooKeeper 中。

hbase:meta 表的结构如下:

Key

  • Region key 的格式 ([table],[region start key],[region id])

Values

  • info:regioninfo (当前 region 的序列化实例 HRegionInfo)
  • info:server (包含当前 region 的 RegionServer 的 server:port)
  • info:serverstartcode (包含此 region 的 RegionServer 进程的开始时间)

当一个表处于拆分过程中时,将创建额外两个列,称为 “info:splitA” 和 “info:splitB”。这些列代表两个子 region。它们的值也是序列化后的 HRegionInfo 实例。region 分割完成后,最终将删除此行。

五、命令行

常用命令

启动命令行

$ ./bin/hbase shell

0 通用命令

version

查看hbase版本

案例

hbase> version

1 表管理命令

create

创建表,指定表名、几个key-value形式的列簇、几个key-value形式的配置。

案例

hbase> # 创建一个表名字叫t1,包含一个名叫f1的列簇,versions设置为5
hbase> create 't1', {NAME => 'f1', VERSIONS => 5}
hbase> # 创建一个表名字叫t1,包含3个列簇,列簇名分别叫f1、f2、f3
hbase> create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}

hbase> # 下面是更简便的写法
hbase> # 创建一个表名字叫t1,包含3个列簇,列簇名分别叫f1、f2、f3
hbase> create 't1', 'f1', 'f2', 'f3'
hbase> # 创建一个表名字叫t1,包含一个名叫f1的列簇,versions(版本)设置为1,TTL(过期时间)设置为2592000毫秒,设置加入查询缓存
hbase> create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, BLOCKCACHE => true}
hbase> # 创建一个表名字叫t1,包含一个名叫f1的列簇,设置配置项hbase.hstore.blockingStoreFiles的值为10
hbase> create 't1', {NAME => 'f1', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}}

list

查看hbase所有的表,支持正则

案例

hbase> #查看所有的表
hbase> list
hbase> # 查看以ods开头的所有表
hbase> list 'ods*'

alter

修改schema,指定表名,修改列簇或其属性

案例

hbase> #  创建一个表名叫t1的表,包含一个列簇叫f1
hbase> alter 't1', NAME => 'f1'
hbase> #  修改t1表,增加一个叫f2的列簇并设置存储在内存中,增加一个叫f3的列簇并设置versions为5
hbase> alter 't1', 'f1', {NAME => 'f2', IN_MEMORY => true}, {NAME => 'f3', VERSIONS => 5}
hbase> #  修改t1表,从t1表删除名为f1的列簇
hbase> alter 't1', 'delete' => 'f1'

hbase> #  也可以修改表相关的属性,如最大文件大小、只读、缓存刷新大小、延迟日志刷新时间等等
hbase> #  修改t1表,设置region的最大存储大小为128MB
hbase> alter 't1', MAX_FILESIZE => '134217728'

disable

禁用表

案例

hbase> disable 't1'

drop

删除表,删除表前必须禁用表

案例

hbase> drop 't1'

enable

修改表状态为可用

案例

hbase> enable 't1'

2 数据控制命令

count

统计表的行数,行数具体指rowkey的数量。

案例

hbase> #  查看表t1的行数。默认count实现是mapreduce任务速度较慢
hbase> count 't1'

put

新增一条记录

案例

添加一个列名为value,值为ts1的记录到表t1下的rowkey为r1下的列簇为c1下
hbase> put 't1', 'r1', 'c1', 'value', ts1

get

获取一行(row)或者一个单元格(cell)

案例

hbase> #  获取表名为t1的rowkey为r1的所有数据
hbase> get 't1', 'r1'
hbase> #  获取表名为t1的rowkey为r1的时间戳在ts1到ts2的闭区间的数据
hbase> get 't1', 'r1', {TIMERANGE => [ts1, ts2]}
hbase> #  获取表名为t1的rowkey为r1的列簇名为c1的数据
hbase> get 't1', 'r1', {COLUMN => 'c1'}
hbase> #  获取表名为t1的rowkey为r1的列簇名为c1、c2、c3的所有数据
hbase> get 't1', 'r1', {COLUMN => ['c1', 'c2', 'c3']}
hbase> #  获取表名为t1的rowkey为r1的列簇名为c1的时间戳为ts1的所有数据
hbase> get 't1', 'r1', {COLUMN => 'c1', TIMESTAMP => ts1}
hbase> #  获取表名为t1的rowkey为r1的列簇名为c1的时间戳为ts1到ts2的闭区间的版本为4的所有数据
hbase> get 't1', 'r1', {COLUMN => 'c1', TIMERANGE => [ts1, ts2], VERSIONS => 4}
hbase> #  获取表名为t1的rowkey为r1的列簇名为c1的时间戳为ts1的版本为4的所有数据
hbase> get 't1', 'r1', {COLUMN => 'c1', TIMESTAMP => ts1, VERSIONS => 4}
hbase> #  获取表名为t1的rowkey为r1的列簇名为c1的binary为abc的数据
hbase> get 't1', 'r1', {FILTER => “ValueFilter(=, 'binary:abc')”}
hbase> #  获取表名为t1的rowkey为r1的列簇名为c1的所有数据
hbase> get 't1', 'r1', 'c1'
hbase> #  获取表名为t1的rowkey为r1的列簇名为c1列名为c2的所有数据
hbase> get 't1', 'r1', 'c1', 'c2'
hbase> #  获取表名为t1的rowkey为r1的列名为c1、c2的所有数据
hbase> get 't1', 'r1', ['c1', 'c2']

scan

扫描,范围查询。可以设置多个条件。

案例

hbase> #  获取表名为.META的列簇为info列名为regioninfo的所有数据
hbase> scan '.META.', {COLUMNS => 'info:regioninfo'}
hbase> #  获取表名为t1的列簇为info列簇为c1、c2的开始rowkey为xyz的数据,限制返回10条
hbase> scan 't1', {COLUMNS => ['c1', 'c2'], LIMIT => 10, STARTROW => 'xyz'}
hbase> #  获取表名为t1的列簇为info列簇为c1、c2的开始rowkey为xyz的数据,限制返回10条
hbase> scan 't1', {COLUMNS => 'c1', TIMERANGE => [1303668804, 1303668904]}
hbase> #  获取表名为t1的前缀为row2并且列名等于xyz并且时间戳处于开区间( 123, 456)中的所有数据
hbase> scan 't1', {FILTER => “(PrefixFilter ('row2') AND (QualifierFilter (>=, 'binary:xyz'))) AND (TimestampsFilter ( 123, 456))”}
hbase> #  获取表名为t1的分页后的所有数据。page=1,offset=0
hbase> scan 't1', {FILTER => org.apache.hadoop.hbase.filter.ColumnPaginationFilter.new(1, 0)}
hbase> #  获取表名为t1的所有的cell,包括标记删除但未真正删除的cell
hbase> scan 't1', {RAW => true, VERSIONS => 10}

truncate

截断表,内部逻辑为 1 禁用表 2 删除表 3 重建表

案例

hbase>truncate 't1'

工具类命令

assign

直译为指派,实际做的是是让下线的region上线

案例

hbase> assign ‘REGION_NAME’

balancer

触发集群开始重新分配region

案例

hbase> balancer

balance_switch

开启或关闭自动 balance

案例

hbase> balance_switch true
hbase> balance_switch false

close_region

关闭region,一旦关闭region将会一直处于closed状态,除非使用assign命令才能让region上线

案例

hbase> close_region ‘REGIONNAME’, ‘SERVER_NAME’

compact

执行小合并,合并region,可以合并一个表的所有region或指定region或一个region的一个列族

案例

hbase> #  合并表t1的全部region
hbase> compact ‘t1’
hbase> #  合并region名为r1的region
hbase> compact ‘r1’
hbase> #  合并region名为r1的c1列簇
hbase> compact ‘r1’, ‘c1’
hbase> #  合并表名为t1的c1列簇
hbase> compact ‘t1’, ‘c1’

major_compact

执行大合并

案例

hbase> #  合并表t1的全部region
hbase> major_compact 't1'
hbase> #  合并region名为r1的region
hbase> major_compact 'r1'
hbase> #  合并region名为r1的c1列簇
hbase> major_compact 'r1', 'c1'
hbase> #  合并表名为t1的c1列簇
hbase> major_compact 't1', 'c1'

split

切割表或者指定region

案例

hbase> #  切割表
split ‘tableName’
hbase> #  切割region
split ‘regionName’ # format: ‘tableName,startKey,id’
hbase> #  根据指定切割的rowkey切割表
split ‘tableName’, ‘splitKey’
hbase> #  根据指定切割的rowkey切割region
split ‘regionName’, ‘splitKey’

move

移动region,如果指定RegionServer则会移动到指定RegionServer上,如果不指定RegionServer,将会随机移动

案例

hbase> #  随机移动一个region
hbase> move ‘ENCODED_REGIONNAME’
hbase> #  将region移动到指定RegionServer上
hbase> move ‘ENCODED_REGIONNAME’, ‘SERVER_NAME’

其他命令或更详细的内容,可以参考脚本官方参考文档
hbase脚本官方参考文档地址:https://learnhbase.wordpress.com/2013/03/02/hbase-shell-commands/

六、表设计

0 最佳标准:

  • 目标是把 region 的大小限制在 10 到 50 GB 之间。
  • 目标是限制 cell 的大小在 10 MB 之内,如果使用的是 mob 类型,限制在 50 MB 之内。否则,考虑把 cell的数据存储在 HDFS 中,并在 HBase 中存储指向该数据的指针。
  • 典型的 scheme 每张表包含 1 到 3 个列族。HBase 表设计不应当和 RDBMS 表设计类似。
  • 对于拥有 1 或 2 个列族的表来说,50-100 个 region 是比较合适的。请记住, region 是列族的连续段。
  • 保持列族名称尽可能短。每个值都会存储列族的名称(忽略前缀编码)。它们不应该像典型 RDBMS 那样,是自文档化,描述性的名称。
  • 如果你正在存储基于时间的机器数据或者日志信息,并且 row key 是基于设备 ID 或者服务 ID + 时间,最终会出现这样一种情况,即更旧的数据 region 永远不会有额外写入。在这种情况下,最终会存在少量的活动 region和大量不会再有新写入的 region。对于这种情况,可以接受更多的 region 数量,因为资源的消耗只取决于活动 region。
  • 如果只有一个列族会频繁写,那么只让这个列族占用内存。当分配资源的时候注意写入模式。

1 案例一:log数据或时序数据

假定数据包含如下元素

  • Hostname
  • Timestamp
  • Log event
  • Value/message

1.1 时间戳位于rowkey开头位置

首先想到的是[timestamp][hostname][log-event],这种设计会遇到递增问题,问题描述:https://hbase.apache.org/book.html#timeseries。HBase并不适合直接存储时序数据,如果想要最佳的存储时序数据的体验,官方建议使用OpenTSDB,或者TDengine等其他方案。

更可取的是分桶存储。具体操作,是给时间戳取模,根据余数将数据放到多个bucket里。如果顺序读取很重要,那么这种方式是很好用的方法。需要注意的是分的桶是多少个,那么查询的scan也将是多少个。

// bucket的计算方法
long bucket = timestamp % numBuckets;
// rowkey的结构
[bucket][timestamp][hostname][log-event]

如上所述,要为特定TIMERANGE选择数据,需要对每个桶执行扫描。 例如,100桶将在Keyspace中提供广泛的分布,但它需要100个扫描来获取单个时间戳的数据,因此需要有所权衡。

1.2 hostname位于rowkey开头位置

当host很多,且值域分布范围很广,同时根据host去scan很重要的时候,[hostname][log-event][timestamp]这个设计就是较为好用的。

1.3 时间戳、倒取时间戳

如果读取最近记录很重要,那么可以通过这样的计算反转时间戳new_timestamp = Long.MAX_VALUE – timestamp。最后rowkey结构为[hostname][log-event][new_timestamp]

1.4 变长、定长rowkey

hostname很短的时候,log-event也很短的时候,那rowkey会很短。如果hostname=myserver1.mycompany.com,且
log-event=com.package1.subpackage2.subsubpackage3.ImportantService的时候,rowkey就会占用很大空间。
这是我们不能接受的。

方法一:可以用MD5算法将不定长的元素给他变成定长元素。

[MD5 hash of hostname] = 16 bytes
[MD5 hash of event-type] = 16 bytes
[timestamp] = 16 bytes

此时三个元素就都是定长的了

方法二:将hostname和log-event存到关系库,hbase只保存关系库对应表的主键

定长的好处是可以避免超长rowkey,只适合随机get查询。劣势是按照hostname或者log-event顺序scan的场景无法满足。

1.5 总结

这种存储这种数据并不是hbase的最佳场景,只能是通过设计让使用体验相对好一些。OpenTSDB之类类的时序数据库才是存储这种数据的最佳方案。

2 案例二:客户、订单数据

假定客户数据包含以下几项:

  • Customer number
  • Customer name
  • Address (e.g., city, state, zip)
  • Phone numbers etc.

假定订单数据包含以下几项:

  • Customer number
  • Order number
  • Sales date
  • A series of nested objects for shipping locations and line-items

假定用户编号+订单编号可以唯一标识一个订单,这两个属性组成的rowkey类似这样:

[customer number][order number]

这里同样也会面临案例一中出现过的变长rowkey问题。
通过MD5可以让变长的属性,转换为定长。

[MD5 of customer number] = 16 bytes
[MD5 of order number] = 16 bytes

2.1 单表、多表

传统关系数据库在这种场景中一般是分为两个表一个customer和order表这是一种方案。另一种可选方案则是把两个表融合成一个表。

融合方案:
customer记录的rowkey可以类似这样[customer-id][type];type=1时标识这条数据是顾客记录
order记录的rowkey可以类似这样[customer-id][type][order];type=2时标识这条数据是订单记录

这种特殊的 CUSTOMER++ 方法的优势在于,它可以根据客户 ID 组织许多不同的记录类型(例如,一次扫描可以获取有关该客户的所有信息)。缺点是扫描特定的记录类型并不容易。

2.2 订单表设计

假设订单表包含如下字段

  • Order 订单
    一个订单可以关联多个发货地址
  • LineItem 订单项
    一个发货地址可以关联多个订单项

有多种选择来存储这种数据

2.2.1 完全标准化

这种方式ORDER、SHIPPING_LOCATION 和 LINE_ITEM 每种数据集都是单独的表。这种方法类似在RDBMS里的结构,虽然可以这样设计,但是无法像RDBMS一样join,如果要join需要自己实现了

  1. order表rowkey
    [customer number][order number]
  2. LINE_ITEM 表的复合行键类似这样
    ````[customer number][order number][shipping location number]```
  3. LINE_ITEM 表的复合行键类似这样
    ````[customer number][order number][shipping location number][line item number]```

2.2.2 具有记录类型的单表

3类数据放到一张表里,每种数据根据type区分

  1. Order数据的rowkey将是这样的
    [customer number][order number][ORDER record type]
  2. ShippingLocation数据的rowkey将是这样的
    [customer number][order number][SHIPPING record type][shipping location number]
  3. LineItem数据的rowkey将是这样的
    [customer number][order number][LINE record type][shipping location number][line item number]

2.2.3 非规范化

具有记录类型的单表的另一种形式是对某些对象结构进行非规范化和扁平化。例如把ShippingLocation属性放到每一个LineItem里,作为LineItem里的一个列。

LineItem表的rowkey可以像这样
[customer number][order number][LINE record type][line item number]

LineItem里的列可以这样

  • itemNumber

  • quantity

  • price

  • shipToLine1 (ShippingLocation的发货线路1)

  • shipToLine2 (ShippingLocation的发货线路2)

  • shipToCity (ShippingLocation的发货目的城市)

  • shipToCounty (ShippingLocation的发货目的区县)

这种设计的优点是结构不那么复杂,缺点是更新会比较复杂。

2.2.4 对象 BLOB

Order表的rowkey像这样[customer number][order number]

其中有一个order列,order列中存储一个对象,对象包括三个对象也就是Order、ShippingLocations 和 LineItems

3 案例三:dist-list

假设数据如下
user123, firstname, Paul
user234, lastname, Smith

那么有两种方案,宽和高的选择

第1种 高

把列放到rowkey里

数据如下所示

<user1><name1>:zs
<user1><name2>:ls
<user1><name3>:ww

rowkey=<user1><name1> value=zs

查询某个用户的指定30条数据将是:

scan { STARTROW => 'FixedWidthUsername' LIMIT => 30}

一般的使用模式是只读取这些列表的前 30 个值,很少访问读取更深的列表。一些用户在这些列表中的总值可能是 ⇐ 30,而一些用户可能有数百万(即幂律分布)

这种方式的优势是方便查询和分页。如果没有其他特殊需求,一般而言更推荐使用这种方案

第2种 宽

把所有列存到一个row的一个column family

<user1><name1>:zs
<user1><name2>:ls
<user1>name3>:ww

rowkey=user1 column1=name1 value=zs column2=name2 value=ls 

这种方案的劣势是需要手动筛选列和分页,优势是可以使用Hbase事务。

七、备份和恢复

1 离线

hadoop distcp
完全停止HBase服务,使用distcp命令运行MapReduce任务进行备份,服务在一段时间内不可用。

用法:
https://hadoop.apache.org/docs/stable/hadoop-distcp/DistCp.html

2 在线

https://blog.csdn.net/l1028386804/article/details/88817298

八、性能优化

1 操作系统

1.1 设置禁止交换

在/etc/sysctl.conf 文件里添加如下参数:

vm.swappiness=0

重启系统生效

2 Java

2.1 垃圾回收器

有两种方案

  1. 新生代用ParallelGC,老年代用CMS
    -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
  2. G1
    -XX:+UseG1GC

小堆(4G及以下)选择 1,大堆(32G及以上)考虑用2,如果堆内存介入 4~32G 之间,可自行测试两种方案。

2.2 JVM参数调优

设 Full GC 后老年代剩余大小空间(使用GC Viewer分析GC日志可以得到Full GC后老年代的剩余空间)为 M,那么堆的大小建议 3 ~ 4倍 M,新生代为 1 ~ 1.5 倍 M,老年代应为 2 ~ 3 倍 M

# 设置更早启动CMS
-XX:CMSInitiatingOccupancyFraction=60
# 打印垃圾回收日志
-verbose:gc -XX: +PrintGCDetails -XX:+PrintGCTimeStamps
# 设置日志文件保存路径
-Xloggc:$HBASE_HOME/logs/gc-$(hostname)-hbase.log

一套推荐配置

export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS -Xmx8g -Xms8g –Xmn3g -XX:+UseParNewGC -XX:+UseConcMarkSweepGC \\
-XX:CMSInitiatingOccupancyFraction=60   -verbose:gc \\
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps \\
-Xloggc:$HBASE_HOME/logs/gc-$(hostname)-hbase.log

export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms32g -Xmx32g –Xmn12g -XX:+UseParNewGC -XX:+UseConcMarkSweepGC \\
-XX:CMSInitiatingOccupancyFraction=60   -verbose:gc \\
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps \\
-Xloggc:$HBASE_HOME/logs/gc-$(hostname)-hbase.log

3 Hbase

3.1 Hbase配置

# 垃圾回收
# 开启 MSLAB 功能,默认开启,可以检查是否开启
hbase.hregion.memstore.mslab.enabled=true

# 读写缓存
# 设置读缓存策略为BucketCache,设置存储介质为堆外内存
hbase.bucketcache.ioengine=offheap
# 设置bucketcache大小
hbase.bucketcache.size=2G
# 设置读缓存大小,默认为0.4,如果读场景多可适当增大,反之减少
hfile.block.cache.size 
# 设置写缓存大小,默认为0.4,如果写场景多可适当增大,反之减少
hbase.regionserver.global.memstore.size
# 对于大负载的put(达到了M范围)或是大范围的Scan操作,handler数目不易过大,易造成OOM。 对于小负载的put或是get,delete等操作,handler数要适当调大。根据上面的原则,要看我们的业务的情况来设置。(具体情况具体分析)。
hbase.regionserver.handler.count

3.2 Hbase建表

3.2.1 启用压缩和编码

编码推荐使用FAST_DIFF

两种压缩算法,期待高压缩比使用GZIP,期待高解压速度使用SNAPPY

案例

# 新建表
create 'test', {NAME => 'f', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'SNAPPY'}
create 'test', {NAME => 'f', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'GZ'}
# 修改已建表
alter 'test', {NAME => 'f', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'SNAPPY'}
alter 'test', {NAME => 'f', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'GZ'}

3.2.2 预分区

如果提前知道key的分布,可以预分区。预分区可以避免写入热点,提高写入速度

3.2.2.1 根据算法分区
create 'test', {NAME => 'f', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'GZIP',NUMREGIONS => 100, SPLITALGO => 'HexStringSplit'}
  • NUMREGIONS 设置分区后的region个数
  • SPLITALGO 设置分区规则,有三种分别是HexStringSplit: rowkey是十六进制的字符串作为前缀的、DecimalStringSplit: rowkey是10进制数字字符串作为前缀的、UniformSplit: rowkey前缀完全随机
3.2.2.2 自定义分区

分区文件如下所示

cat /opt/tmp/splits.txt

a
b
c
d
e

建分区表命令

create 'test', 'f', SPLITS_FILE => '/opt/tmp/splits.txt'

自定义的分区是rowkey完全匹配,也就是rowkey为a的属于一个region,rowkey为b的属于另一个region。一共分为5个region

3.3 Hbase读写

  1. put必须批量提交
  2. 写优先时,可关闭WAL,通过手动flushTable让数据落地磁盘
  3. rowkey设计时必须避免写入热点

3.4 二级索引

两种方案:

  1. 协处理器
  2. Apache Phoenix

建议使用Apache Phoenix创建索引

3.4.1 Apache Phoenix建二级索引

3.4.1.1 函数式索引

Functional Indexes函数式索引,从Phoeinx4.3以上就支持函数索引,其索引不局限于列,可以合适任意的表达式来创建索引,当在查询时用到了这些表达式时就直接返回表达式结果

3.4.1.2 全局索引

Global index全局索引,适用于读多写少的业务场景。在默认情况下如果想查询的字段不是索引字段的话索引表不会被使用。

3.4.1.3 本地索引

Local Index适用于写操作频繁以及空间受限制的场景,使用Local indexing的时候即使查询的字段不是索引字段索引表也会被使用,这会带来查询速度的提升,这点跟Global indexing不同

3.4.1.4 不可变索引

Immutable index不可变索引,适用于数据只增加不更新并且按照时间先后顺序存储。

3.4.1.5 可变索引

Mutable index 可变索引,默认建索引都是可变索引。可变索引分为全局和本地。

具体操作参考Phoenix 官方文档索引部分
http://phoenix.apache.org/secondary_indexing.html

4 HDFS配置

# 备份间隔默认3秒 可以调高,避免hdfs频繁备份,从而提高吞吐率。
dfs.replication.interval
# namenode线程数默认为8,可以调高这个处理线程数,使得写数据更快
dfs.namenode.handler.count
# 写超时时间 并发写数据量大的时候要调大一些
dfs.datanode.socket.write.timeout
# socket超时时间,并发写时要适当调大
dfs.socket.timeout

参考资料

官方文档:https://hbase.apache.org/book.html#_preface

官方文档中文版:http://hbase.org.cn/

hbase脚本参考文档:https://learnhbase.wordpress.com/2013/03/02/hbase-shell-commands/
《Hbase权威指南》

《Hbase 权威指南》作者的hbase表设计教学视频
https://www.youtube.com/watch?v=_HLoH_PgrLk

备份与恢复的博客https://blog.csdn.net/l1028386804/article/details/88817298

两篇调优的博客
https://blog.csdn.net/baron_nd/article/details/109328893
https://cloud.tencent.com/developer/article/1780331

https://www.jianshu.com/p/8155bf732b82

http://phoenix.apache.org/secondary_indexing.html
PS:有误之处,请不吝赐教!

以上是关于Apache HBase基础知识的主要内容,如果未能解决你的问题,请参考以下文章

Hbase 基础API

Apache Hadoop与Gora的组合功能

Tephra Apache HBase

hbase构建二级索引的实现方式都有哪些

大数据学习--之--HBASE理论基础

Hbase 出现 org.apache.hadoop.hbase.ipc.ServerNotRunningYetException: Server is not running yet 错误(示例代码