【Hbase】-总结
【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主键设置
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的耗时。
技术源于积累,成就贵在坚持。