大数据--Hbase

Hbase 基础

hbase 数据库介绍

  1. 简介

          hbase 是基于 Google BigTable 模型开发的,典型的 key/value 系统。是建立在 hdfs之上,提供高可靠性、高性能、可伸缩、实时读写 nosql 的数据库系统。它是 Apache Hadoop 生态系统中的重要一员,主要用于海量结构化和半结构化数据存储。

          它介于 nosql 和 RDBMS 之间,仅能通过主键row key。和主键range来检索数据,仅支持单行事务(可通过 hive 支持来实现多表 join 等复杂操作)

          Hbase 查询数据功能很简单,不支持 join 等复杂操作,不支持复杂的事物(行级的事物)与hadoop 一样,Hbase 目标横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。

          HBase中的表一般有这样的特点:

    1. :一个表中可以有上十亿行,上百万列
    2. 无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列
    3. 面向列:面向列(族)的存储和权限控制列(族)独立检索
    4. 稀释对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀释。
    5. 数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳。
    6. 数据类型单一:Hbase 中的数据都是字节数组 byte[]。
  2. 表结构逻辑视图

          Hbase 以表的形式存储数据。表有行和列组成。列划分为若干个列族(colum family)

    在这里插入图片描述
    A : 一个 value 可以有多个版本,通过版本号(时间戳来区分)。
    B: Hbase 中通过 rowkey 和 column 确定的为一个存贮单元称为 cell
    C: Hbase 在建立表的时候,不需要指定表中的字段,只需要指定若干个列族
    D: 插入数据的时候,列族中可以存储任意多列
    D: 查询一个具体字段的值的时候,需要指定的坐标是表明–> 行建 --> 列族(columnFamily):列名 --> 版本

  3. Row Key

          与 nosql 数据库们一样,row key 是用来检索记录的主键。访问 hbase table 中的行,只有三种方式:

    1. 通过单个 row key 访问
    2. 通过 row key 的 range
    3. 全表扫描

          Row key 行键可以是任意字符串最大长度是 64KB,实际应用中长度一般为 10-100bytes),在 hbase 内部,row key 保存为字节数组。

          Hbase 会对表中的数据按照 rowkey 排序(字典顺序)

          存储时,数据按照 Row Key 的字典序(byte order)排序存储。设计 key 时,要充分排序存储这个特性,将经常一起读取的行存储放到一起(位置相关性)

          注意:

          字典对 int 排序的结果是:

          1,10,100,11,12,13,14,15,16,17,18,19,2,29,21,···,9,91,92,93,94,95,96,97,98,99。要保持整型的自然序,行键必须用 0 作左补充

          行的一次读写是原子操作(不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

  4. 列族

          hbase 表中的每个列,都归属于某个列族。列族是表的 schema 的一部分(而列不是),必须在使用表之前定义。

          列名都以列族作为前缀。例如 date:year 和 date:month ,year 和 month 都属于 date 这个列族。

          访问控制、磁盘和内存的使用统计都是在列族层面进行的。

          列族越多,在取一行数据时所要参与 IO 、搜寻的文件就越多,所以,如果没有必要,不要设置太多的列族。一般设置 2-3 个比较合理。

  5. 时间戳

          Hbase 中通过 row 和 columns 确定的为一个存贮单元称为 cell。每个 cell 都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型时 64 位 整型。时间戳可以由hbase(在数据写入时自动)赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显示赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 cell 中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。

          为了避免数据存在过多版本造成的管理(包括存贮和索引)负担,hbase 提供了两种数据版本回收方式:

    1. 保存数据的最后 n 个版本
    2. 保存最近一段时间内的版本(设置数据的生命周期 TTL )

      用户可以针对每个列族进行设置。

  1. Cell
         由 {row key, column( =<family> + <label>), version} 唯一确定的单元。
    cell 中的数据是没有类型的,全部是字节码形式存贮。

hbase 集群结构

系统架构

架构图:
在这里插入图片描述

       Hbase 基本组件说明

  1. Client:

           包含访问 Hbase 的接口,并维护 cache 来加快 Hbase 的访问,比如 region 的位置信息。

  2. HMaster:

           是 hbase 集群的主节点,可以配置多个,用来实现 HA。

           为 RegionServer 分配 region。

           负责 RegionServer 的负载均衡。

           发现失效的 RegionServer 并重新分配其上的 region。

           处理schema更新请求。

           HDFS上的垃圾文件回收

  3. RegionServer:

           Regionserver 维护 region ,处理这些 region 的 IO 请求。

           Regionserver 负责切分在运行过程中变得过大的 region。

  4. Region:

           分布式存储的最小单元。

  5. Zookeeper 作用:

           通过选举,保证任何时候,集群中只有一个活着的 HMaster,Hmaster 与 RegionServers 启动时会向 Zookeeper 注册。

           存贮所有 Region 的地址。

           实时监控 RegionServer 的上线和下线信息。并实时通知给 Hmaster。

           存储 HBase 的 schema 和 table 的元数据。

           Zookeeper 的引入是的 Hmaster 不再是单点故障。

       可以看到,client访问hbase上数据的过程并不需要master参与(寻址访问zookeeper和region server,数据读写访问regione server),master仅仅维护者table和region的元数据信息,负载很低。

物理存储

在这里插入图片描述

  • 整体结构

    1. Table 中所有的行都按照 row key 的字典序排列。

    2. Table 在的行的方向上分隔为多个 Hregion

    3. region 按大小分割(默认 10G),每个表一开始只有一个 region,随着数据不断插入表,region 不断增大,当增大到一个阀值的时候,Hregion 就会等分为两个新的 Hregion。当 Table 中的行不断增多,就会有越来越多的 Hregion。

    4. Hregion 是 Hbase 中分布式存储和负载均衡的最小单元。最小单元表示不同的 Hregion 可以分布在不同的 Hregion server 上。 但一个 Hregion 是不会拆分到多个 regionserver 上的

    5. HRrgion 虽然是负载均衡的最小单位,但并不是物理存储的最小单元。事实上,Hregion 由一个或者多个 Store组成,每个 Store保存一个 column family。每个 Store 又由一个 memStore 和 至多个 StoreFile 组成。如上图

      在这里插入图片描述

  • Store File & Hfile结构(更加底层 了解即可)
    Store 以 Hfile 格式保存在 HDFS上。
    附:Hfile 的格式为:
    在这里插入图片描述

          首先 Hfile 文件是不定长的,长度固定的只有其中的两块:Trailer 和 FileInfo。正如图中所示的,Trailer 中有指针指向其他数据块的起始点。File Info 中记录了文件的一些 Meta 信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等。

          Data Index和Meta Index块记录了每个Data块和Meta块的起始点。

          Data Block是HBase I/O的基本单元,为了提高效率,HRegionServer中有基于LRU的Block Cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定,大号的Block有利于顺序Scan,小号Block利于随机查询。 每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成, Magic内容就是一些随机数字,目的是防止数据损坏。

          HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:


    在这里插入图片描述

          开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,表示RowKey的长度,紧接着是 RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。

    Hfile分为六个部分

    1. Data Block 段–保存表中的数据,这部分可以被压缩。

    2. Meta Block 段 (可选的)–保存用户自定义的kv对,可以被压缩。

    3. File Info 段–Hfile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。

    4. Data Block Index 段–Data Block的索引。每条索引的key是被索引的block的第一条记录的key。

    5. Meta Block Index段 (可选的)–Meta Block的索引。

    6. Trailer–这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先 读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个 block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。

            HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。目标Hfile的压缩支持两种方式:Gzip,Lzo。

  • Memstore与storefile

          一个 region 由多个 store 组成,每个 store 包含一个列族的所有数据。Store 包括位于内存的 memstore 和位于硬盘的 storefile。

          写操作先写入 memstore ,当memstore 中的数据量达到某个阈值, Hregionserver 启动 flashcache 进程写入 storefile , 每次写入形成单独一个 storefile 。

          当 storefile 的个数超过一定阈值后(默认参数hbase.hstore.blockingStoreFiles=10),多个 storeFile 会进行合并,当该 region 的一个 store 的 storefile 大小之和,即一个 store 的大小超过 hbase.hregion.max.filesize=10G 时,这个 region会被拆分会把当前的 region 分割成两个,并由 Hmaster 分配给相应的 region 服务器,实现负载均衡。

          客户端检索数据时,先在memstore找,找不到再找storefile。

  • HLog(WAL log)(了解)

          WAL 意为 Write ahead log (http://en.wikipedia.org/wiki/Write-ahead_logging), 该机制用于数据的容错和恢复,Hlog 记录数据的所有变更,一旦数据修改,就可以从 log中 进行恢复。

          每个 HRegionServer 中都有一个 HLog 对象,HLog 是一个实现 Write Ahead Log 的类,在每次用户操作写入 MemStore 的同时,也会写一份数据到 HLog 文件中 ,HLog 文件定期会滚动出新的,并删除旧的文件(已持久化到 StoreFile 中的数据)。当 HRegionServer 意外终止后,HMaster 会通过 Zookeeper 感知到,HMaster 首先会处理遗留的 HLog 文件,将其中不同 Region 的 Log 数据进行拆分,分别放到相应 region 的目录下,然后再将失效的 region 重新分配,领取到这些 region 的 HRegionServer 在 Load Region 的过程中,会发现有历史 HLog 需要处理,因此会 Replay HLog 中的数据到MemStore 中,然后 flush 到 StoreFiles ,完成数据恢复。

           HLog 文件就是一个普通的 Hadoop Sequence File

    1. HLog Sequence File 的 Key 是 HLogKey 对象,HLogKey 中记录了写入数据的归属信息,除了 table 和 region 名字外,同时还包括 sequence number 和 timestamp,timestamp是”写入时间”,sequence number 的起始值为0,或者是最近一次存入文件系统中 sequence number 。
    2. HLog Sequece File 的 Value 是 HBase 的 KeyValue 对象,即对应 HFile 中的 KeyValue。

寻址机制

  1. 寻址示意图(怎么查询 / 写入数据的)

    在这里插入图片描述

  2. -ROOT-和.META.表结构
    -Root-表结构
    在这里插入图片描述
    .META.行记录结构
    在这里插入图片描述

  3. 寻址流程

          现在假设我们要从 Table2 里面查询一条 RowKey 是 RK100000 的数据。那么我们应该遵循以下步骤:

    • 从 .META. 表里面查询哪个 Region 包含这条数据。
    • 获取管理这个 Region 的 RegionServer 地址 。
    • 连接这个 RegionServer , 查到这条数据。

          系统如何找到某个 row key (或者某个 row key range )所在的 regio

          bigtable 使用三层类似 B+ 树的结构来保存 region 位置

          第一层: 保存 zookeeper 里面的文件,它持有 root region 的位置。

          第二层: root region 是 .META. 表的第一个 region ,其中保存了 .META. 表其他 region 的位置。通过 root region ,我们就可以访问 .META. 表的数据。

          第三层: .META. 表的它是一个特殊的表,保存了 hbase 中所有数据表的 region 位置信息。


          说明:

    1. root region 永远不会被 split,保证了最需要三次跳转,就能定位到任意 region 。
    2. .META. 表每行保存一个 region 的位置信息,row key 采用表名 + 表的最后一行编码而成。
    3. 为了加快访问,.META. 表的全部 region 都保存在内存中。
    4. client会将查询过的位置信息保存缓存起来,缓存不会主动失效,因此如果client上的缓存全部失效,则需要进行最多6次网络来回,才能定位到正确的region(其中三次用来发现缓存失效,另外三次用来获取位置信息)。
  4. 总结

          Region定位流程:

    在这里插入图片描述
    a). 寻找 RegionServer

          Zookeeper --> -ROOT-(单 Region) --> .META. -->用户表


    b) -ROOT-表

          表包含 .META. 表所在的region 列表,该表只会有一个 Region ;
           Zookeeper 中记录了 -ROOT- 表的location。

    c) .META.表

          表包含所有的用户空间 region列表,以及 RegionServer 的服务器地址

