Hbase分布式非关系型数据库

HBase简介
HBase数据模型
HBase操作命令
HBase预分区
hbase优化

HBase简介

  • HBase是基于HDFS的,是实时的 , 高可靠性(当一个节点故障,另一个节点能迅速切换),高性能(用来处理大数据) , 面向列 , 可伸缩的(可以横向扩展节点) ,实时读写的分布式的非关系型数据库 .
  • 在大数据里, 我们用HDFS做文件存储系统 , MR来处理hbase中的海量数据 , 用zookeeper来作为其分布式协同服务 .
  • hbase主要用来存储非结构化和半结构化的松散数据(列值存储NoSQL数据库)
  • mysql等关系型数据库是面向于行的 , 关系型数据库有3大优点: 容易理解 , 使用方便 , 易于维护 . 但也有三大缺点: 高并发读写需求(磁盘IO是一个很大的瓶颈 , 很难承受高访问) , 海量数据的读写性能低 , 扩展性和可用性差 . 关系型数据库能操作的数据在千万级别左右 , hbase是上亿的 .
  • 关系型数据库,导致性能欠佳的最主要原因是多表的关联查询 , 以及复杂的多表关联查询 .
  • HBase 非关系型数据库时面向于列的 , 非关系型数据库还有redis , MongoDB等 .
  • HBase利用MapReduce来处理海量数据, 利用Zookeeper来作分布式协同服务 Hbase和Zookeeper具有强依赖关系(没有不行).
  • 数据库依据不同的应用场合, 可以分为几类: 高性能并发读写的键值对数据库: Redis , Tokyo , Cabinet , Flare , 面向海量数据访问的面向文档数据库: MongoDB , CouchDB . 面向可扩展性的分布式数据库: HBase
  • 严谨的银行系统适合用关系型数据库, 微博,视频等实时,高并发的业务系统适合用非关系型数据库

HBase数据模型

在这里插入图片描述
以上图片为hbase数据库的一条数据 .

  • RowKey : 类似关系型数据库的主键 , 唯一确定的的字段,决定一行记录,按照字典顺率排序,只能存储64k的字节数据,rowkey的设计很关键,如果用时间戳作为rowkey的开头,那么越新的数据排在越后,我们可以通过一个max最大值减去时间戳.还有散列,倒叙
  • TimeStamp:时间戳, 用于标注我们的版本.在hbase每个cell存储单元对同一份数据有多个版本 , 根据唯一的时间戳来区分每个版本之间的差异,不同版本的数据按照时间倒序排序 , 最新的数据版本排在最前面 . 时间戳的类型是64位整形. 时间戳可以在数据写入时自动赋值 , 赋值的时间戳是精确到毫秒的当前系统时间 . 时间戳也可以由客户显式赋值,如果应用程序要避免数据的版本冲突 , 就必须自己生成具有唯一性的时间戳 .
  • CF1,CF2,CF3:列族 , 必须声明 , 定义表的时候要提前给出 .hbase中的每个列都归属于某个列族 , 列族是我们能够处理的最小单位, 列族必须作为表模式定义的一部分预先给出 ,在创建表的时候我们可以不给列,但必须给出列族 , 每个列族都可以有多个列成员 . 权限控制 ,存储以及调优都是在列族层面进行的 . hbase把同一列族里面的数据储存在同一目录下 , 由几个文件保存.
  • q1,q2,q3 : 列名 ,列名以列族作为前缀 ,
  • val1.val2,val3:列值
  • cell单元格是由行和列的坐标交叉决定的 , 单元格是有版本的 , 单元格的内容是未解析的字节数组 .
  • HLog(WAL log) : HLog 记录了数据的归属信息 , 一般的操作日子只保存操作 , HLog不仅保存操作,也保存数据 . 如果发生数据丢失 , 可以恢复数据 .
  • HBase架构

