【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列簇:
  • 列簇的概念:

hbase集群master hbase集群中master的作用_数据

列是根据列簇进行分组的。

  • 列簇的特点:
  • 一张表有自己独立的列簇而列簇在一张表中最多不能多于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集群master hbase集群中master的作用_元数据_02


  • 上图模拟了一个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。读取的流程如下图所示:
  • hbase集群master hbase集群中master的作用_数据_03


  • 第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结构:
  • hbase集群master hbase集群中master的作用_数据_04


  • 从上图我们可以看出多个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 类和数据模型之间的对应关系:

hbase集群master hbase集群中master的作用_hbase集群master_05

1、 HBaseAdmin

关系: org.apache.hadoop.hbase.client.HBaseAdmin

作用:提供了一个接口来管理 HBase 数据库的表信息。它提供的方法包括:创建表,删 除表,列出表项,使表有效或无效,以及添加或删除表列族成员等。

hbase集群master hbase集群中master的作用_hbase集群master_06

hbase集群master hbase集群中master的作用_元数据_07

hbase集群master hbase集群中master的作用_数据_08

2、 HBaseConfiguration

关系: org.apache.hadoop.hbase.HBaseConfiguration

作用:对 HBase 进行配置

hbase集群master hbase集群中master的作用_数据_09

3、 HTableDescriptor

关系: org.apache.hadoop.hbase.HTableDescriptor

作用:包含了表的名字极其对应表的列族

hbase集群master hbase集群中master的作用_时间戳_10

4、 HColumnDescriptor

关系: org.apache.hadoop.hbase.HColumnDescriptor

作用:维护着关于列族的信息,例如版本号,压缩设置等。它通常在创建表或者为表添 加列族的时候使用。列族被创建后不能直接修改,只能通过删除然后重新创建的方式。

列族被删除的时候,列族里面的数据也会同时被删除。

hbase集群master hbase集群中master的作用_时间戳_11

5、 HTable

关系: org.apache.hadoop.hbase.client.HTable

作用:可以用来和 HBase 表直接通信。此方法对于更新操作来说是非线程安全的

hbase集群master hbase集群中master的作用_数据_12

hbase集群master hbase集群中master的作用_元数据_13

6、 Put

关系: org.apache.hadoop.hbase.client.Put

作用:用来对单个行执行添加操作

hbase集群master hbase集群中master的作用_数据_14

7、 Get

关系: org.apache.hadoop.hbase.client.Get

作用:用来获取单个行的相关信息

hbase集群master hbase集群中master的作用_hbase集群master_15

8、 Result

关系: org.apache.hadoop.hbase.client.Result

作用:存储 Get 或者 Scan 操作后获取表的单行值。使用此类提供的方法可以直接获取值 或者各种 Map 结构( key-value 对)

hbase集群master hbase集群中master的作用_数据_16

二、具体增删改查    代码具体实现:

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的耗时。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

技术源于积累,成就贵在坚持。