读写过程

  • 读请求过程:

    1. 客户端通过 zookeeper 以及 root 表和 meta 表找到目标数据所在的regionserver 。
    2. 联系 regionserver 查询目标数据。
    3. regionserver 定位到目标数据所在的region,发出查询请求。
    4. region 先在 memstore 中查找,命中则返回。
    5. 如果在 memstore 中找不到,则在 storefile 中扫描(可能会扫描到很多的 storefile----bloomfilter 布隆过滤器)补充:布隆过滤器参数类型有2种:
      Row、row+col
  • 写请求过程:

    1. client 向 region server 提交写请求。
    2. region server找到目标 region 。
    3. region检查数据是否与 schema 一致。
    4. 如果客户端没有指定版本,则获取当前系统时间作为数据版本。
    5. 将更新写入 WAL log 。
    6. 将更新写入 Memstore 。
    7. 判断 Memstore 的是否需要 flush 为 StoreFile 文件。
  • 细节描述:

          hbase 使用 MemStore 和 StoreFile 存储对表的更新。

          数据在更新时首先写入 Log( WAL log ) 和内存 ( MemStore ) 中,MemStore 中的数据是排序的,当 MemStore 累计到一定阈值时,就会创建一个新的 MemStore ,并且将老的 MemStore 添加到 flush 队列,由单独的线程 flush 到磁盘上,成为一个 StoreFile 。于此同时,系统会在 zookeeper 中记录一个 redo point ,表示这个时刻之前的变更已经持久化了。

          当系统出现意外时,可能导致内存 ( MemStore ) 中的数据丢失,此时使用 Log( WAL log ) 来恢复checkpoint 之后的数据。

Region 管理

  • master 上线
        master启动进行以下步骤:

    1. 从 zookeeper 上获取唯一一个代表 active master 的锁,用来阻止其它 master 成为活着的 master 。
    2. 扫描 zookeeper 上的 server 父节点,获得当前可用的 region server 列表。
    3. 和每个 region server 通信,获得当前已分配的 region 和 region server 的对应关系。
    4. 扫描 .META. region 的集合,计算得到当前还未分配的 region ,将他们放入待分配 region 列表。
  • master 下线

          由于 master 只维护表和 region 的元数据,而不参与表数据 IO 的过程,master 下线仅导致所有元数据的修改被冻结(无法创建删除表,无法修改表的 schema ,无法进行 region 的负载均衡,无法处理 region 上下线,无法进行 region 的合并,唯一例外的是 region 的 split 可以正常进行,因为只有 region server 参与),表的数据读写还可以正常进行。因此 master 下线短时间内对整个 hbase 集群没有影响。