在这里插入图片描述
hbase执行流程:client端接受并进行读写操作,访问zookeep查询寻址入口信息 , master向zookeeper心跳保持,RegionServer节点存放着数据,一台服务器节点有一个RegionServer,client端操作的时候会把操作信息和数据写到HLog , RegionServer下有多个Region,每个Region存放着一张表的某部分连续字段,每个Region的数据量达到一定阈值的时候会水平裂变成等分的两个Region. 每个Region一般情况下会有两个到三个的Store(列族). 数据在写到Region的Store的时候是写到Store的MemStore内存,写满之后会落地到磁盘形成一个个StoreFile文件 , 当StoreFile文件达到一定阈值的时候,会把一个个StoreFile文件进行合并成大的StoreFile文件(小合并). storefile越来越大也就是store越来越大,region也越来越到 , storeFile合并到无法再合并的时候,region就会等分裂变成2个region,每个region又可以接受新的数据 . 当多个RegionServer的工作量差距比较大的时候, Master会将忙的RegionServer节点的region分配给空闲的RegionServer节点 , 负载均衡 . 每隔24小时会有一次大合并 , 把多个region之间的StoreFile进行合并 .

  • client包含访问hbase的接口 并维护cache缓存来加快对hbase的访问
  • zookeeper ,保存任何时候 , 集群中只有一个master , 存储所有Region的寻址入口 . 心跳检查实时监控RegionServer的上线和下线信息 . 并实时通知Master . 存储hbase的schema和table元数据,hbase的元数据信息和hive不同,是保存在zookeeper上面的 .
  • Master 为RegionServer分配Region , 负责RegionServer的负载均衡 . 发现失效的RegionServer(宕机)重新分配其上的region , 管理用户对table的增删改操作.
  • HRegionServer , ResionServer维护region,处理对这些region的IO请求 , RegionServer负责切分在运行过程中变得过大的region . 当不满足条件的时候可扩展
  • HRegion , Region自动把表水平划分成多个区域 , 每个region会保存一个表里面某段连续的数据 . 每个表一开始只有一个region , 随着数据不断插入表 , region不断增大 , 当增大到一个阈值的时候 , region就会等裂变成两个新的region . 当table中的行不断增多 , 就会有越来越多的region, 这样一张完整的表就被保存在多个regionSerber上了 . 存数据,HRegion也可以扩展 .
  • 一个Store代表一个列族
  • MemStore和storefile , 一个region由多个store组成,一个store对应一个CF列族 , store包括位于内存中的memstore和位于磁盘的storefile写操作先写入memstore,当memstore中的数据达到某个阈值 , hregionserver会启动flashcache进程写入storefile, 每次写入形成单独的一个storefile .
    当storefile文件的数量增长到一定与之后 , 系统会进行合并 , 在合并过程中进行版本合并和删除工作 , 形成更大的storefile .
    一个region所有storefile的大小和数量超过一定阈值后 , 会把当前的region分割成两个 , 并由hmaster分配到相应的regionserver服务器 , 实现负载均衡
    客户端检索数据 ,现在memstore找 , 找不到再找storefile .
    MemStore超过例如64M产生溢写到StoreFile
  • dfs client把数据文件存到hdfs上
  • hbase的数据保存在hdfs上
  • HRegionServer , HRegion , Store(1-2) , StoreFile到达阈值后都会进行等分裂变
  • Hregion是Hbase中分布式存储和负载均衡的最小单元, 不同的HRegion可以分布在不同的HregionServer上 .
  • HRegion由一个或者多个Store组成 , 每个Store保存一个columns family
  • 每个Store又由一个memStore和0至多个StoreFile组成 , 如图 , StoreFile以HFile格式保存在HDFS上 .
    在这里插入图片描述

HBase预分区

在HBase创建的一般表 , 一张表只有一个region , 所有添加的数据都只向一个region中进行操作 , 当这个region过大就会产生split裂变 . split裂变会带来资源损耗 , 预分区用多个region来一起承受数据的添加 , 就可以减少spilit裂变操作的产生 .
标的预分区要结合业务场景来选择合适的分区的key值区间 , 预分区不同的region都有各自的startKey和endKey , 左壁右开 , 作为单一region数据存储的区间范围 .

  • 创建预分区表:
    create 'yfq','cf1',SPLITS => ['10','20','30']
  • yfq:表名 __ cf1:列族___按[]中的值分为了以下4个区
  • 值得注意的是,区间划分不是对比int类型,而是对比String类型,例如rowkey的值:00101,取开头2位,它将被划分在第一个分区 .
    在这里插入图片描述

