【Hbase】-总结
不善于总结,就是在浪费时间
一、Hbase架构及包含角色
- Client:主要作用是提供访问Hbase 的接口,维护了对应的Cache来加速Hbase 的访问,例如cache的.META元数据信息。
- Zookeeper:主要作用是提供Hmaster高可用及RegionServer的监控、元数据入口、集群配置维护等操做:
- 使用ZK的选举机制进行选举leader,如果Hmaster中的一个Master 挂掉,ZK会重新选举一个Master。
- 可以监控ReagionServer的状态,如果有异常,就会通知Master的上下限的信息。
- 通过zk存储元数据的统一入口。
- Hmater:
为RegionServer分配Region.
维护集群的负载均衡。
维护集群元数据信息。
发现失效的Region,并将失效的Region分配到正常的RegionServer上。
当RegionServer失效的时候,协调对应的Hlog的拆分。
HregionServer:
直接进行数据操作的角色,直接对接用户的读写请求。
管理Master为其分配的 region
处理用户的读写请求。
负责和HDFS进行交互操作,将数据存储在HDFS中。
负责region变大后的拆分。
负责Storefile的合并操作。
HDFS:
提供了底层的数据存储服务,并实现数据及Hlog的高可用,确保数据不丢失。
Phonenix:
实现Hbase创建二级索引。
可以提供Hbase的SQL查询。
二、Hbase的几个概念:
- CF列簇:
- 列簇的概念:
列是根据列簇进行分组的。
- 列簇的特点:
- 一张表有自己独立的列簇而列簇在一张表中最多不能多于5个一般在使用的时候都是1个。
- 列簇必须要在表创建的时候创建,并且创建完成后无法改变。
- 每个列簇中的列数是无限制的。
- 同一列簇中表的列是存储在一起的。
- 列在列簇中是有序的,安装字典进行排序。
- 列在运行时候创建。
- 列只有插入后才会存在,空值不进行保存。
- 每个CF可以有一个或多个列成员(ColumnQualifier),列成员不需要在表定义时给出,新的列族成员可以随后按需、动态加入
- 数据按CF分开存储,HBase所谓的列式存储就是根据CF分开存储(每个CF对应一个Store),这种设计非常适合于数据分析的情形
- Rowkey行健:
行键是字节数组, 任何字符串都可以作为行键;
表中的行根据行键进行排序,数据按照Row key的字节序(byte order)排序存储;
所有对表的访问都要通过行键 (单个RowKey访问,或RowKey范围访问,或全表扫描) (二级索引)
Rowkey对Hbase的性能影响非常大,Rowkey的设计就显得尤为的重要。设计的时候要兼顾基于Rowkey的单行查询也要键入Rowkey的范围扫描。
Region区域:
Region类似于数据库的分片和分区的概念,每个Region负责一小部分Rowkey范围的数据的读写和维护,Region包含了对应的起始行到结束行的所有信息。master将对应的region分配给不同的RergionServer,由RegionSever来提供Region的读写服务和相关的管理工作。
Hbase会将一个大表的数据基于Rowkey的不同范围分配到不通的Region中,每个Region负责一定范围的数据访问和存储。这样即使是一张巨大的表,由于被切割到不通的region,访问起来的时延也很低。
HBase自动把表水平(按Row)划分成多个区域(region),每个region会保存一个表里面某段连续的数据;
每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region;
当table中的行不断增多,就会有越来越多的region。这样一张完整的表被保存在多个Region 上。
HRegion是HBase中分布式存储和负载均衡的最小单元(默认256M)。最小单元表示不同的HRegion可以分布在不同的HRegionServer上。但一个HRegion不会拆分到多个server上。
Region实例:
上图模拟了一个Hbase的表是如何拆分成region,以及分配到不同的RegionServer中去。上面是1个Userinfo表,里面有7条记录,其中rowkey为0001到0002的记录被分配到了Region1上,Rowkey为0003到0004的记录被分配到了Region2上,而rowkey为0005、0006和0007的记录则被分配到了Region3上。region1和region2被master分配给了RegionServer1(RS1),Region3被master配分给了RegionServer2(RS2)
备注:这里只是为了更容易的说明拆分的规则,其实真实的场景并不会几条记录拆分到不通的Region上,而是到一定的数据量才会拆分,具体的在Region的拆分那部分再具体的介绍。
Region寻址:
- 既然读写都在RegionServer上发生,每个RegionSever为一定数量的region服务,那么client要对某一行数据做读写的时候如何能知道具体要去访问哪个RegionServer呢?那就是接下来我们要讨论的问题
- 老的Region寻址方式:
- 在Hbase 0.96版本以前,Hbase有两个特殊的表,分别是-ROOT-表和.META.表,其中-ROOT-的位置存储在ZooKeeper中,-ROOT-本身存储了 .META. Table的RegionInfo信息,并且-ROOT-不会分裂,只有一个region。而.META.表可以被切分成多个region。读取的流程如下图所示:
- 第1步:client请求ZK获得-ROOT-所在的RegionServer地址
- 第2步:client请求-ROOT-所在的RS地址,获取.META.表的地址,client会将-ROOT-的相关信息cache下来,以便下一次快速访问
- 第3步:client请求 .META.表的RS地址,获取访问数据所在RegionServer的地址,client会将.META.的相关信息cache下来,以便下一次快速访问
- 第4步:client请求访问数据所在RegionServer的地址,获取对应的数据
- 从上面的路径我们可以看出,用户需要3次请求才能直到用户Table真正的位置,这在一定程序带来了性能的下降。在0.96之前使用3层设计的主要原因是考虑到元数据可能需要很大。但是真正集群运行,元数据的大小其实很容易计算出来。在BigTable的论文中,每行METADATA数据存储大小为1KB左右,如果按照一个Region为128M的计算,3层设计可以支持的Region个数为2^34个,采用2层设计可以支持^17(131072)。那么2层设计的情况下一个 集群可以存储4P的数据。这仅仅是一个Region只有128M的情况下。如果是10G呢? 因此,通过计算,其实2层设计就可以满足集群的需求。因此在0.96版本以后就去掉了-ROOT-表了。
- 新的Region寻址方式:
- 如上面的计算,2层结构其实完全能满足业务的需求,因此0.96版本以后将-ROOT-表去掉了。如下图所示:
- 访问路径变成了3步:
- 第1步:Client请求ZK获取.META.所在的RegionServer的地址。
- 第2步:Client请求.META.所在的RegionServer获取访问数据所在的RegionServer地址,client会将.META.的相关信息cache下来,以便下一次快速访问。
- 第3步:Client请求数据所在的RegionServer,获取所需要的数据。
- 总结去掉-ROOT-的原因有如下2点:
- 其一:提高性能
- 其二:2层结构已经足以满足集群的需求
- 这里还有一个问题需要说明,那就是Client会缓存.META.的数据,用来加快访问,既然有缓存,那它什么时候更新?如果.META.更新了,比如Region1不在RerverServer2上了,被转移到了RerverServer3上。client的缓存没有更新会有什么情况?
- 其实,Client的元数据缓存不更新,当.META.的数据发生更新。如上面的例子,由于Region1的位置发生了变化,Client再次根据缓存去访问的时候,会出现错误,当出现异常达到重试次数后就会去.META.所在的RegionServer获取最新的数据,如果.META.所在的RegionServer也变了,Client就会去ZK上获取.META.所在的RegionServer的最新地址。
- 时间戳(TimeStamp)
每个Cell可能又多个版本,它们之间用时间戳区分。
单元格(Cell)
Cell 由行键,列族:限定符,时间戳唯一决定,数据全部以字节码形式存储。
三、Hbase 读写流程
- Hbase的写逻辑设计到内存、写log、刷盘等操作。
- 写入流程:
- 从上图可以看出氛围3步骤:
- 第1步:Client获取数据写入的Region所在的RegionServer
- 第2步:请求写Hlog
- 第3步:请求写MemStore
- 只有当写Hlog和写MemStore都成功了才算请求写入完成。MemStore后续会逐渐刷到HDFS中。
- 备注:Hlog存储在HDFS,当RegionServer出现异常,需要使用Hlog来恢复数据。
- 读操作流程:
步骤1:client访问Zookeeper,查找.META.表信息。
步骤2:从.META.表查找,获取存放目标数据的HRegion信息,从而找到对应的HRegionServer。
步骤3:通过HRegionServer获取需要查找的数据。
步骤4:HRegionserver的内存分为MemStore和BlockCache两部分,MemStore主要用于写数据,BlockCache主要用于读数据。读请求先到MemStore中查数据,查不到就到BlockCache中查,再查不到就会到StoreFile上读,并把读的结果放入BlockCache。
四、MemStore刷盘:
为了提高Hbase的写入性能,当写请求写入MemStore后,不会立即刷盘。而是会等到一定的时候进行刷盘的操作。具体是哪些场景会触发刷盘的操作呢?总结成如下的几个场景:
- 全局内存控制
- 这个全局的参数是控制内存整体的使用情况,当所有memstore占整个heap的最大比例的时候,会触发刷盘的操作。这个参数是hbase.regionserver.global.memstore.upperLimit,默认为整个heap内存的40%。但这并不意味着全局内存触发的刷盘操作会将所有的MemStore都进行输盘,而是通过另外一个参数hbase.regionserver.global.memstore.lowerLimit来控制,默认是整个heap内存的35%。当flush到所有memstore占整个heap内存的比率为35%的时候,就停止刷盘。这么做主要是为了减少刷盘对业务带来的影响,实现平滑系统负载的目的。
- MemStore达到上限
当MemStore的大小达到hbase.hregion.memstore.flush.size大小的时候会触发刷盘,默认128M大小
RegionServer的Hlog数量达到上限
前面说到Hlog为了保证Hbase数据的一致性,那么如果Hlog太多的话,会导致故障恢复的时间太长,因此Hbase会对Hlog的最大个数做限制。当达到Hlog的最大个数的时候,会强制刷盘。这个参数是hase.regionserver.max.logs,默认是32个。
手工触发
可以通过hbase shell或者java api手工触发flush的操作。
关闭RegionServer触发
在正常关闭RegionServer会触发刷盘的操作,全部数据刷盘后就不需要再使用Hlog恢复数据。
Region使用HLOG恢复完数据后触发
当RegionServer出现故障的时候,其上面的Region会迁移到其他正常的RegionServer上,在恢复完Region的数据后,会触发刷盘,当刷盘完成后才会提供给业务访问。
五、Hlog
Hlog是Hbase实现WAL(Write ahead log)方式产生的日志信息,内部是一个简单的顺序日志。每个RegionServer对应1个Hlog(备注:1.x版本的可以开启MultiWAL功能,允许多个Hlog),所有对于该RegionServer的写入都被记录到Hlog中。Hlog实现的功能就是我们前面讲到的保证数据安全。当RegionServer出现问题的时候,能跟进Hlog来做数据恢复。此外为了保证恢复的效率,Hbase会限制最大保存的Hlog数量,如果达到Hlog的最大个数(hase.regionserver.max.logs参数控制)的时候,就会触发强制刷盘操作。对于已经刷盘的数据,其对应的Hlog会有一个过期的概念,Hlog过期后,会被监控线程移动到 .oldlogs,然后会被自动删除掉。Hbase是如何判断Hlog过期的呢?要找到这个答案,我们就必须了解Hlog的详细结构。
- Hlog结构:
- 从上图我们可以看出多个Region共享一个Hlog文件,单个Region在Hlog中是按照时间顺序存储的,但是多个Region可能并不是完全按照时间顺序。每个Hlog最小单元由Hlogkey和WALEdit两部分组成。Hlogky由sequenceid、timestamp、cluster ids、regionname以及tablename等组成,WALEdit是由一系列的KeyValue组成,对一行上所有列(即所有KeyValue)的更新操作,都包含在同一个WALEdit对象中,这主要是为了实现写入一行多个列时的原子性。注意,图中有个sequenceid。sequenceid是一个store级别的自增序列号,这个非常重要,region的数据恢复和Hlog过期清除都要依赖它。下面就来简单描述一下sequenceid的相关逻辑。
- Memstore在达到一定的条件会触发刷盘的操作,刷盘的时候会获取刷新到最新的一个sequenceid的下一个sequenceid,并将新的sequenceid赋给oldestUnflushedSequenceId,并刷到Ffile中。有点绕,举个例子来说明:比如对于某一个store,开始的时候oldestUnflushedSequenceId为NULL,此时,如果触发flush的操作,假设初始刷盘到sequenceid为10,那么hbase会在10的基础上append一个空的Entry到HLog,最新的sequenceid为11,然后将sequenceid为11的号赋给oldestUnflushedSequenceId,并将oldestUnflushedSequenceId的值刷到Hfile文件中进行持久化。
- Hlog文件对应所有Region的store中最大的sequenceid如果已经刷盘,就认为Hlog文件已经过期,就会移动到.oldlogs,等待被移除。
- 当RegionServer出现故障的时候,需要对Hlog进行回放来恢复数据。回放的时候会读取Hfile的oldestUnflushedSequenceId中的sequenceid和Hlog中的sequenceid进行比较,小于sequenceid的就直接忽略,但与或者等于的就进行重做。回放完成后,就完成了数据的恢复工作。
- Hlog的生命周期
Hlog从产生到最后删除需要经历如下几个过程:
产生
所有涉及到数据的变更都会先写Hlog,除非是你关闭了Hlog
滚动
Hlog的大小通过参数hbase.regionserver.logroll.period控制,默认是1个小时,时间达到hbase.regionserver.logroll.period 设置的时间,Hbase会创建一个新的Hlog文件。这就实现了Hlog滚动的目的。Hbase通过hbase.regionserver.maxlogs参数控制Hlog的个数。滚动的目的,为了控制单个Hlog文件过大的情况,方便后续的过期和删除。
过期
前面我们有讲到sequenceid这个东东,Hlog的过期依赖于对sequenceid的判断。Hbase会将Hlog的sequenceid和Hfile最大的sequenceid(刷新到的最新位置)进行比较,如果该Hlog文件中的sequenceid比刷新的最新位置的sequenceid都要小,那么这个Hlog就过期了,过期了以后,对应Hlog会被移动到.oldlogs目录。
这里有个问题,为什么要将过期的Hlog移动到.oldlogs目录,而不是直接删除呢?
答案是因为Hbase还有一个主从同步的功能,这个依赖Hlog来同步Hbase的变更,有一种情况不能删除Hlog,那就是Hlog虽然过期,但是对应的Hlog并没有同步完成,因此比较好的做好是移动到别的目录。再增加对应的检查和保留时间。
删除
如果Hbase开启了replication,当replication执行完一个Hlog的时候,会删除Zoopkeeper上的对应Hlog节点。在Hlog被移动到.oldlogs目录后,Hbase每隔hbase.master.cleaner.interval(默认60秒)时间会去检查.oldlogs目录下的所有Hlog,确认对应的Zookeeper的Hlog节点是否被删除,如果Zookeeper 上不存在对应的Hlog节点,那么就直接删除对应的Hlog。
hbase.master.logcleaner.ttl(默认10分钟)这个参数设置Hlog在.oldlogs目录保留的最长时间。
六、Hbase主键设置
是三维有序存储的,通过rowkey(行键),column key(column family和qualifier)和TimeStamp(时间戳)这个三个维度可以对HBase中的数据进行快速定位。
- HBase中rowkey可以唯一标识一行记录,在HBase查询的时候,有以下几种方式:
- 1>通过get方式,指定rowkey获取唯一一条记录
- 2>通过scan方式,设置startRow和stopRow参数进行范围匹配
- 3>全表扫描,即直接扫描整张表中所有行记录
rowkey长度原则
- rowkey是一个二进制的,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes,以byte[] 形式保存,一般设计成定长。
- 建议越短越好,不要超过16个字节,原因如下:
- 1>数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;
- 2>MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
- 3>目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特。
rowkey散列原则
- 如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。
rowkey唯一原则
- 必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
七、热点问题
HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。 设计良好的数据访问模式以使集群被充分,均衡的利用。
为了避免写热点,设计rowkey使得不同行在同一个region,但是在更多数据情况下,数据应该被写入集群的多个region,而不是一个
一些常见的避免热点的方法以及它们的优缺点:
- 加盐
- 这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。
- 哈希
哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据
反转
第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。
反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题
其他一些建议
尽量减少行和列的大小在HBase中,value永远和它的key一起传输的。当具体的值在系统间传输时,它的rowkey,列名,时间戳也会一起传输。如果你的rowkey和列名很大,甚至可以和具体的值相比较,那么你将会遇到一些有趣的问题。HBase storefiles中的索引(有助于随机访问)最终占据了HBase分配的大量内存,因为具体的值和它的key很大。可以增加block大小使得storefiles索引再更大的时间间隔增加,或者修改表的模式以减小rowkey和列名的大小。压缩也有助于更大的索引。
列族尽可能越短越好,最好是一个字符
冗长的属性名虽然可读性好,但是更短的属性名存储在HBase中会更好
八、预分区
背景:HBase默认建表时有一个region,这个region的rowkey是没有边界的,即没有startkey和endkey,在数据写入时,所有数据都会写入这个默认的region,随着数据量的不断 增加,此region已经不能承受不断增长的数据量,会进行split,分成2个region。在此过程中,会产生两个问题:1.数据往一个region上写,会有写热点问题。2.region split会消耗宝贵的集群I/O资源。基于此我们可以控制在建表的时候,创建多个空region,并确定每个region的起始和终止rowky,这样只要我们的rowkey设计能均匀的命中各个region,就不会存在写热点问题。自然split的几率也会大大降低。当然随着数据量的不断增长,该split的还是要进行split。像这样预先创建hbase表分区的方式,称之为预分区,下面给出一种预分区的实现方式:
- 首先看没有进行预分区的表,startkey和endkey为空。
- 要进行预分区,首先要明确rowkey的取值范围或构成逻辑,以我的rowkey组成为例:两位随机数+时间戳+客户号,两位随机数的范围从00-99,于是我划分了10个region来存储数据,每个region对应的rowkey范围如下:-10,10-20,20-30,30-40,40-50,50-60,60-70,70-80,80-90,90-在使用HBase API建表的时候,需要产生splitkeys二维数组,这个数组存储的rowkey的边界值。下面是java 代码实现:
需要注意的是,在上面的代码中用treeset对rowkey进行排序,必须要对rowkey排序,否则在调用admin.createTable(tableDescriptor,splitKeys)的时候会出错。创建表的代码如下:
/**
* 创建预分区hbase表
* @param tableName 表名
* @param columnFamily 列簇
* @return
*/
@SuppressWarnings("resource")
public boolean createTableBySplitKeys(String tableName, List<String> columnFamily) {
try {
if (StringUtils.isBlank(tableName) || columnFamily == null || columnFamily.size() < 0) {
log.error("Parameters tableName|columnFamily should not be null,Please check!");
}
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
return true;
} else {
HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf(tableName));
for (String cf : columnFamily) {
tableDescriptor.addFamily(new HColumnDescriptor(cf));
}
byte[][] splitKeys = getSplitKeys();
admin.createTable(tableDescriptor,splitKeys);//指定splitkeys
log.info("Create Table " + tableName+ "Success!columnFamily:" + columnFamily.toString());
}
} catch (MasterNotRunningException e) {
log.error(e);
return false;
} catch (ZooKeeperConnectionException e) {
log.error(e);
return false;
} catch (IOException e) {
log.error(e);
return false;
}
return true;
}
九、Hbase Java API
一、几个主要 Hbase API 类和数据模型之间的对应关系:
1、 HBaseAdmin
关系: org.apache.hadoop.hbase.client.HBaseAdmin
作用:提供了一个接口来管理 HBase 数据库的表信息。它提供的方法包括:创建表,删 除表,列出表项,使表有效或无效,以及添加或删除表列族成员等。
2、 HBaseConfiguration
关系: org.apache.hadoop.hbase.HBaseConfiguration
作用:对 HBase 进行配置
3、 HTableDescriptor
关系: org.apache.hadoop.hbase.HTableDescriptor
作用:包含了表的名字极其对应表的列族
4、 HColumnDescriptor
关系: org.apache.hadoop.hbase.HColumnDescriptor
作用:维护着关于列族的信息,例如版本号,压缩设置等。它通常在创建表或者为表添 加列族的时候使用。列族被创建后不能直接修改,只能通过删除然后重新创建的方式。
列族被删除的时候,列族里面的数据也会同时被删除。
5、 HTable
关系: org.apache.hadoop.hbase.client.HTable
作用:可以用来和 HBase 表直接通信。此方法对于更新操作来说是非线程安全的
6、 Put
关系: org.apache.hadoop.hbase.client.Put
作用:用来对单个行执行添加操作
7、 Get
关系: org.apache.hadoop.hbase.client.Get
作用:用来获取单个行的相关信息
8、 Result
关系: org.apache.hadoop.hbase.client.Result
作用:存储 Get 或者 Scan 操作后获取表的单行值。使用此类提供的方法可以直接获取值 或者各种 Map 结构( key-value 对)
二、具体增删改查 代码具体实现:
package HbaseDome;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
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.protobuf.generated.ZooKeeperProtos.Table;
import org.apache.hadoop.hbase.util.Bytes;
public class Hbasedome implements HBaseDemoInterface{
static Configuration conf =null;
private static final String ZKconnect="192.168.123.212:2181,192.168.123.213:2181,192.168.123.214:2181";
static{
conf=HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", ZKconnect);
}
// static String tableName="student";
// static String[] family={"lie01","lie02"};
public static void main(String[] args) {
Hbasedome a =new Hbasedome();
String tableName="student11";
String[] family={"lie01","lie02"};
try {
HTableDescriptor htds =new HTableDescriptor(tableName);
for(int z=0;z<family.length;z++){
HColumnDescriptor h=new HColumnDescriptor(family[z]);
htds.addFamily(h);
}
// a.descTable("table03");
// a.createTable(tableName, htds);
// a.descTable("table03");
// a.getAllTables();
// a.createTable(tableName,family);
// a.getResult("table03", "usr001");
// a.dropTable("user1");
// a.getAllTables();
// a.putData("table03", "usr005", "liezu01", "name", "liu");
// a.getResult("table03", "usr001");
// a.getResultScann("table03");
// a.getResultScann("table03","");
Result result = a.getResult("table03", "usr001");
System.out.println(result.toString());
List<Cell> cells = result.listCells();
for (int i = 0; i < cells.size(); i++) {
Cell cell = cells.get(i);
System.out.println(cell.toString());
// printCell(cell);
}
// List<KeyValue> list = result.list();
// for (int i = 0; i < list.size(); i++) {
// KeyValue kv = list.get(i);
// printKeyValye(kv);
// }
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void printKeyValye(KeyValue kv) {
System.out.println(Bytes.toString(kv.getRow()) + "\t" + Bytes.toString(kv.getFamily()) + "\t" + Bytes.toString(kv.getQualifier()) + "\t" + Bytes.toString(kv.getValue()) + "\t" + kv.getTimestamp());
}
public static void printCell(Cell cell) {
System.out.println(Bytes.toString(cell.getRow()) + "\t" + Bytes.toString(cell.getFamily()) + "\t" + Bytes.toString(cell.getQualifier()) + "\t" + Bytes.toString(cell.getValue()) + "\t" + cell.getTimestamp());
}
//创建表
@Override
public void createTable(String tableName, String[] family) throws Exception {
HBaseAdmin admin=new HBaseAdmin(conf);
HTableDescriptor desc =new HTableDescriptor(tableName);
for(int i=0;i<family.length;i++){
desc.addFamily(new HColumnDescriptor(family[i]));
System.out.println("11111111111"+family[i]);
}
if(admin.tableExists(tableName)){
System.out.println("表已经存在,别瞎输行吗");
// System.exit(0);
}else{
admin.createTable(desc);
System.out.println("表创建成功");
}
}
//创建表
@Override
public void createTable(String tableName, HTableDescriptor htds) throws Exception {
HBaseAdmin admin=new HBaseAdmin(conf);
boolean tableExists1 = admin.tableExists(Bytes.toBytes(tableName));
System.out.println(tableExists1 ? "表已存在" : "表不存在");
admin.createTable(htds);
boolean tableExists = admin.tableExists(Bytes.toBytes(tableName));
System.out.println(tableExists ? "创建表成功" : "创建失败");
}
@Override
public void descTable(String tableName) throws Exception {
HBaseAdmin admin=new HBaseAdmin(conf);
HTable table=new HTable(conf, tableName);
HTableDescriptor desc =table.getTableDescriptor();
HColumnDescriptor[] columnFamilies = desc.getColumnFamilies();
for(HColumnDescriptor t:columnFamilies){
System.out.println(Bytes.toString(t.getName()));
}
}
//// 这种方式是替换该表tableName的所有列簇
@Override
public void modifyTable(String tableName) throws Exception {
HBaseAdmin admin=new HBaseAdmin(conf);
HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName));
htd.addFamily(new HColumnDescriptor(Bytes.toBytes("cf3")));
htd.addFamily(new HColumnDescriptor(Bytes.toBytes("cf2")));
admin.modifyTable(tableName, htd);
// 删除该表tableName当中的特定的列簇
// admin.deleteColumn(tableName, "cf3");
System.out.println("修改成功");
}
@Override
public void getAllTables() throws Exception {
HBaseAdmin admin =new HBaseAdmin(conf);
String[] tableNames = admin.getTableNames();
for(int i=0;i<tableNames.length;i++){
System.out.println(tableNames[i]);
}
}
//更新数据 插入数据
@Override
public void putData(String tableName, String rowKey, String familyName, String columnName, String value)
throws Exception {
HTable htable=new HTable(conf, Bytes.toBytes(tableName));
Put put=new Put(Bytes.toBytes(rowKey));
put.add(Bytes.toBytes(familyName), Bytes.toBytes(columnName), Bytes.toBytes(value));
htable.put(put);
}
//为表添加数据
@Override
public void addData(String tableName, String rowKey, String[] column1, String[] value1, String[] column2,
String[] value2) throws Exception {
Put put=new Put(Bytes.toBytes(rowKey));
HTable htable=new HTable(conf, Bytes.toBytes(tableName));
HColumnDescriptor[] columnFamilies = htable.getTableDescriptor().getColumnFamilies();
for(int i=0;i<=columnFamilies.length;i++){
String nameAsString = columnFamilies[i].getNameAsString();
if(nameAsString.equals("lie01")){
for(int j=0;j<column1.length;j++){
put.add(Bytes.toBytes(nameAsString), Bytes.toBytes(column1[j]),Bytes.toBytes(value1[j]));
}
}
if(nameAsString.equals("lie02")){
for(int j=0;j<column2.length;j++){
put.add(Bytes.toBytes(nameAsString), Bytes.toBytes(column2[j]),Bytes.toBytes(value2[j]));
}
}
}
htable.put(put);
System.out.println("addData ok!");
}
//根据rowkey 查询
@Override
public Result getResult(String tableName, String rowKey) throws Exception {
Get get=new Get(Bytes.toBytes(rowKey));
HTable htable=new HTable(conf, Bytes.toBytes(tableName));
Result result=htable.get(get);
// for(KeyValue k:result.list()){
// System.out.println(Bytes.toString(k.getFamily()));
// System.out.println(Bytes.toString(k.getQualifier()));
// System.out.println(Bytes.toString(k.getValue()));
// System.out.println(k.getTimestamp());
// }
return result;
}
//查询指定的某列
@Override
public Result getResult(String tableName, String rowKey, String familyName, String columnName) throws Exception {
Get get=new Get(Bytes.toBytes(rowKey));
HTable htable=new HTable(conf, Bytes.toBytes(tableName));
get.addColumn(Bytes.toBytes(familyName),Bytes.toBytes(columnName));
Result result=htable.get(get);
for(KeyValue k:result.list()){
System.out.println(Bytes.toString(k.getFamily()));
System.out.println(Bytes.toString(k.getQualifier()));
System.out.println(Bytes.toString(k.getValue()));
System.out.println(k.getTimestamp());
}
return result;
}
//遍历查询表
@Override
public ResultScanner getResultScann(String tableName) throws Exception {
Scan scan=new Scan();
ResultScanner rs =null;
HTable htable=new HTable(conf, tableName);
try{
rs=htable.getScanner(scan);
for(Result r: rs){
for(KeyValue kv:r.list()){
System.out.println(Bytes.toString(kv.getRow()));
System.out.println(Bytes.toString(kv.getFamily()));
System.out.println(Bytes.toString(kv.getQualifier()));
System.out.println(Bytes.toString(kv.getValue()));
System.out.println(kv.getTimestamp());
}
}
}finally{
rs.close();
}
return rs;
}
@Override
public ResultScanner getResultScann(String tableName, Scan scan) throws Exception {
ResultScanner rs =null;
HTable htable=new HTable(conf, tableName);
try{
rs=htable.getScanner(scan);
for(Result r: rs){
for(KeyValue kv:r.list()){
System.out.println(Bytes.toString(kv.getRow()));
System.out.println(Bytes.toString(kv.getFamily()));
System.out.println(Bytes.toString(kv.getQualifier()));
System.out.println(Bytes.toString(kv.getValue()));
System.out.println(kv.getTimestamp());
}
}
}finally{
rs.close();
}
return rs;
}
//查询表中的某一列
@Override
public Result getResultByColumn(String tableName, String rowKey, String familyName, String columnName)
throws Exception {
HTable htable=new HTable(conf, tableName);
Get get=new Get(Bytes.toBytes(rowKey));
get.addColumn(Bytes.toBytes(familyName),Bytes.toBytes(columnName));
Result result=htable.get(get);
for(KeyValue kv: result.list()){
System.out.println(Bytes.toString(kv.getFamily()));
System.out.println(Bytes.toString(kv.getQualifier()));
System.out.println(Bytes.toString(kv.getValue()));
System.out.println(kv.getTimestamp());
}
return result;
}
//查询某列数据的某个版本
@Override
public Result getResultByVersion(String tableName, String rowKey, String familyName, String columnName,
int versions) throws Exception {
HTable htable=new HTable(conf, tableName);
Get get =new Get(Bytes.toBytes(rowKey));
get.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName));
get.setMaxVersions(versions);
Result result=htable.get(get);
for(KeyValue kv: result.list()){
System.out.println(Bytes.toString(kv.getFamily()));
System.out.println(Bytes.toString(kv.getQualifier()));
System.out.println(Bytes.toString(kv.getValue()));
System.out.println(kv.getTimestamp());
}
return result;
}
//删除指定某列
@Override
public void deleteColumn(String tableName, String rowKey, String falilyName, String columnName) throws Exception {
HTable htable=new HTable(conf, tableName);
// Delete delete1=new Delete(Bytes.toBytes(rowKey));
Delete de =new Delete(Bytes.toBytes(rowKey));
de.deleteColumn(Bytes.toBytes(falilyName), Bytes.toBytes(columnName));
htable.delete(de);
}
//删除指定的某个rowkey
@Override
public void deleteColumn(String tableName, String rowKey) throws Exception {
HTable htable=new HTable(conf, tableName);
Delete de =new Delete(Bytes.toBytes(rowKey));
htable.delete(de);
}
//让该表失效
@Override
public void disableTable(String tableName) throws Exception {
HBaseAdmin admin=new HBaseAdmin(conf);
admin.disableTable(tableName);
}
//删除表
@Override
public void dropTable(String tableName) throws Exception {
HBaseAdmin admin=new HBaseAdmin(conf);
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
}
package com.ghgj.hbase.test1610;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
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.util.Bytes;
public class HBaseAPIDemo1610 implements HBaseDemoInterface {
private static final String ROWKEY = "p001";
private static final String ROWKEY2 = "p002";
private static final String FAMILY1 = "cf1";
private static final String FAMILY2 = "cf2";
private static final String KEY = "name";
private static final String VALUE = "huangbo";
private static final String TABLE_NAME = "person";
private static final String[] COLUMN_FAMILY = new String[] { FAMILY1, FAMILY2 };
static Configuration conf = null;
static HBaseAdmin admin = null;
static HTable table = null;
static {
try {
conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "hadoop03:2181,hadoop04:2181,hadoop05:2181");
admin = new HBaseAdmin(conf);
table = new HTable(conf, TABLE_NAME);
} catch (IOException e) {
// e.printStackTrace();
System.out.println("报错");
}
}
public static void main(String[] args) throws Exception {
HBaseAPIDemo1610 hbase = new HBaseAPIDemo1610();
// 测试创建表
hbase.createTable(TABLE_NAME, COLUMN_FAMILY);
// 测试创建表
HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(TABLE_NAME));
for (int i = 0; i < COLUMN_FAMILY.length; i++) {
HColumnDescriptor cf1 = new HColumnDescriptor(COLUMN_FAMILY[i]);
htd.addFamily(cf1);
}
hbase.createTable(TABLE_NAME, htd);
// 查看表属性
hbase.descTable(TABLE_NAME);
// 查询所有的表
hbase.getAllTables();
// 测试修改表
hbase.modifyTable(TABLE_NAME);
// 插入数据
hbase.putData(TABLE_NAME, ROWKEY, FAMILY1, KEY, VALUE);
// 测试插入一堆数据
String[] column1 = new String[] { "name1", "age", "province" };
String[] value1 = new String[] { "huangbo", "33", "xinjiang" };
String[] column2 = new String[] { "gender" };
String[] value2 = new String[] { "male" };
hbase.addData(TABLE_NAME, ROWKEY2, column1, value1, column2, value2);
// 通过rowkey查询数据
Result result = hbase.getResult(TABLE_NAME, ROWKEY2);
System.out.println(result.toString());
List<KeyValue> list = result.list();
for (int i = 0; i < list.size(); i++) {
KeyValue kv = list.get(i);
printKeyValye(kv);
}
// 通过rowkey, family, province查询数据
Result result1 = hbase.getResult(TABLE_NAME, ROWKEY2, FAMILY1, "province");
List<Cell> cells = result1.listCells();
for (int i = 0; i < cells.size(); i++) {
Cell cell = cells.get(i);
printCell(cell);
}
// 扫描全表数据
ResultScanner resultScann = hbase.getResultScann(TABLE_NAME);
printResultScanner(resultScann);
/*Iterator<Result> iterator = resultScann.iterator();
while(iterator.hasNext()){
Result next = iterator.next();
}*/
// 通过scan扫描全表数据,scan中可以加入一些过滤条件
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes("user"));
scan.setStopRow(Bytes.toBytes("zk002"));
scan.setTimeRange(1488252774189l, 1488252774191l);
ResultScanner resultScann1 = hbase.getResultScann(TABLE_NAME, scan);
printResultScanner(resultScann1);
// 两种方式查询最大版本数的hbase数据
Result resultByVersion = hbase.getResultByVersion(TABLE_NAME, ROWKEY, FAMILY1, "name", 3);
printResult(resultByVersion);
System.out.println("-------------------");
ResultScanner rs = hbase.getResultByVersion(ROWKEY, FAMILY1, "name", 3);
printResultScanner(rs);
// 删除表
hbase.dropTable(TABLE_NAME);
}
public static void printResultScanner(ResultScanner resultScann) {
for (Result result : resultScann) {
printResult(result);
}
}
public static void printResult(Result result) {
List<Cell> cells = result.listCells();
for (int i = 0; i < cells.size(); i++) {
Cell cell = cells.get(i);
printCell(cell);
}
}
public static void printCell(Cell cell) {
System.out.println(Bytes.toString(cell.getRow()) + "\t" + Bytes.toString(cell.getFamily()) + "\t" + Bytes.toString(cell.getQualifier()) + "\t" + Bytes.toString(cell.getValue()) + "\t" + cell.getTimestamp());
}
public static void printKeyValye(KeyValue kv) {
System.out.println(Bytes.toString(kv.getRow()) + "\t" + Bytes.toString(kv.getFamily()) + "\t" + Bytes.toString(kv.getQualifier()) + "\t" + Bytes.toString(kv.getValue()) + "\t" + kv.getTimestamp());
}
// create 'tablename','cf1','cf2'
@Override
public void createTable(String tableName, String[] family) throws Exception {
HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName));
for (int i = 0; i < family.length; i++) {
HColumnDescriptor cf1 = new HColumnDescriptor(family[i]);
htd.addFamily(cf1);
}
admin.createTable(htd);
boolean tableExists = admin.tableExists(Bytes.toBytes(tableName));
System.out.println(tableExists ? "创建表成功" : "创建失败");
}
@Override
public void createTable(String tableName, HTableDescriptor htd) throws Exception {
admin.createTable(htd);
boolean tableExists = admin.tableExists(Bytes.toBytes(tableName));
System.out.println(tableExists ? "创建表成功" : "创建失败");
}
// desc 'person'
@Override
public void descTable(String tableName) throws Exception {
HTableDescriptor tableDescriptor = table.getTableDescriptor();
HColumnDescriptor[] columnFamilies = tableDescriptor.getColumnFamilies();
for (HColumnDescriptor hcd : columnFamilies) {
// System.out.println(hcd.toString()+"\t");
System.out.println(Bytes.toString(hcd.getName()));
}
}
@Override
public void modifyTable(String tableName) throws Exception {
// 这种方式是替换该表tableName的所有列簇
HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName));
htd.addFamily(new HColumnDescriptor(Bytes.toBytes("cf3")));
htd.addFamily(new HColumnDescriptor(Bytes.toBytes("cf2")));
admin.modifyTable(tableName, htd);
// 删除该表tableName当中的特定的列簇
// admin.deleteColumn(tableName, "cf3");
System.out.println("修改成功");
}
// list
@Override
public void getAllTables() throws Exception {
TableName[] listTableNames = admin.listTableNames();
for (TableName tn : listTableNames) {
System.out.println(tn.toString());
}
}
// put 'tablename','rowkey','familyname:key','value'
@Override
public void putData(String tableName, String rowKey, String familyName, String columnName, String value) throws Exception {
// HTable table = new HTable(conf, tableName);
Put put = new Put(Bytes.toBytes(rowKey));
put.add(Bytes.toBytes(familyName), Bytes.toBytes(columnName), Bytes.toBytes(value));
table.put(put);
System.out.println("插入成功");
}
/**
* @param tableName
* 表名
* @param rowKey
* rowkey
* @param column1
* 第一个列簇的key数组
* @param value1
* 第一个列簇的value数组,key数组和value数组长度必须一样
* @param column2
* 第二列簇的key数组
* @param value2
* 第二个列簇的values数组, 同上同理
* @throws Exception
*/
@Override
public void addData(String tableName, String rowKey, String[] column1, String[] value1, String[] column2, String[] value2) throws Exception {
List<Put> puts = new ArrayList<Put>();
for (int i = 0; i < column1.length; i++) {
Put put = new Put(Bytes.toBytes(rowKey));
put.add(Bytes.toBytes(FAMILY1), Bytes.toBytes(column1[i]), Bytes.toBytes(value1[i]));
puts.add(put);
}
for (int i = 0; i < column2.length; i++) {
Put put = new Put(Bytes.toBytes(rowKey));
put.add(Bytes.toBytes(FAMILY2), Bytes.toBytes(column2[i]), Bytes.toBytes(value2[i]));
puts.add(put);
}
table.put(puts);
System.out.println("插入一堆数据成功");
}
// get 'tablename','rowkey'
@Override
public Result getResult(String tableName, String rowKey) throws Exception {
Get get = new Get(Bytes.toBytes(rowKey));
Result result = table.get(get);
return result;
}
@Override
public Result getResult(String tableName, String rowKey, String familyName, String columnName) throws Exception {
Get get = new Get(Bytes.toBytes(rowKey));
get.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName));
Result result = table.get(get);
return result;
}
@Override
public ResultScanner getResultScann(String tableName) throws Exception {
Scan scan = new Scan();
ResultScanner scanner = table.getScanner(scan);
// ResultScanner scanner = table.getScanner(Bytes.toBytes(FAMILY2));
// ResultScanner scanner = table.getScanner(Bytes.toBytes(FAMILY1),
// Bytes.toBytes("name1"));
return scanner;
}
@Override
public ResultScanner getResultScann(String tableName, Scan scan) throws Exception {
return table.getScanner(scan);
}
@Override
public Result getResultByColumn(String tableName, String rowKey, String familyName, String columnName) throws Exception {
return null;
}
// get 'person','p001',{COLUMNS => 'cf1:name', VERSIONS => 3}
@Override
public Result getResultByVersion(String tableName, String rowKey, String familyName, String columnName, int versions) throws Exception {
Get get = new Get(Bytes.toBytes(rowKey));
get.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName));
get.setMaxVersions(versions);
Result result = table.get(get);
return result;
}
public ResultScanner getResultByVersion(String rowKey, String familyName, String columnName, int versions) throws Exception {
Scan scan = new Scan(Bytes.toBytes(rowKey), Bytes.toBytes(rowKey));
scan.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName));
scan.setMaxVersions(versions);
ResultScanner scanner = table.getScanner(scan);
return scanner;
}
@Override
public void deleteColumn(String tableName, String rowKey, String falilyName, String columnName) throws Exception {
}
@Override
public void deleteColumn(String tableName, String rowKey) throws Exception {
}
@Override
public void disableTable(String tableName) throws Exception {
admin.disableTable(tableName);
}
@Override
public void dropTable(String tableName) throws Exception {
try {
admin.deleteTable(tableName);
} catch (Exception e) {
// e.printStackTrace();
disableTable(tableName);
admin.deleteTable(tableName);
System.out.println("ssssssss");
} finally {
boolean tableExists = admin.tableExists(Bytes.toBytes(tableName));
System.out.println(tableExists ? "删除失败" : "删除成功");
}
}
}
分区优化
- 集群如果内存较大就不使用交换分区,如果内存不大可以使用部分交换分区
- 如果需要关闭内存空间将改值设置为0
- Sysctl -w vm.swappiness=0
- 如果内存空间不太充足的话,可以将该参数调小
- Sysctl -w vm.swappiness=5
- 也可以在Linux系统中手动释放交换空间命令:
- Linux swapoff命令用于关闭系统交换区(swap area), -a代表all
- swapoff -a 全部关闭交换空间
- swapon -a 全部开启交换空间
- 完成交换空间释放
十、参数优化
服务端
- hbase.regionserver.handler.count:rpc请求的线程数量,默认值是10,生产环境建议使用100,也不是越大越好,特别是当请求内容很大的时候,比如scan/put几M的数据,会占用过多的内存,有可能导致频繁的GC,甚至出现内存溢出。
- hbase.master.distributed.log.splitting:默认值为true,建议设为false。关闭hbase的分布式日志切割,在log需要replay时,由master来负责重放
- hbase.regionserver.hlog.splitlog.writer.threads:默认值是3,建议设为10,日志切割所用的线程数
- hbase.snapshot.enabled:快照功能,默认是false(不开启),建议设为true,特别是对某些关键的表,定时用快照做备份是一个不错的选择。
- hbase.hregion.max.filesize:默认是10G, 如果任何一个column familiy里的StoreFile超过这个值, 那么这个Region会一分为二,因为region分裂会有短暂的region下线时间(通常在5s以内),为减少对业务端的影响,建议手动定时分裂,可以设置为60G。
- hbase.hregion.majorcompaction:hbase的region主合并的间隔时间,默认为1天,建议设置为0,禁止自动的major主合并,major合并会把一个store下所有的storefile重写为一个storefile文件,在合并过程中还会把有删除标识的数据删除,在生产集群中,主合并能持续数小时之久,为减少对业务的影响,建议在业务低峰期进行手动或者通过脚本或者api定期进行major合并。
- hbase.hregion.memstore.flush.size:默认值128M,单位字节,一旦有memstore超过该值将被flush,如果regionserver的jvm内存比较充足(16G以上),可以调整为256M。
- hbase.hregion.memstore.block.multiplier:默认值2,如果一个memstore的内存大小已经超过hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier,则会阻塞该memstore的写操作,为避免阻塞,建议设置为5,如果太大,则会有OOM的风险。如果在regionserver日志中出现"Blocking updates for '<threadName>' on region <regionName> : memstore size <多少M> is >= than blocking <多少M> size"的信息时,说明这个值该调整了。
- hbase.hstore.compaction.min:默认值为3,如果任何一个store里的storefile总数超过该值,会触发默认的合并操作,可以设置5~8,在手动的定期major compact中进行storefile文件的合并,减少合并的次数,不过这会延长合并的时间,以前的对应参数为hbase.hstore.compactionThreshold。
- hbase.hstore.compaction.max:默认值为10,一次最多合并多少个storefile,避免OOM。
- hbase.hstore.blockingStoreFiles:默认为7,如果任何一个store(非.META.表里的store)的storefile的文件数大于该值,则在flush memstore前先进行split或者compact,同时把该region添加到flushQueue,延时刷新,这期间会阻塞写操作直到compact完成或者超过hbase.hstore.blockingWaitTime(默认90s)配置的时间,可以设置为30,避免memstore不及时flush。当regionserver运行日志中出现大量的“Region <regionName> has too many store files; delaying flush up to 90000ms"时,说明这个值需要调整了
- hbase.regionserver.global.memstore.upperLimit:默认值0.4,regionserver所有memstore占用内存在总内存中的upper比例,当达到该值,则会从整个regionserver中找出最需要flush的region进行flush,直到总内存比例降到该数以下,采用默认值即可。
- hbase.regionserver.global.memstore.lowerLimit:默认值0.35,采用默认值即可。
- hbase.regionserver.thread.compaction.small:默认值为1,regionserver做Minor Compaction时线程池里线程数目,可以设置为5。
- hbase.regionserver.thread.compaction.large:默认值为1,regionserver做Major Compaction时线程池里线程数目,可以设置为8。
- hbase.regionserver.lease.period:默认值60000(60s),客户端连接regionserver的租约超时时间,客户端必须在这个时间内汇报,否则则认为客户端已死掉。这个最好根据实际业务情况进行调整
- hfile.block.cache.size:默认值0.25,regionserver的block cache的内存大小限制,在偏向读的业务中,可以适当调大该值,需要注意的是hbase.regionserver.global.memstore.upperLimit的值和hfile.block.cache.size的值之和必须小于0.8。
- dfs.socket.timeout:默认值60000(60s),建议根据实际regionserver的日志监控发现了异常进行合理的设置,比如我们设为900000,这个参数的修改需要同时更改hdfs-site.xml
- dfs.datanode.socket.write.timeout:默认480000(480s),有时regionserver做合并时,可能会出现datanode写超时的情况,480000 millis timeout while waiting for channel to be ready for write,这个参数的修改需要同时更改hdfs-site.xml
- jvm和垃圾收集参数:
- export HBASE_REGIONSERVER_OPTS="-Xms36g -Xmx36g -Xmn1g -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=15 -XX:CMSInitiatingOccupancyFraction=70 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/data/logs/gc-$(hostname)-hbase.log"由于我们服务器内存较大(96G),我们给一部分regionserver的jvm内存开到64G,到现在为止,还没有发生过一次full gc,hbase在内存使用控制方面确实下了不少功夫,比如各种blockcache的实现,细心的同学可以看源码。、
Client端
- hbase.client.write.buffer:默认为2M,写缓存大小,推荐设置为5M,单位是字节,当然越大占用的内存越多,此外测试过设为10M下的入库性能,反而没有5M好。
- hbase.client.pause:默认是1000(1s),如果你希望低延时的读或者写,建议设为200,这个值通常用于失败重试,region寻找等。
- hbase.client.retries.number:默认值是10,客户端最多重试次数,可以设为11,结合上面的参数,共重试时间71s。
- hbase.ipc.client.tcpnodelay:默认是false,建议设为true,关闭消息缓冲。
- hbase.client.scanner.caching:scan缓存,默认为1,避免占用过多的client和rs的内存,一般1000以内合理,如果一条数据太大,则应该设置一个较小的值,通常是设置业务需求的一次查询的数据条数。
- 如果是扫描数据对下次查询没有帮助,则可以设置scan的setCacheBlocks为false,避免使用缓存。
- table用完需关闭,关闭scanner。
- 限定扫描范围:指定列簇或者指定要查询的列,指定startRow和endRow。
- 使用Filter可大量减少网络消耗。
- 通过Java多线程入库和查询,并控制超时时间。后面会共享下我的hbase单机多线程入库的代码。
- 建表注意事项:开启压缩合理的设计rowkey进行预分区开启bloomfilter 。
ZooKeeper调优
- 1.zookeeper.session.timeout:默认值3分钟,不可配置太短,避免session超时,hbase停止服务,线上生产环境由于配置为1分钟,如果太长,当regionserver挂掉,zk还得等待这个超时时间(已有patch修复),从而导致master不能及时对region进行迁移。
- 2.zookeeper数量:建议5个或者7个节点。给每个zookeeper 4G左右的内存,最好有独立的磁盘。3.hbase.zookeeper.property.maxClientCnxns:zk的最大连接数,默认为300,无需调整。
- 4.设置操作系统的swappiness为0,则在物理内存不够的情况下才会使用交换分区,避免GC回收时会花费更多的时间,当超过zk的session超时时间则会出现regionserver宕机的误报。
HDFS调优
- 1.dfs.name.dir:namenode的数据存放地址,可以配置多个,位于不同的磁盘并配置一个nfs远程文件系统,这样namenode的数据可以有多个备份
- 2.dfs.namenode.handler.count:namenode节点RPC的处理线程数,默认为10,可以设置为60
- 3.dfs.datanode.handler.count:datanode节点RPC的处理线程数,默认为3,可以设置为30
- 4.dfs.datanode.max.xcievers:datanode同时处理文件的上限,默认为256,可以设置为8192
其他
- 列族名、column名、rowkey均会存储到hfile中,因此这几项在设计表结构时都尽量短些。
- regionserver的region数量不要过1000,过多的region会导致产生很多memstore,可能会导致内存溢出,也会增加major compact的耗时。
技术源于积累,成就贵在坚持。