Hbase 容错性

  • Master 容错:
    1. Zookeeper 重新选择一个新的 Master 。
    2. 无 Master 过程中,数据读取仍照常进行。
    3. 无 Master 过程中,region 切分、负载均衡等无法进行。
  • RegionServer 容错:

          定时向 Zookeeper 汇报心跳,如果一旦时间内未出现心跳,Master 将该 RegionServer 上的 Region 重新分配到其他 RegionServer 上,失效服务器上“预写”日志由主服务器进行分割并派送给新的 RegionServer 。

  • Zookeeper 容错:

           Zookeeper 是一个可靠地服务,一般配置 3 或 5 个 Zookeeper实例 。

Hbase 集群搭建(外部 zookeeper)

       点击跳转hbase官网:http://hbase.apache.org/

在这里插入图片描述

  1. 搭建一个 hadoop 集群
    点击跳转hadoop安装教程

  2. 部署一个 zookeeper 集群
    点击跳转zookeeper安装教程

  3. 上传 hbase 安装包
    在这里插入图片描述

  4. 解压
    在这里插入图片描述

  5. 配置 hbase 集群 , 先修改 3 个文件
    要把 hadoop 的 hdfs-site.xml 和 core-site.xml 放到 hbase/conf 下。

    cp ./hadoop/core-site.xml /opt/software/hbase1.2.1/conf/
    cp ./hadoop/hdfs-site.xml /opt/software/hbase1.2.1/conf/
    
    1. 修改 hbase-env.sh

      //java的环境变量
      export JAVA_HOME=/var/local/jdk1.7.0_67
      //告诉hbase使用外部的zk
      export HBASE_MANAGES_ZK=false
      
    2. 修改 hbase-site.xml

      	<configuration>
      		<!-- 指定hbase在HDFS上存储的路径 -->
              <property>
                      <name>hbase.rootdir</name>
                      <value>hdfs://h2:9000/hbase</value>
              </property>
      		<!-- 指定hbase是分布式的 -->
              <property>
                      <name>hbase.cluster.distributed</name>
                      <value>true</value>
              </property>
      		<!-- 指定zk的地址,多个用“,”分割 -->
              <property>
                      <name>hbase.zookeeper.quorum</name>
                      <value>h2:2181,h3:2181,h5:2181</value>
              </property>
      </configuration>
      
    3. 修改 regionservers(此处最好更改为集群中所有的节点)

      vm02
      vm03
      
    4. 修改 backup-master 来指定备用的主节点(可以理解为就是主节点的所在)

      vm02
      
  6. 拷贝 hbase 到其他节点

    scp -r /opt/software/hbase1.2.1/  vm1:/opt/software/
    scp -r /opt/software/hbase1.2.1/  vm0:/opt/software/		
    
  7. 同步每一个节点的时间

    ntpdate -u cn.pool.ntp.org
    //先安装 yum install -y ntp
    
  8. 启动 hbase

    1. 首先启动 zk 集群
      zkServer.sh start
    2. 启动 hdfs 集群
      start-dfs.sh
    3. 启动 hbase ,在主节点 vm01 上运行
      start-hbase.sh
    4. 通过浏览器访问 hbase 管理页面
      vm01:16010
    5. 为了保证集群的可靠性,要启动多个 HMaster
      start-daemon.sh start master
  9. 注意: 使用jdk8的时候,出现了Java HotSpot™ 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0的红色标识。字面意思是MaxPermSize不需要我们配置了,所以我就按照它的方法把default VM arguments中MaxPermSize参数给删掉就不会出现上面的提示了。

命令行演示

基本 shell 命令

      进入命令行

      ./hbase shell

      显示 hbase 中的表

      list

      创建user表,包含info、data两个列族

      create 'user','info','data' 或者 create 'user', {NAME => 'info', VERSIONS => '3'},{NAME => 'data'}

      查看表的整体信息

      Describe 'tableName'

在这里插入图片描述

      向user表中插入信息,row key为rk0001,列族info中添加name列标示符,值为zhangsan

      put 'user', 'rk0001', 'info:name', 'zhangsan'

      向user表中插入信息,row key为rk0001,列族info中添加gender列标示符,值为female

      put 'user', 'rk0001', 'info:gender', 'female'

      向user表中插入信息,row key为rk0001,列族info中添加age列标示符,值为20

      put 'user', 'rk0001', 'info:age', 20

      向user表中插入信息,row key为rk0001,列族data中添加pic列标示符,值为picture

      put 'user', 'rk0001', 'data:pic', 'picture'

      获取user表中row key为rk0001的所有信息

      get 'user', 'rk0001'

      获取user表中row key为rk0001,info列族的所有信息

      get 'user', 'rk0001', 'info'

      获取user表中row key为rk0001,info列族的name、age列标示符的信息

      get 'user', 'rk0001', 'info:name', 'info:age'

      获取user表中row key为rk0001,info、data列族的信息

      get 'user', 'rk0001', 'info', 'data'

      get 'user', 'rk0001', {COLUMN => ['info', 'data']}

      get 'user', 'rk0001', {COLUMN => ['info:name', 'data:pic']}

      获取user表中row key为rk0001,列族为info,版本号最新5个的信息

      get 'user', 'rk0001', {COLUMN => 'info', VERSIONS => 2}

      get 'user', 'rk0001', {COLUMN => 'info:name', VERSIONS => 5}

      get 'user', 'rk0001', {COLUMN => 'info:name', VERSIONS => 5, TIMERANGE => [1392368783980, 1392380169184]}

       注意时间段需要修改
      VERSIONS => 5:指定当前查询最近的几个版本
      TIMERANGE :可以时间错误进行过滤

      获取user表中row key为rk0001,cell的值为zhangsan的信息

      get 'people', 'rk0001', {FILTER => "ValueFilter(=, 'binary:zhangsan')"}

      获取user表中row key为rk0001,列标示符中含有a的信息

      get 'people', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}

      put 'user', 'rk0002', 'info:name', 'fanbingbing'
      put 'user', 'rk0002', 'info:gender', 'female'
      put 'user', 'rk0002', 'info:nationality', '中国'
      get 'user', 'rk0002', {FILTER => "ValueFilter(=, 'binary:中国')"}

      查询user表中的所有信息

      scan 'user'

      查询user表中列族为info的信息

      scan 'user', {COLUMNS => 'info'}

      scan 'user', {COLUMNS => 'info', RAW => true, VERSIONS => 5}
      scan 'persion', {COLUMNS => 'info', RAW => true, VERSIONS => 3}

      查询user表中的所有信息

      scan 'user', {COLUMNS => ['info', 'data']}

      scan 'user', {COLUMNS => ['info:name', 'data:pic']}

      查询user表中列族为info、列标示符为name的信息

      scan 'user', {COLUMNS => 'info:name'}

      查询user表中列族为info、列标示符为name的信息,并且版本最新的5个

      scan 'user', {COLUMNS => 'info:name', VERSIONS => 5}

      查询user表中列族为info和data且列标示符中含有a字符的信息

      scan 'user', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}

      查询user表中列族为info,rk范围是[rk0001, rk0003)的数据

      scan 'people', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}

      查询user表中row key以rk字符开头的

      scan 'user',{FILTER=>"PrefixFilter('rk')"}

      查询user表中指定范围的数据

      scan 'user', {TIMERANGE => [1392368783980, 1392380169184]}

      查询user表中指定范围的数据

      scan 'user', {TIMERANGE => [1392368783980, 1392380169184]}

      删除数据

      删除user表row key为rk0001,列标示符为info:name的数据

      delete 'people', 'rk0001', 'info:name'
      删除user表row key为rk0001,列标示符为info:name,timestamp为1392383705316的数据
      delete 'user', 'rk0001', 'info:name', 1392383705316

      清空user表中的数据

      truncate 'people'

      修改表结构

      首先停用user表

      disable 'user'

      添加两个列族f1和f2

      alter 'people', NAME => 'f1'

      alter 'user', NAME => 'f2'

      启用表

      alter 'people', NAME => enable 'user'

      删除一个列族:

      alter 'user', NAME => 'f1', METHOD => 'delete' 或 alter 'user', 'delete' => 'f1'

      添加列族f1同时删除列族f2

      alter 'user', {NAME => 'f1'}, {NAME => 'f2', METHOD => 'delete'}

      将user表的f1列族版本号改为5

      alter 'people', NAME => 'info', VERSIONS => 5

      启用表

      enable 'user'

      删除表

      disable 'user'

      drop 'user'

      查询数据

      get 'person', 'rk0001', {FILTER => "ValueFilter(=, 'binary:中国')"}

      get 'person', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}
      scan 'person', {COLUMNS => 'info:name'}
      scan 'person', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}
      scan 'person', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}

      scan 'person', {COLUMNS => 'info', STARTROW => '20140201', ENDROW => '20140301'}
      scan 'person', {COLUMNS => 'info:name', TIMERANGE => [1395978233636, 1395987769587]}
      delete 'person', 'rk0001', 'info:name'

      alter 'person', NAME => 'ffff'
      alter 'person', NAME => 'info', VERSIONS => 10

      get 'user', 'rk0002', {COLUMN => ['info:name', 'data:pic']}

      查询总数数目

      count ‘tableName’