HBase操作命令

  • 进入hbase数据库:hbase shell,shell6中回删按键:ctrl+backspace
  • 帮助文档:help
  • 查看表list
  • 创建表
  • 创建带列族的表
  • 创建一张名为t1,列族是cf1的表:create 't1' , 'cf1'
  • 往表中添加数据put '[表名]','[rowkey]','[列族]:[字段名]','[值]',
    例子:put ‘t1’,‘00101’,‘cf1:name’,'zhangsan’
  • 创建带列族和版本的表
  • 创建一张名为t2,列族为cf1,带5个版本的表create 't2',{NAME => 'cf1', VERSIONS => 5}
  • 查询表内容

1.scan,对全表进行扫描,慎用,数据量越多处理速度越慢

scan '[表名]',例子:scan 't1’
查询特定版本的内容:scan 't2',{RAW=>true,VERSIONS=>10}

  1. get

get '[表名]','[rowkey]','[列族:字段名]',例子:get ‘t1’,‘00101’,‘cf1:name’

  • 删除表
  • 删除表需要首先屏蔽表,disable '[表名]'
  • 删除该表drop '[表名]'

hbase优化

  • 1 .表设计
    表设计不好会造成的情况 : rowkey设计不合理 , 数据倾斜造成查询效率低 .

    • Pre-Creating Regions 预分区
      我们创建完一张表后 , 向这张表写数据的时候 , 每次都向一个Region里写 , 当这个Region足够大的时候会裂变 , 这种情况会造成一直往一台服务器写,造成这台服务器负载特别高 . 如果我们提前预判到数据量特别大的时候 , 我们提前创建一些空的regions , 当数据写入hbase的时候 ,会按照region分区的情况 , 在集群内左数据的负载均衡 .
    • Row Key
      rowkey检索表中的数据 , 支持三种方式:
      • 通过单个rowkey访问 , 按照某一个rowkey的键值进行get
      • 通过rowkey的range进行scan : 即通过设置startRowkey和endRowKey , 在范围内进行扫描 , 常用
      • 全表扫描 , 即扫描整张表的数据 , 比较慢 , 不推荐
        我们在hbase中 , rowkey是一个任意字符串 , 最大长度64K , 实际在应用中我们把rowkey设置在10-100个字节就好 , 存在byte字节数组中 , 一般设计为定长的 , 在满足需求的情况下尽可能缩短rowkey的长度可以提高检索的速度 .
        rowkey是字典排序的 , 将经常可能操作的数据存储到一块儿 . 可能访问到的数据可以考虑用时间戳作为rowkey一部分 , Long.MAX_VALUE - 时间戳作为rowkey , 能保证新写入的数据被读取时快速搜索到 .
        rowkey设计的规则: 1.越小越好 . 2. Rowkey的设计按照具体的业务 . 3. 散列性:取反: 001->100 , 002-.200 ,哈希.
    • Column Family 列族
      不要再一张表定义太多的列族 , 列族不要超过2个3个, 列族就是store , 一个store的memStore进行溢写的时候会带动同一个region下其他sstore的memstore的溢写 , 不管有没有溢写需求 , 这样会造成很多的小文件 .
    • In Memory
      创建表的时候 , 通过HColumnDescriptor.setInMemory(true)将表放到RegionServer的缓冲中 , 保证读取的时候被cache命中 .
    • Max Version
      创建表的时候 , 通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版本 , 只保存最新的数据 ,设置setMaxVersions(1)就可以了 .
    • Time to Live
    • 创建表的时候, 通过HColumnDesctiptor.setTimeToLive(int timeToLive)设置表中数据的存储生命周期 , 例如数据只存储两天setTimeToLive(2*24*60*60)
    • 1.7 Compact & Split
      (在合适的时间进行大合并)
      在HBase中,数据在更新时首先写入WAL 日志(HLog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时, 系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minor compact)。
      StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile。
      由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。
      实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。
      hbase为了防止小文件(被刷到磁盘的menstore)过多,以保证保证查询效率,hbase需要在必要的时候将这些小的store file合并成相对较大的store file,这个过程就称之为compaction。在hbase中,主要存在两种类型的compaction:minor compaction和major compaction。
      minor compaction:的是较小、很少文件的合并。
      major compaction 的功能是将所有的store file合并成一个,触发major compaction的可能条件有:major_compact 命令、majorCompact() API、region server自动运行(相关参数:hbase.hregion.majoucompaction 默认为24 小时、hbase.hregion.majorcompaction.jetter 默认值为0.2 防止region server 在同一时间进行major compaction)。
      hbase.hregion.majorcompaction.jetter参数的作用是:对参数hbase.hregion.majoucompaction 规定的值起到浮动的作用,假如两个参数都为默认值24和0,2,那么major compact最终使用的数值为:19.2~28.8 这个范围。
      1、 关闭自动major compaction
      2、 手动编程major compaction
      Timer类,contab
      minor compaction的运行机制要复杂一些,它由一下几个参数共同决定:
      hbase.hstore.compaction.min :默认值为 3,表示至少需要三个满足条件的store file时,minor compaction才会启动
      hbase.hstore.compaction.max 默认值为10,表示一次minor compaction中最多选取10个store file
      hbase.hstore.compaction.min.size 表示文件大小小于该值的store file 一定会加入到minor compaction的store file中
      hbase.hstore.compaction.max.size 表示文件大小大于该值的store file 一定会被minor compaction排除
      hbase.hstore.compaction.ratio 将store file 按照文件年龄排序(older to younger),minor compaction总是从older store file开始选择

  • 2 . 写表操作

  • 多个HTable并发写 , 创建多个HTable客户端进行写操作 ,提高写数据的吞吐量 , 但我们实际很少采用这样的方式:

static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;
wTableLog = new HTable[tableN];
for (int i = 0; i < tableN; i++) {
    wTableLog[i] = new HTable(conf, table_log_name);
    wTableLog[i].setWriteBufferSize(5 * 1024 * 1024); //5MB
    wTableLog[i].setAutoFlush(false);
}
  • Htable参数设置
    • Auto Flush : 通过调用HTable.setAutoFlush(false)方法将HTable写客户端的自动flush关闭, 这样可以避免一条put就执行一次的情况发生 , 只有当put填满客户端写缓存(memstore)时 , 才把这一批put一起向hbase服务端发起请求 , 默认情况下是开启自动flush的
    • Write Buffer : 通过Htable.setWriteBufferSize(writeBufferSize)方法设置HTable客户端的写buffer大小(写入操作时缓冲区的大小) , 如果新设置的buffer小于当前写buffer中的数据时 , buffer将会被flush到服务端(数据超过我们设置的值时才flush) . 其中 , writeBufferSize的单位是byte字节数 , 我们需要根据实际的写入数据量来设置这个值 . 读缓冲时block65535 ,64k ,一般情况下我们一次IO的数据量也是64k
    • WAL Flag(HLOG) : 关闭写HLOG(WAL Flag)日志 . 慎重使用 , 要可以接受丢失数据的情况才允许使用 . 我们前面在架构的时候提过 , 客户端提交数据给regionserver的region之前 , 会先写Hlog日志保存操作和数据 , 写入成功之后再写memstore缓存 , 如果省略写Hlog日志这个步骤理所应当是会提高速度的 . 但是我们一般只会对不重要的数据在put和delete的操作的时候,才会调用Put.setWriteToWAL(false)或者Delete.setWriteToWAL(false) 来放弃写WAL日志 .
    • 批量写 : 我们在put的时候 , 可以HTable.put(Put) , 也可以HTable.put(List<Put>) , 将制定的RowKey列表,批量写入多行记录 , 批量执行 , 一次网络IO即可 , 对于数据的实时性要求高 , 网络传输RTT高的情况下会带来明显的性能提升 .
    • 多线程并发写 : 在客户端开启多个HTable写线程 , 每个线程负责一个HTable对象的flush操作 , 这样结合定时flush和写buffer(WriteBufferSize) , 可以保证在数据量小的时候 , 数据可以在短时间内被flush的同时 , 也保证数据量打的时候 ,写buffer一满就即是flush .