hbase 代码开发(基本,过滤器查询

基本增删改查 java 实现

在 idea 中创建项目 hbase_api
引入pom 依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hbase</groupId>
        <artifactId>hbase-client</artifactId>
        <version>1.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hbase</groupId>
        <artifactId>hbase-common</artifactId>
        <version>1.2.1</version>
    </dependency>
</dependencies>
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.*;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.filter.FilterList.Operator;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

//todo:Hbase Api操作
public class HbaseDemo {
    //初始化Configuration对象
    private Configuration conf=null;
    //初始化链接
    private Connection conn = null;
    @Before
    public void init() throws Exception{
        conf= HBaseConfiguration.create();
        // 对于hbase的客户端来说,只需要知道hbase所使用的zookeeper集群地址就可以了
        // 因为hbase的客户端找hbase读写数据完全不用经过hmaster
        conf.set("hbase.zookeeper.quorum","vm0:2181,vm1:2181,vm2:2181");
        //获取链接
        conn=ConnectionFactory.createConnection(conf);
       }

    /**
     * 建表
     * @throws Exception
     */
    @Test
    public void createTable() throws Exception{
        //获取一个表的管理器
        Admin admin = conn.getAdmin();
        //构造一个表描述器,并指定表名
        HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf("t_user_info".getBytes()));
        //构造一个列族描述器,并指定列族名
        HColumnDescriptor hcd1 = new HColumnDescriptor("base_info");
        // 构造第二个列族描述器,并指定列族名
        HColumnDescriptor hcd2 = new HColumnDescriptor("extra_info");
        // 为该列族设定一个版本数量
        hcd2.setVersions(1, 3);

        // 将列族描述器添加到表描述器中
        tableDescriptor.addFamily(hcd1).addFamily(hcd2);

        //利用表的管理器创建表
        admin.createTable(tableDescriptor);
        //关闭
        admin.close();
        conn.close();
    }

    /**
     * 修改表
     * @throws Exception
     */
    @Test
    public void modifyTable() throws Exception{
        //获取一个表的管理器
        Admin admin = conn.getAdmin();
        //获取表的描述器
        HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("t_user_info"));
        //修改已有的ColumnFamily---extra_info最小版本数和最大版本数
        HColumnDescriptor hcd1 = tableDescriptor.getFamily("extra_info".getBytes());
        hcd1.setVersions(2,5);

        // 添加新的ColumnFamily
        tableDescriptor.addFamily(new HColumnDescriptor("other_info"));

        //表的管理器admin 修改表
        admin.modifyTable(TableName.valueOf("t_user_info"),tableDescriptor);
        //关闭
        admin.close();
        conn.close();
    }

    /**
     *  put添加数据
     * @throws Exception
     */
    @Test
    public void testPut() throws Exception {
        //构建一个 table对象,通过table对象来添加数据
        Table table = conn.getTable(TableName.valueOf("t_user_info"));
        //创建一个集合,用于存放Put对象
        ArrayList<Put> puts = new ArrayList<Put>();

        // 构建一个put对象(kv),指定其行键  例如hbase shell:  put '表名','rowkey','列族:列名称','值'
        Put put01 = new Put(Bytes.toBytes("user001"));
        put01.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("zhangsan"));


        Put put02 = new Put("user001".getBytes());
        put02.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("password"), Bytes.toBytes("123456"));

        Put put03 = new Put("user002".getBytes());
        put03.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("lisi"));
        put03.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("married"), Bytes.toBytes("false"));

        Put put04 = new Put("zhang_sh_01".getBytes());
        put04.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("zhang01"));
        put04.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("married"), Bytes.toBytes("false"));

        Put put05 = new Put("zhang_sh_02".getBytes());
        put05.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("zhang02"));
        put05.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("married"), Bytes.toBytes("false"));

        Put put06 = new Put("liu_sh_01".getBytes());
        put06.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("liu01"));
        put06.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("married"), Bytes.toBytes("false"));

        Put put07 = new Put("zhang_bj_01".getBytes());
        put07.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("zhang03"));
        put07.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("married"), Bytes.toBytes("false"));

        Put put08 = new Put("zhang_bj_01".getBytes());
        put08.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("zhang04"));
        put08.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("married"), Bytes.toBytes("false"));

        //把所有的put对象添加到一个集合中
        puts.add(put01);
        puts.add(put02);
        puts.add(put03);
        puts.add(put04);
        puts.add(put05);
        puts.add(put06);
        puts.add(put07);
        puts.add(put08);

        //一起提交所有的记录
        table.put(puts);

        table.close();
        conn.close();

    }

    /**
     *  读取数据  get:一次读一行
     * @throws Exception
     */
    @Test
    public void testGet() throws Exception {
        //获取一个table对象
        Table table = conn.getTable(TableName.valueOf("t_user_info"));
        // 构造一个get查询参数对象,指定要get的是哪一行
        Get get = new Get("user001".getBytes());
        //返回查询结果数据
        Result result = table.get(get);
        //获取结果中的所有cell
        List<Cell> cells = result.listCells();
        //遍历所有的cell
        for(Cell c:cells){
            //获取行键
            byte[] rowArray = c.getRowArray();
            //获取列族
            byte[] familyArray = c.getFamilyArray();
            //获取列族下的列名称
            byte[] qualifierArray = c.getQualifierArray();
            //列字段的值
            byte[] valueArray = c.getValueArray();
            //打印rowArray、familyArray、qualifierArray、valueArray
//            System.out.println(new String(rowArray));
//            System.out.println(new String(familyArray));
//            System.out.println(new String(qualifierArray));
//            System.out.println(new String(valueArray));

            //按指定位置截取,获取rowArray、familyArray、qualifierArray、valueArray
            System.out.print(new String(rowArray, c.getRowOffset(), c.getRowLength()));
            System.out.print(" "+new String(familyArray, c.getFamilyOffset(), c.getFamilyLength()));
            System.out.print(":" + new String(qualifierArray, c.getQualifierOffset(), c.getQualifierLength()));
            System.out.println(" " + new String(valueArray, c.getValueOffset(), c.getValueLength()));

        }

        //关闭
        table.close();
        conn.close();

    }

    /**
     * scan 批量查询数据
     * @throws Exception
     */
    @Test
    public void testScan() throws Exception {
        //获取table对象
        Table table = conn.getTable(TableName.valueOf("t_user_info"));
        //获取scan对象
        Scan scan = new Scan();
        //获取查询的数据
        ResultScanner scanner = table.getScanner(scan);
        //获取ResultScanner所有数据,返回迭代器
        Iterator<Result> iter = scanner.iterator();
        //遍历迭代器
        while (iter.hasNext()) {
            //获取当前每一行结果数据
            Result result = iter.next();
            //获取当前每一行中所有的cell对象
            CellScanner cellScanner = result.cellScanner();
            //迭代所有的cell
            while (cellScanner.advance()) {
                //获取第一个cell对象
                Cell current = cellScanner.current();
                byte[] rowArray = current.getRowArray();
                byte[] familyArray = current.getFamilyArray();
                byte[] qualifierArray = current.getQualifierArray();
                byte[] valueArray = current.getValueArray();


                System.out.println(new String(rowArray, current.getRowOffset(), current.getRowLength()));
                System.out.print(new String(familyArray, current.getFamilyOffset(), current.getFamilyLength()));
                System.out.print(":" + new String(qualifierArray, current.getQualifierOffset(), current.getQualifierLength()));
                System.out.println(" " + new String(valueArray, current.getValueOffset(), current.getValueLength()));
            }
            System.out.println("-----------------------");
        }

        //关闭
        table.close();
        conn.close();

    }


    /**
     * 删除表中的列数据
     * @throws Exception
     */
    @Test
    public void testDel() throws Exception {
        //获取table对象
        Table table = conn.getTable(TableName.valueOf("t_user_info"));
        //获取delete对象,需要一个rowkey
        Delete delete = new Delete("user001".getBytes());
        //在delete对象中指定要删除的列族-列名称
        delete.addColumn("base_info".getBytes(), "password".getBytes());
        //执行删除操作
        table.delete(delete);

        //关闭
        table.close();
        conn.close();
    }

    /**
     * 删除表
     * @throws Exception
     */
    @Test
    public void testDrop() throws Exception {
        //获取一个表的管理器
        Admin admin = conn.getAdmin();
        //删除表时先需要disable,将表置为不可用,然后在delete
        admin.disableTable(TableName.valueOf("t_user_info"));
        admin.deleteTable(TableName.valueOf("t_user_info"));
        admin.close();
        conn.close();
    }
}

基本的增删改查 scala 实现(依赖同上)

import java.util

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.{Cell, CellUtil, HBaseConfiguration, HColumnDescriptor, HTableDescriptor, TableName}
import org.apache.hadoop.hbase.client._
RowFilter, SubstringComparator}
import org.apache.hadoop.hbase.util.Bytes
import scala.collection.mutable.ArrayBuffer

def delcolumn(configuration: Configuration,tablename:String,columnName:String): Unit ={
    val connection: Connection = ConnectionFactory.createConnection(configuration)
    val tableName: TableName = TableName.valueOf(tablename)
    val admin: Admin = connection.getAdmin
    admin.deleteColumn(tableName,Bytes.toBytes(columnName))
  }

  def delRow(configuration: Configuration,tablename:String,rowkey:String): Unit ={
    val connection: Connection = ConnectionFactory.createConnection(configuration)
    val tableName: TableName = TableName.valueOf(tablename)
    val table: Table = connection.getTable(tableName)
    val delete = new Delete(Bytes.toBytes(rowkey))
    table.delete(delete)
    println("删除成功")
  }

  def getRow(configuration: Configuration,tablename:String,rowkey:String): Unit ={
    val connection: Connection = ConnectionFactory.createConnection(configuration)
    val tableName: TableName = TableName.valueOf(tablename)
    val table: Table = connection.getTable(tableName)
    val get = new Get(Bytes.toBytes(rowkey))
    val result: Result = table.get(get)
    val cells: Array[Cell] = result.rawCells()
    cells.foreach(println)

  }

  def addcolumn(configuration: Configuration,tablename:String,columnName:String): Unit ={
    val connection: Connection = ConnectionFactory.createConnection(configuration)
    val tableName: TableName = TableName.valueOf(tablename)
    val admin: Admin = connection.getAdmin
    val columnDescriptor = new HColumnDescriptor(columnName)
    val tableDescriptor: HTableDescriptor = admin.getTableDescriptor(tableName)
    admin.addColumn(tableName,columnDescriptor)
  }

  def deletedata(configuration: Configuration,tablename:String): Unit ={
    val connection: Connection = ConnectionFactory.createConnection(configuration)
    val tableName: TableName = TableName.valueOf(tablename)
    val admin: Admin = connection.getAdmin
    admin.disableTable(tableName)
    admin.deleteTable(tableName)
    println("删除成功")
  }

  def selectone(configuration: Configuration,tablename:String): Unit ={
    val connection: Connection = ConnectionFactory.createConnection(configuration)
    val tableName: TableName = TableName.valueOf(tablename)
    val admin: Admin = connection.getAdmin
    val table: Table = connection .getTable(tableName)
    val scan = new Scan()
    scan.setStartRow(Bytes.toBytes("rk0002"))
    scan.setStopRow(Bytes.toBytes("rk0002"))
    val scanner: ResultScanner = table.getScanner(scan)
    var result: Result = scanner.next()
    val cellses = new ArrayBuffer[Array[Cell]]()
    while(result!=null){
      cellses.append(result.rawCells())
      result = scanner.next()
    }

    cellses.foreach(_.foreach(x=>{
    println(x)
//      println(x.getRowArray,x.getRowOffset,x.getRowLength)
//      println(x.getFamilyArray,x.getFamilyOffset,x.getFamilyLength)
//      println(":"+x.getQualifierArray,x.getQualifierOffset,x.getQualifierLength)
//      println(" " + x.getValueArray,x.getValueOffset,x.getValueLength)
    }))
  }

  def selectall(configuration: Configuration,tablename:String): Unit ={
    val connection: Connection = ConnectionFactory.createConnection(configuration)
    val tableName: TableName = TableName.valueOf(tablename)
    val admin: Admin = connection.getAdmin
    val table: Table = connection .getTable(tableName)
    val scan = new Scan()
    val scanner: ResultScanner = table.getScanner(scan)
    var result: Result = scanner.next()
    val cellses = new ArrayBuffer[Array[Cell]]()
    while(result!=null){
      cellses.append(result.rawCells())
      result = scanner.next()
    }
    println("=====================")

    cellses.foreach(_.foreach(x=>{
      println(x)
      /*println(x.getRowArray,x.getRowOffset,x.getRowLength)
      println(x.getFamilyArray,x.getFamilyOffset,x.getFamilyLength)
      println(":"+x.getQualifierArray,x.getQualifierOffset,x.getQualifierLength)
      println(" " + x.getValueArray,x.getValueOffset,x.getValueLength)*/
    }))
  }

  def insertdatas(configuration: Configuration,tablename:String): Unit ={
      val connection: Connection = ConnectionFactory.createConnection(configuration)
      val tableName: TableName = TableName.valueOf(tablename)
      val table: BufferedMutator = connection.getBufferedMutator(tableName)
      
      val put1 = new Put(Bytes.toBytes("rk001"))
      put1.addColumn(Bytes.toBytes("info"),Bytes.toBytes("name"),Bytes.toBytes("xiaoming"))
      put1.addColumn(Bytes.toBytes("data"),Bytes.toBytes("score"),Bytes.toBytes(90))

      val put2 = new Put(Bytes.toBytes("rk002"))
      put2.addColumn(Bytes.toBytes("info"),Bytes.toBytes("name"),Bytes.toBytes("xiaohong"))
      put2.addColumn(Bytes.toBytes("data"),Bytes.toBytes("score"),Bytes.toBytes(85))

      val put3 = new Put(Bytes.toBytes("rk003"))
      put3.addColumn(Bytes.toBytes("info"),Bytes.toBytes("name"),Bytes.toBytes("xiaohua"))
      put3.addColumn(Bytes.toBytes("data"),Bytes.toBytes("score"),Bytes.toBytes(88))


      val puts = new util.ArrayList[Put]()
      puts.add(put1)
      puts.add(put2)
      puts.add(put3)

      table.mutate(puts)
      table.flush()

      println("批量插入成功")
  }


  def createTable(configuration: Configuration,tablename:String,columnfamily:Array[String]): Unit ={
      val connection: Connection = ConnectionFactory.createConnection(configuration)
      val admin: Admin = connection.getAdmin
      val tableName: TableName = TableName.valueOf(tablename)
      if (admin.tableExists(tableName)){
        admin.disableTable(tableName)
        admin.deleteTable(tableName)
      }
      val tabledesc = new HTableDescriptor(tableName)
      columnfamily.foreach(x => tabledesc.addFamily(new HColumnDescriptor(x)))
      admin.createTable(tabledesc)
      println("创建成功")
  }