for (int i = 0; i < threadN; i++) {
    Thread th = new Thread() {
        public void run() {
            while (true) {
                try {
                    sleep(1000); //1 second
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
synchronized (wTableLog[i]) {
                    try {
                        wTableLog[i].flushCommits();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
}
    };
    th.setDaemon(true);
    th.start();
}

  • 3 . 读表操作
  • 创建多个HTable客户端用于读操作, 提高读数据的吞吐量
static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;
rTableLog = new HTable[tableN];
for (int i = 0; i < tableN; i++) {
    rTableLog[i] = new HTable(conf, table_log_name);
    rTableLog[i].setScannerCaching(50);
}
  • HTable参数设置
    • Scanner Caching 扫描缓存
      hbase.client.scanner.caching配置项可以设置Hbase scanner一次从服务端抓取的数据条数 , 默认一次一条 . 通过将其设置成一个合理的值 . 可以减少scan过程里next()的时间开销 , 但是代价是scanner需要通过客户端的内存来维持这些被cache的行记录 .
      scanner Caching的配置:
      • 在hbase的conf配置文件中配置(所有的查询都会被设置 , 不太好)
      • 通过调用HTable.setScannerCaching(int scannerCaching)进行配置 . (常用的)
      • 通过调用Scan.setCaching(int caching)进行配置 .
    • Scan Attribute Selection
      scan时指定需要的Column Family(列族) , 可以减少网络传输数据量 , 否则默认scan操作会返回整行所有column family数据 .
    • Close ResultScanner
      通过scan取完数据后 , 记得关闭ResultScanner , 否则regionserver可能会出现问题(对应的server资源没办法释放)
  • 批量读
    类似批量写 , HTable.get(list)
  • 多线程并发读
    类似多线程写
public class DataReaderServer {
     //获取店铺一天内各分钟PV值的入口函数
     public static ConcurrentHashMap<String, String> getUnitMinutePV(long uid, long startStamp, long endStamp){
         long min = startStamp;
         int count = (int)((endStamp - startStamp) / (60*1000));
         List<String> lst = new ArrayList<String>();
         for (int i = 0; i <= count; i++) {
            min = startStamp + i * 60 * 1000;
            lst.add(uid + "_" + min);
         }
         return parallelBatchMinutePV(lst);
     }
      //多线程并发查询,获取分钟PV值
private static ConcurrentHashMap<String, String> parallelBatchMinutePV(List<String> lstKeys){
        ConcurrentHashMap<String, String> hashRet = new ConcurrentHashMap<String, String>();
        int parallel = 3;
        List<List<String>> lstBatchKeys  = null;
        if (lstKeys.size() < parallel ){
            lstBatchKeys  = new ArrayList<List<String>>(1);
            lstBatchKeys.add(lstKeys);
        }
        else{
            lstBatchKeys  = new ArrayList<List<String>>(parallel);
            for(int i = 0; i < parallel; i++  ){
                List<String> lst = new ArrayList<String>();
                lstBatchKeys.add(lst);
            }

            for(int i = 0 ; i < lstKeys.size() ; i ++ ){
                lstBatchKeys.get(i%parallel).add(lstKeys.get(i));
            }
        }
        
        List<Future< ConcurrentHashMap<String, String> >> futures = new ArrayList<Future< ConcurrentHashMap<String, String> >>(5);
        
        ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
        builder.setNameFormat("ParallelBatchQuery");
        ThreadFactory factory = builder.build();
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(lstBatchKeys.size(), factory);
        
        for(List<String> keys : lstBatchKeys){
            Callable< ConcurrentHashMap<String, String> > callable = new BatchMinutePVCallable(keys);
            FutureTask< ConcurrentHashMap<String, String> > future = (FutureTask< ConcurrentHashMap<String, String> >) executor.submit(callable);
            futures.add(future);
        }
        executor.shutdown();
        
        // Wait for all the tasks to finish
        try {
          boolean stillRunning = !executor.awaitTermination(
              5000000, TimeUnit.MILLISECONDS);
          if (stillRunning) {
            try {
                executor.shutdownNow();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
          }
        } catch (InterruptedException e) {
          try {
              Thread.currentThread().interrupt();
          } catch (Exception e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
          }
        }
        
        // Look for any exception
        for (Future f : futures) {
          try {
              if(f.get() != null)
              {
                  hashRet.putAll((ConcurrentHashMap<String, String>)f.get());
              }
          } catch (InterruptedException e) {
            try {
                 Thread.currentThread().interrupt();
            } catch (Exception e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
          } catch (ExecutionException e) {
            e.printStackTrace();
          }
        }
        
        return hashRet;
    }
     //一个线程批量查询,获取分钟PV值
    protected static ConcurrentHashMap<String, String> getBatchMinutePV(List<String> lstKeys){
        ConcurrentHashMap<String, String> hashRet = null;
        List<Get> lstGet = new ArrayList<Get>();
        String[] splitValue = null;
        for (String s : lstKeys) {
            splitValue = s.split("_");
            long uid = Long.parseLong(splitValue[0]);
            long min = Long.parseLong(splitValue[1]);
            byte[] key = new byte[16];
            Bytes.putLong(key, 0, uid);
            Bytes.putLong(key, 8, min);
            Get g = new Get(key);
            g.addFamily(fp);
            lstGet.add(g);
        }
        Result[] res = null;
        try {
            res = tableMinutePV[rand.nextInt(tableN)].get(lstGet);
        } catch (IOException e1) {
            logger.error("tableMinutePV exception, e=" + e1.getStackTrace());
        }

        if (res != null && res.length > 0) {
            hashRet = new ConcurrentHashMap<String, String>(res.length);
            for (Result re : res) {
                if (re != null && !re.isEmpty()) {
                    try {
                        byte[] key = re.getRow();
                        byte[] value = re.getValue(fp, cp);
                        if (key != null && value != null) {
                            hashRet.put(String.valueOf(Bytes.toLong(key,
                                    Bytes.SIZEOF_LONG)), String.valueOf(Bytes
                                    .toLong(value)));
                        }
                    } catch (Exception e2) {
                        logger.error(e2.getStackTrace());
                    }
                }
            }
        }

        return hashRet;
    }
}
//调用接口类,实现Callable接口
class BatchMinutePVCallable implements Callable<ConcurrentHashMap<String, String>>{
     private List<String> keys;

     public BatchMinutePVCallable(List<String> lstKeys ) {
         this.keys = lstKeys;
     }

     public ConcurrentHashMap<String, String> call() throws Exception {
         return DataReadServer.getBatchMinutePV(keys);
     }
}

  • 缓存查询结果
    频繁查询hbase的场景 , 可以考虑自己做缓存 , 例如redis.
  • Blockcache
    hbase上regionserver的内存分两部分 , 一部分memstore , 用来写 , 还有一部分是blockCache, 用来读.
    写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64MB以后,会启动 flush刷新到磁盘。当Memstore的总大小超过限制时(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。
    读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache达到上限(heapsize * hfile.block.cache.size * 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。
    一个Regionserver上有一个BlockCache和N个Memstore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而Memstore为0.4。对于注重读响应时间的系统,可以将 BlockCache设大些,比如设置BlockCache=0.4,Memstore=0.39,以加大缓存的命中率。
  • HTable和HTablePool使用注意事项
    HTable和HTablePool都是HBase客户端API的一部分,可以使用它们对HBase表进行CRUD操作。下面结合在项目中的应用情况,对二者使用过程中的注意事项做一下概括总结。
Configuration conf = HBaseConfiguration.create();
try (Connection connection = ConnectionFactory.createConnection(conf)) {
  try (Table table = connection.getTable(TableName.valueOf(tablename)) {
    // use table as needed, the table returned is lightweight
  }
}

  • HTABLE
    HTable是HBase客户端与HBase服务端通讯的Java API对象,客户端可以通过HTable对象与服务端进行CRUD操作(增删改查)。它的创建很简单:
Configuration conf = HBaseConfiguration.create();
HTable table = new HTable(conf, "tablename");
//TODO CRUD Operation……

HTable使用时的一些注意事项:

  1. 规避HTable对象的创建开销
    因为客户端创建HTable对象后,需要进行一系列的操作:检查.META.表确认指定名称的HBase表是否存在,表是否有效等等,整个时间开销比较重,可能会耗时几秒钟之长,因此最好在程序启动时一次性创建完成需要的HTable对象,如果使用Java API,一般来说是在构造函数中进行创建,程序启动后直接重用。
  2. HTable对象不是线程安全的
    HTable对象对于客户端读写数据来说不是线程安全的,因此多线程时,要为每个线程单独创建复用一个HTable对象,不同对象间不要共享HTable对象使用,特别是在客户端auto flash被置为false时,由于存在本地write buffer,可能导致数据不一致。
  3. HTable对象之间共享Configuration
    HTable对象共享Configuration对象,这样的好处在于:
    • 共享ZooKeeper的连接:每个客户端需要与ZooKeeper建立连接,查询用户的table regions位置,这些信息可以在连接建立后缓存起来共享使用;
    • 共享公共的资源:客户端需要通过ZooKeeper查找-ROOT-和.META.表,这个需要网络传输开销,客户端缓存这些公共资源后能够减少后续的网络传输开销,加快查找过程速度。
    因此,与以下这种方式相比:
    HTable table1 = new HTable(“table1”);
    HTable table2 = new HTable(“table2”);
    下面的方式更有效些:
    Configuration conf = HBaseConfiguration.create();
    HTable table1 = new HTable(conf, “table1”);
    HTable table2 = new HTable(conf, “table2”);
    备注:即使是高负载的多线程程序,也并没有发现因为共享Configuration而导致的性能问题;如果你的实际情况中不是如此,那么可以尝试不共享Configuration。
  • HTablePool
    HTablePool可以解决HTable存在的线程不安全问题,同时通过维护固定数量的HTable对象,能够在程序运行期间复用这些HTable资源对象。
    Configuration conf = HBaseConfiguration.create();
    HTablePool pool = new HTablePool(conf, 10);
  1. HTablePool可以自动创建HTable对象,而且对客户端来说使用上是完全透明的,可以避免多线程间数据并发修改问题。
  2. HTablePool中的HTable对象之间是公用Configuration连接的,能够可以减少网络开销。
    HTablePool的使用很简单:每次进行操作前,通过HTablePool的getTable方法取得一个HTable对象,然后进行put/get/scan/delete等操作,最后通过HTablePool的putTable方法将HTable对象放回到HTablePool中。
    下面是个使用HTablePool的简单例子:

public void createUser(String username, String firstName, String lastName, String email, String password, String roles) throws IOException {
  HTable table = rm.getTable(UserTable.NAME);
  Put put = new Put(Bytes.toBytes(username));
  put.add(UserTable.DATA_FAMILY, UserTable.FIRSTNAME,
  Bytes.toBytes(firstName));
  put.add(UserTable.DATA_FAMILY, UserTable.LASTNAME,
    Bytes.toBytes(lastName));
  put.add(UserTable.DATA_FAMILY, UserTable.EMAIL, Bytes.toBytes(email));
  put.add(UserTable.DATA_FAMILY, UserTable.CREDENTIALS,
    Bytes.toBytes(password));
  put.add(UserTable.DATA_FAMILY, UserTable.ROLES, Bytes.toBytes(roles));
  table.put(put);
  table.flushCommits();
  rm.putTable(table);
}

Hbase和DBMS比较:
查询数据不灵活:
1、 不能使用column之间过滤查询
2、 不支持全文索引。使用solr和hbase整合完成全文搜索。
a) 使用MR批量读取hbase中的数据,在solr里面建立索引(no store)之保存rowkey的值。
b) 根据关键词从索引中搜索到rowkey(分页)
c) 根据rowkey从hbase查询所有数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值