过滤器查询

引言:过滤器的类型很多,但是可以分为两大类 —比较过滤器,专用过滤器
过滤器的作用是在服务端判断数据是否满足条件,然后只将满足条件的数据返回给客户端。

hbase 过滤器的比较运算符:(按照字典顺序进行比较)
LESS <
LESS_OR_EQUAL <=
EQUAL =
NOT_EQUAL <>
GREATER_OR_EQUAL >=
GREATER >
NO_OP 排除所有(不做任何操作 等待)

Hbase 过滤器的比较器(指定比较机制)
inaryComparator 按字节索引顺序比较指定字节数组,采用Bytes.compareTo(byte[])
BinaryPrefixComparator 跟前面相同,只是比较左端的数据是否相同
NullComparator 判断给定的是否为空
BitComparator 按位比较
RegexStringComparator 提供一个正则的比较器,仅支持 EQUAL 和非EQUAL
SubstringComparator 判断提供的子串是否出现在value中。

Hbase 过滤器的分类

  • 比较过滤器

    1. 行键过滤器 RowFilter
      Filter filter1 = new RowFilter(CompareOp.LESS_OR_EQUAL, 					    new BinaryComparator(Bytes.toBytes("row-22")));  
      scan.setFilter(filter1);  
      
    2. 列族过滤器FamilyFilter
      Filter filter1 = new FamilyFilter(CompareFilter.CompareOp.LESS, new BinaryComparator(Bytes.toBytes("colfam3")));
      scan.setFilter(filter1);  
      
    3. 列过滤器 QualifierFilter
      filter = new QualifierFilter(CompareFilter.CompareOp.LESS_OR_EQUAL, new BinaryComparator(Bytes.toBytes("col-2")));
      scan.setFilter(filter1);
      
    4. 值过滤器 ValueFilter
      Filter filter = new ValueFilter(CompareFilter.CompareOp.EQUAL, new SubstringComparator(".4") );  
      scan.setFilter(filter1);  
      
  • 专用过滤器

    1. 单列值过滤器 SingleColumnValueFilter ----会返回满足条件的整行
      SingleColumnValueFilter filter = new SingleColumnValueFilter(  
      Bytes.toBytes("colfam1"),  
      Bytes.toBytes("col-5"),  
      CompareFilter.CompareOp.NOT_EQUAL,  
      new SubstringComparator("val-5"));  
      filter.setFilterIfMissing(true);  //如果不设置为true,则那些不包含指定	column的行也会返回
      scan.setFilter(filter1);  
      
    2. SingleColumnValueExcludeFilter 与上相反
    3. 前缀过滤器 PrefixFilter----针对行键
      Filter filter = new PrefixFilter(Bytes.toBytes("row1"));  
      scan.setFilter(filter1);  
      
    4. 列前缀过滤器 ColumnPrefixFilter
      Filter filter = new ColumnPrefixFilter(Bytes.toBytes("qual2"));  
      scan.setFilter(filter1);  
      
    5. 分页过滤器 PageFilter
      /**
       * 分页查询
       *
       * @throws Exception
       */
      @Test
      public void pageScan() throws Exception {
          //定义一个字节数组(0x00  这是我们给的一个随机数)
          final byte[] POSTFIX = new byte[]{0x00};
          //定义一个table对象
          Table table = conn.getTable(TableName.valueOf("t_user_info"));
          //一次需要获取一页的条数
          Filter filter = new PageFilter(3);
          //定义个字节数组
          byte[] lastRow = null;
          //总数 初始值0
          int totalRows = 0;
          while (true) {
              Scan scan = new Scan();
              //权标扫描,每次返回三条
              scan.setFilter(filter);
              if (lastRow != null) {
                  byte[] startRow = Bytes.add(lastRow, POSTFIX);   //设置本次查询的起始行键 00000030x00
                  scan.setStartRow(startRow);
              }
              ResultScanner scanner = table.getScanner(scan);
              int localRows = 0; //开始行号
              Result result;
              //如果不等null  说明里面有值
              while ((result = scanner.next()) != null) {
                  System.out.println(++localRows + ":" + result);
                  totalRows++;
                  //结果赋值
                  lastRow = result.getRow();    //0000003
              }
              scanner.close();
              //重新赋值0,在进行下一次的查询
              if (localRows == 0) {
                  break;
              }
              Thread.sleep(2000);
          }
          System.out.println("total rows:" + totalRows);
          //关闭
          table.close();
          conn.close();
      }
      
    6. 综合使用
      /**
       * @describe:定义一个方法,接受一个过滤器,返回结果数据
       * @Param: [filter] 过滤
       * @Return: void
       */
      public void testScan(Filter filter) throws Exception {
          //拿到表对象
          Table table = conn.getTable(TableName.valueOf("t_user_info"));
      
          Scan scan = new Scan();
          //设置过滤器
          scan.setFilter(filter);
          //返回满足条件的结果
          ResultScanner scanner = table.getScanner(scan);
      
          //以迭代器的形式返回
          Iterator<Result> iter = scanner.iterator();
          while (iter.hasNext()) {
              Result result = iter.next();
              CellScanner cellScanner = result.cellScanner();
              while (cellScanner.advance()) {
                  Cell current = cellScanner.current();
                  byte[] rowArray = current.getRowArray();
                  byte[] familyArray = current.getFamilyArray();
                  byte[] valueArray = current.getValueArray();
                  byte[] qualifierArray = current.getQualifierArray();
                  //打印结果
                  System.out.println(new String(rowArray, current.getRowOffset(), current.getRowLength()));
                  System.out.print(new String(familyArray, current.getFamilyOffset(), current.getFamilyLength()));
                  System.out.print(":" + new String(qualifierArray, current.getQualifierOffset(), current.getQualifierLength()));
                  System.out.println(" " + new String(valueArray, current.getValueOffset(), current.getValueLength()));
              }
              System.out.println("-----------------------");
          }
      }
      
      
      
       /**
           * 过滤器使用
           *
           * @throws Exception
           */
          @Test
          public void testFilter() throws Exception {
      
              // 1.针对行键的前缀过滤器(将字符串转化成字节数组)
          /*    Filter pf = new PrefixFilter(Bytes.toBytes("liu"));
              testScan(pf);*/
      
              // 2.行过滤器  需要一个 比较运算符 和 比较器(比user002小的都可以打印出来的user002)
      /*     RowFilter rf1 = new RowFilter(CompareOp.LESS, new BinaryComparator(Bytes.toBytes("user002")));
               testScan(rf1);*/
      
             /*RowFilter rf2 = new RowFilter(CompareOp.EQUAL, new SubstringComparator("01"));//rowkey包含"01"子串的
             testScan(rf2);*/
      
              // 3.针对指定一个列的value来过滤(把passwoord等于123456 的打印出来)
              //SingleColumnValueFilter scvf = new SingleColumnValueFilter("base_info".getBytes(), "password".getBytes(), CompareOp.EQUAL, "123456".getBytes());
         /* SingleColumnValueFilter scvf = new SingleColumnValueFilter("base_info".getBytes(), "password".getBytes(), CompareOp.LESS, "zzzz".getBytes());
            scvf.setFilterIfMissing(false);   // true表示如果指定的列缺失,则也过滤掉不显示,为false表示不过滤也显示(测试一下)
            testScan(scvf);*/
      
              //4.针对指定一个列的value的比较器来过滤
      //    ByteArrayComparable comparator1 = new RegexStringComparator("^zhang"); //以zhang开头的
      //    ByteArrayComparable comparator2 = new SubstringComparator("si");      //包含"si"子串
      //    SingleColumnValueFilter scvf = new SingleColumnValueFilter("base_info".getBytes(), "username".getBytes(), CompareOp.EQUAL, comparator1);
      //    SingleColumnValueFilter scvf = new SingleColumnValueFilter("base_info".getBytes(), "username".getBytes(), CompareOp.EQUAL, comparator2);
      //    testScan(scvf);
      
      
              //5.针对列族名的过滤器   返回结果中只会包含满足条件的列族中的数据
              //列簇是basicInfo的打印出来
            /*  FamilyFilter ff1 = new FamilyFilter(CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("base_info"))); //
              //列簇是一base开头的信息
              FamilyFilter ff2 = new FamilyFilter(CompareOp.EQUAL, new BinaryPrefixComparator(Bytes.toBytes("base")));
              testScan(ff2);*/
      
              //6.针对列名的过滤器 返回结果中只会包含满足条件的列的数据
            /*QualifierFilter qf1 = new QualifierFilter(CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("password")));
            QualifierFilter qf2 = new QualifierFilter(CompareOp.EQUAL, new BinaryPrefixComparator(Bytes.toBytes("user")));
            testScan(qf1);
            testScan(qf2);*/
      
              //7.跟SingleColumnValueFilter结果不同,只返回符合条件的该column
              //列族的前缀
         /* ColumnPrefixFilter cf = new ColumnPrefixFilter("passw".getBytes());
            testScan(cf);*/
      
      
              //8.多个列进行过滤,只需要满足一个条件就可以,是或者的关系
         /* byte[][] prefixes = new byte[][] { Bytes.toBytes("username"),Bytes.toBytes("password") };
            MultipleColumnPrefixFilter mcf = new MultipleColumnPrefixFilter(prefixes);
            testScan(mcf);*/
      
              //9.多个过滤器同时使用
              FamilyFilter ff2 = new FamilyFilter(CompareOp.EQUAL, new BinaryPrefixComparator(Bytes.toBytes("base")));
              ColumnPrefixFilter cf = new ColumnPrefixFilter("password".getBytes());
              FilterList filterList = new FilterList(Operator.MUST_PASS_ALL);
              filterList.addFilter(ff2);
              filterList.addFilter(cf);
              testScan(filterList);
          }
      

补充(打印Cell的简便方法— scala)

	var result = scanner.next()
	while (result != null){
        result.rawCells().foreach(x=>{		//result.rawCells() 获取 Cell
        println(
            Bytes.toString(CellUtil.cloneRow(x)) +" "+
            Bytes.toString(CellUtil.cloneFamily(x)) +" "+
            Bytes.toString(CellUtil.cloneQualifier(x)) +" "+
            Bytes.toString(CellUtil.cloneValue(x))
        )
        })
        result = scanner.next()
      }

Hbase 高级应用(了解)

建表高级属性

      下面几个 shell 命令在 hbase 操作中可以起到很好的作用,且主要体现在建表过程中,看下面几个 create 属性。

  1. BLOOMFILTER 默认是 NONE 是否使用布隆过滤及何种方式。
    布隆过滤可以每列族单独启用
    使用 HColumnDescriptor.setBloomFilterType (NONE | ROW | ROWCOL )对列族单独启用布隆。
       ☆ Default = ROW 对行进行布隆过滤。
       ☆ 对 ROW,行键的哈希在每次插入行时将被添加到布隆。
       ☆ 对 ROWCOL,行键 + 列族 + 列族修饰的哈希将在每次插入行时添加到布隆
          使用方法: create ‘table’,{BLOOMFILTER =>‘ROW’}
          HColumnDescriptor.setBloomFilterType

  2. VERSIONS 默认是1 这个参数的意思是数据保留1个 版本,如果我们认为我们的数据没有这么大的必要保留这么多,随时都在更新,而老版本的数据对我们毫无价值,那将此参数设为1 能节约2/3的空间。
          使用方法: create ‘table’,{VERSIONS=>‘2’}
          附:MIN_VERSIONS => '0’是说在 compact 操作执行之后,至少要保留的版本。

  3. COMPRESSION 默认值是NONE 即不使用压缩。
          这个参数意思是该列族是否采用压缩,采用什么压缩算法
          使用方法: create ‘table’,{NAME=>‘info’,COMPRESSION=>‘SNAPPY’}
          建议采用SNAPPY压缩算法

           HBase 中,在 Snappy 发布之前(Google 2011年对外发布 Snappy ),采用的LZO算法,目标是达到尽可能快的压缩和解压速度,同时减少对 CPU 的消耗;

           在 Snappy 发布之后,建议采用 Snappy 算法(参考《HBase: The Definitive Guide》),具体可以根据实际情况对 LZO 和 Snappy 做过更详细的对比测试后再做选择。;

    lgorithm% remainingEncodingDecoding
    ZIP13.4%21 MB/s118 MB/s
    ZO20.5%135 MB/s410 MB/s
    ippy/Snappy22.2%172 MB/s409 MB/s

           如果建表之初没有压缩,后来想要加入压缩算法,可以通过alter修改schema。

  4. alter
    使用方法:
    如: 修改压缩算法
    disable ‘table’
    alter ‘table’,{NAME=>‘info’,COMPRESSION=>‘snappy’}

  5. TTL
    默认是 2147483647 即:Integer.MAX_VALUE 值大概是 68 年。
    这个参数时说明该列族的存活时间,单位是s。

          这个参数可以根据具体的需求对数据设定存活时间,超过存过时间的数据将在表中不在显示,待下次 major compact 的时候再彻底删除数据。

          注意的是 TTL 设定之后 MIN_VERSIONS=>'0' 这样设置之后,TTL 时间戳过期后,将全部彻底删除该family下所有的数据,如果 MIN_VERSIONS 不等于0那将保留最新的 MIN_VERSIONS 个版本的数据,其它的全部删除,比如 MIN_VERSIONS=>'1' 届时将保留一个最新版本的数据,其它版本的数据将不再保存。

  6. describe ‘table’ 这个命令查看了 create table 的各项参数或者是默认值。

  7. disable_all   ‘toplist.*’  disable_all 支持正则表达式,并列出当前匹配的表的如下:
    toplist_a_total_1001
    toplist_a_total_1002
    toplist_a_total_1008
    toplist_a_total_1009
    toplist_a_total_1019
    toplist_a_total_1035

    Disable the above 25 tables (y/n)? 并给出确认提示.

  8. drop_all 这个命令和disable_all的使用方式是一样的

  9. hbase 表预分区----手动分区

          默认情况下,在创建 HBase 表的时候会自动创建一个 region 分区,当导入数据的时候,所有的 HBase 客户端都向这一个 region 写数据,直到这个 region 足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入 HBase 时,会按照 region 分区情况,在集群内做数据的负载均衡。

          create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'} 也可以使用api的方式:

          bin/hbase org.apache.hadoop.hbase.util.RegionSplitter test_table HexStringSplit -c 10 -f info

          参数:
          test_table 是表名
          HexStringSplit 是 split 方式
          -c 是分10个region
          -f 是family
    可在 UI 上查看结果,如图:

    在这里插入图片描述

          这样就可以将表预先分为 15 个区,减少数据达到 storefile 大小的时候自动分区的时间消耗,并且还有以一个优势,就是合理设计 rowkey 能让各个 region 的并发请求平均分配(趋于均匀) 使 IO 效率达到最高,但是预分区需要将 filesize 设置一个较大的值,设置哪个参数呢, hbase.hregion.max.filesize 这个值默认是 10G 也就是说单个 region 默认大小是 10G。

           这个参数的默认值在 0.90 到 0.92 到 0.94.3各版本的变化:256M--1G--10G

          但是如果 MapReduce Input 类型为 TableInputFormat 使用 hbase 作为输入的时候,就要注意了, 每个 region 一个 map,如果数据小于 10G 那只会启用一个 map 造成很大的资源浪费,这时候 可以考虑适当调小该参数的值, 或者采用预分配 region 的方式,并将检测如果达到这个值,再手动分配 region。

hbase应用案例行键设计

表结构设计

  1. 列族数量的设定

          以用户信息为例,可以将必须的基本信息存放在一个列族,而一些附加的额外信息可以放在另一列族。

          比如说:电信,联通,移动

  2. 行键的设计

           语言详单:

           13877889988-20150625

           13877889988-20150625

           13877889988-20150626

           13877889988-20150626

           13877889989

           13877889989

           13877889989

           -- 将需要批量查询的数据尽可能连续存放。

           CMS 系统--多条件查询

           尽可能将查询条件关键词拼装到 rowkey 中,查询频率最高的条件尽量往前靠

           20150230-zhangsan-category…

           20150230-lisi-category…

          Category+20150230       20150230-zhangsan-category

          (每一个条件的值长度不同,可以通过做定长映射来提高效率)

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 字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。

rowKey 散列原则

      如果 rowkey 按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将 rowkey 的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个 RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个 RegionServer 上,这样在数据检索的时候负载会集中在个别的 RegionServer 上,造成热点问题,会降低查询效率。

rowKey 唯一原则

      必须在设计上保证其唯一性,rowkey 是按照字典顺序排序存储的,因此,设计 rowkey 的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。

rowKey 代码实现

def getKeys():mutable.ListBuffer[String]= {
  //1.startKey是从mysql中读取
  val format = new SimpleDateFormat("yyyyMMddHHmmss")
  var num: Long = format.format(new Date()).toLong
  val array = new Array[Char](7)
  val list = new mutable.ListBuffer[String]()
  val builder = new mutable.StringBuilder()
  for (i <- 0 until 7) {
    array(6 - i) = (num % 42 + 48).toChar
    num = num / 42
  }
  for (it <- array) {
    builder.append(it.toString)
  }
  list.append(builder.toString())
  list
}

什么是热点

      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 ,这样的就避免了以手机号那样比较固定开头导致热点问题。

时间戳反转

      一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为 rowkey 的一部分对这个问题十分有用,可以用 Long.Max_Value - timestamp 追加到 key 的末尾,例如 [key][reverse_timestamp] , [key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为 HBase 中 rowkey 是有序的,第一条记录是最后录入的数据。

      比如需要保存一个用户的操作记录,按照操作时间倒序排序,在设计 rowkey 的时候,可以这样设计:

      [userId反转][Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指定反转后的userId,startRow是[userId反转][000000000000], stopRow 是[userId反转][Long.Max_Value - timestamp]

      如果需要查询某段时间的操作记录,startRow 是[user反转][Long.Max_Value - 起始时间], stopRow 是[userId反转][Long.Max_Value - 结束时间]

      其他一些建议

      尽量减少行和列的大小在 HBase 中, value 永远和它的 key 一起传输的。当具体的值在系统间传输时,它的 rowkey ,列名,时间戳也会一起传输。如果你的 rowkey 和列名很大,甚至可以和具体的值相比较,那么你将会遇到一些有趣的问题。 HBase storefiles 中的索引(有助于随机访问)最终占据了 HBase 分配的大量内存,因为具体的值和它的 key 很大。可以增加 block 大小使得 storefiles 索引再更大的时间间隔增加,或者修改表的模式以减小 rowkey 和列名的大小。压缩也有助于更大的索引。

      列族尽可能越短越好,最好是一个字符

      冗长的属性名虽然可读性好,但是更短的属性名存储在 HBase 中会更好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值