HBASE简介(一)

 

HBASE简介(一)

关系型数据库的不足

不擅长的处理:

1. 大量数据的写入处理

2. 为有数据更新的表做索引或表结构(schema)变更

3. 字段不固定时应用

4. 对简单查询需要快速返回结果的处理

 

NoSQL数据库

NoSQL数据库通常不支持Join处理,各个数据都是独立设计的,很容易把数据分散在多个服务器上,故减少了每个服务器上的数据量,即使要处理大量数据的写入,也变得更加容易,数据的读入操作当然也同样容易。

 

典型的NoSQL数据库

 

临时性键值存储(memcached、Redis)、永久性键值存储(ROMA、Redis)、面向文档的数据库(MongoDB、CouchDB)、面向列的数据库(Cassandra、HBase)

 

HBASE定义

Apache HBase是Hadoop数据库,是一个分布式,可扩展的大数据存储。

    当您需要随机,实时读取/写入您的大数据时可以使用Apache HBase。 该项目的目标是在集群上托管非常大的表 - 数十亿行×数百万列。 Apache HBase是一个开源的,分布式的,版本化的非关系数据库,其模型是由Chang等人在Google的Bigtable之后建模的。Bigtable是一种用于结构化数据的分布式存储系统。 就像Bigtable利用Google文件系统提供的分布式数据存储一样,Apache HBase在Hadoop和HDFS之上提供了类似的功能。

特点

HBASE有以下特点:

  1. 线性和模块化的可扩展性
  2. 严格一致性的读取和写入
  3. 自动和可配置的表分片
  4. 区域服务器之间的自动容灾
  5. 为使用Apache HBase表支持Hadoop MapReduce作业提供方便的基础类
  6. 易于使用Java API进行客户端访问
  7. 提供块缓存和布隆过滤器进行实时查询
  8. 通过服务器端过滤器实现查询预测
  9. Thrift网关和支持XML的REST-ful Web service,Protobuf和二进制数据编码
  10. 支持基于jruby(JIRB)的脚本
  11. 监控支持Ganglia可视化和导出到文件

HBASE和HDFS

HDFSHBASE
分布式的文件存储系统j建立在HDFS之上的数据库
HDFS不支持快速单个的记录查询支持快速单个的记录查询
支持高延迟的批处理单行访问低延迟
顺序访问内部采用hash-tables和随机访问,存储的数据索引在HDFS上

HBASE数据结构

HBase 表结构

HBase以表的形式存储数据。表有行和列组成。列划分为若干个列族/列簇(column family)。如下图所示:

具体名字包括:

Row Key(行键)

与nosql数据库们类似,row key是用来检索记录的主键。访问hbase table中的行,包括三种方式:

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


Rowkey行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。
存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。

column family(列族)

hbase表中的每个列,都归属与某个列族。列族是表的chema的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。
访问控制、磁盘和内存的使用统计都是在列族层面进行的。
实际应用中,列族上的控制权限能帮助我们管理不同类型的应用:
我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)。

Cell

HBase中通过row和columns确定的为一个存贮单元称为cell。由{row key, column( =<family> + <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。

timestamp

每个cell都保存着同一份数据的多个版本。版本通过时间戳来索引。

 为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。

HBASE存储结构

HBase的部署架构通常如下图所示:

HBase存储结构如下图所示:

  1. Client:

    • Client 包含访问HBase的接口,维护cache加快对HBase的访问
  2. HMaster:

    • 为 HRegion 分配HRegion Server;
    • 负责 HRegion Server的负载均衡;
    • 发现失效的HRegion Server,并重新分配其上的Region;
    • 处理Schema的更新处理请求
    • 管理功能:提供创建、删除、更新表的接口
  3. Zookeeper:

    • Zookeeper保证集群中只有一个HMaser;
    • 监控HRegion Server的状态,及时将上线下线的HRegion Server信息通知HMaster;
    • 记录所有HRegion的寻址入口
    • 存储Schema信息,包括有哪些表,表有哪些column family
  4. Region Server

    • 处理region的IO请求(所以Client访问HBase的数据不需要HMaster的参与。从Zookeeper中寻址,然后到HRegion Server上进行读写操作)
    • 对到达阈值的HRegion进行分裂
  5. WAL
    • 即Write Ahead Log, 是HDFS上一个文件,早期版本中称为HLog,用以存储尚未进行持久化的数据。

      所有写操作都会先保证将数据写入这个Log文件后,才会真正更新MemStore,最后写入HFile中。采用这种模式,可以保证HRegionServer宕机后,我们依然可以从该Log文件中读取数据,Replay所有的操作,而不至于数据丢失。

  6. BlockCache

    • BlockCache是一个读缓存。在内存中存放经常读的数据,提升读性能。当缓存满的时候,最近最少使用的数据(Least Recently Used data )被踢出去。
  7. MemStore

    • 是一个写缓存。存储尚未写到磁盘中的数据。在写到磁盘之前,数据是经过排序的。在Region中每个column family对应一个HStore,每个HStore有一个MemStore和0到n个HFile。相同列族的数据存放在一个文件中。
       
  8. Hfiles

  • 在磁盘上,用于存储排序后的数据行(KeyValues.)

读写过程

读取过程:

  1. 集群启动时,HMaster负责将所有的Region分配到每个HRegionServer中。
  2. zookeeper中存储了meta表的region信息,所以先从zookeeper中找到meta表regionServer的位置
  3. 读取meta表中的数据,namespace、表名和rowkey,找到对应region server。
  4. 联系对应的region server,查找对应的region。
  5. 先从MemStore找数据,如果没有,再到StoreFile上读
  6. 如果再次读取,客户端将使用缓存来获取META 的位置及之前的行健。这样时间久了,客户端不需要查询META表,
    除非Region 移动所导致的丢失,这样的话,则将会重新查询更新缓存。
     

写入过程:

  1. 集群启动时,HMaster负责将所有的Region分配到每个HRegionServer中。
  2. zookeeper中存储了meta表的region信息,所以先从zookeeper中找到meta表regionServer的位置
  3. 读取meta表中的数据,namespace、表名和rowkey,找到对应region server。
  4. 联系对应的region server,查找对应的region,把数据先后写入到WAL和MemStore中。MemStore达到一个阈值后则把数据刷成一个StoreFile文件。若MemStore中的数据有丢失,则可以总HLog上恢复
  5. 当多个StoreFile文件达到一定的大小后,会触发Compact合并操作,合并为一个StoreFile,这里同时进行版本的合并和数据删除。
    当Compact后,逐步形成越来越大的StoreFIle后,会触发Split操作,把当前的StoreFile分成两个,这里相当于把一个大的region分割成两个region。

cache机制

HBase上RegionServer的cache主要分为两个部分,分别是memstore&blockcache,其中memstore主要用于写缓存,而blockcache用于读缓存。

当数据写入hbase时,会先写入memstore,RegionServer会给每个region提供一个memstore,memstore中的数据达到系统设置的水位值后,会触发flush将memstore中的数据刷写到磁盘。

客户的读请求会先到memstore中查数据,若查不到就到blockcache中查,再查不到就会从磁盘上读,并把读入的数据同时放入blockcahce。

一个RegionServer上有一个BlockCache和N个Memstore。Cache在HBase中所处的位置如下图中所示:

 

集群的高可用

Hbase集群的高可用通过zookeeper维护:

Zookeeper一般在分布式系统中的成员之间协调共享的状态信息,Region Server和活跃的HMaster通过会话连接到Zookeeper,ZooKeeper维护短暂的阶段,通过心跳机制用于活跃的会话。
每个Region Server创建一个短暂的节点,HMaster监控这些节点发现可用的Region Server,同时HMaster 也监控这些节点的服务器故障。HMaster 通过创建一个临时的节点,Zookeeper决定其中一个HMaster作为活跃的。活跃的HMaster 给ZooKeeper发送心跳信息,不活跃的HMaster在活跃的HMaster出现故障时,接受通知。
如果一个Region Server或是一个活跃的HMaster在发送心跳信息时失败或是出现了故障,则会话过期,相应的临时节点将被删除,监听器将因这些删除的节点更新通知信息,活跃的HMaster将监听Region Server,并且将会恢复出现故障的Region Server,不活跃的HMaster 监听活跃的HMaster故障,如果一个活跃的HMaster出现故障,则不活跃的HMaster将会变得活跃。

 

存储目录结构:

  • 表数据的存储目录结构构成:hdfs://{域名}/hbase/data/${名字空间}/${表名}/${区域名称}/${列族名称}/${文件名}
  • WAL目录结构构成:hdfs://{域名}/hbase/WALs/${区域服务器名称,主机名,端口号,时间戳}/

HBASE客户端操作

 

HBase shell操作

hbase shell操作如下

  • hbase shell: 进入shell
  • help:帮助,常见命令和组:
    1. Group name: general(常规组)
      Commands: status, table_help, version, whoami
    2. Group name: ddl(数据定义语言组)
      Commands: alter, alter_async, alter_status, create, describe, disable, disable_all, drop, drop_all, enable, enable_all, exists, get_table, is_disabled, is_enabled, list, locate_region, show_filters
    3. Group name: namespace(命名空间组)
      Commands: alter_namespace, create_namespace, describe_namespace, drop_namespace, list_namespace, list_namespace_tables
    4. Group name: dml(数据操作语言组)
      Commands: append, count, delete, deleteall, get, get_counter, get_splits, incr, put, scan, truncate, truncate_preserve
    5. Group name: tools
      Commands: assign, balance_switch, balancer, balancer_enabled, catalogjanitor_enabled, catalogjanitor_run, catalogjanitor_switch, close_region, compact, compact_rs, flush, major_compact, merge_region, move, normalize, normalizer_enabled, normalizer_switch, split, trace, unassign, wal_roll, zk_dump
    6. Group name: replication
      Commands: add_peer, append_peer_tableCFs, disable_peer, disable_table_replication, enable_peer, enable_table_replication, list_peers, list_replicated_tables, remove_peer, remove_peer_tableCFs, set_peer_tableCFs, show_peer_tableCFs
    7. Group name: snapshots
      Commands: clone_snapshot, delete_all_snapshot, delete_snapshot, list_snapshots, restore_snapshot, snapshot
    8. Group name: configuration
      Commands: update_all_config, update_config
    9. Group name: quotas
      Commands: list_quotas, set_quota
    10. Group name: security
      Commands: grant, list_security_capabilities, revoke, user_permission
    11. Group name: procedures
      Commands: abort_procedure, list_procedures
    12. Group name: visibility labels
      Commands: add_labels, clear_auths, get_auths, list_labels, set_auths, set_visibility

 

基本api操作

创建命名空间和表

用户需要首先创建命名空间和表,shell命令行:

hbase(main):024:0> create_namespace 'ns1'
hbase(main):025:0> create 'ns1:t1','f1'

 

java api代码示例:

@Test
public void createNameSpace() throws Exception {
    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    Admin admin = conn.getAdmin();
    //创建名字空间描述符
    NamespaceDescriptor nsd = NamespaceDescriptor.create("ns2").build();
    admin.createNamespace(nsd);

    NamespaceDescriptor[] ns = admin.listNamespaceDescriptors();
    for (NamespaceDescriptor n : ns) {
        System.out.println(n.getName());
    }
}

@Test
public void listNameSpaces() throws Exception {
    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    Admin admin = conn.getAdmin();

    NamespaceDescriptor[] ns = admin.listNamespaceDescriptors();
    for (NamespaceDescriptor n : ns) {
        System.out.println(n.getName());
    }
}

@Test
public void createTable() throws Exception {
    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    Admin admin = conn.getAdmin();
    //创建表名对象
    TableName tableName = TableName.valueOf("ns2:t2");
    //创建表描述符对象
    HTableDescriptor tbl = new HTableDescriptor(tableName);
    //创建列族描述符
    HColumnDescriptor col = new HColumnDescriptor("f1");
    tbl.addFamily(col);

    admin.createTable(tbl);
    System.out.println("over");
}

 

 

put操作

将数据插入表中,shell命令行:

hbase(main):016:0> put 'ns1:t1','row1','f1:id',100 

 

java api代码示例:

@Test
public void put() throws Exception {
    //创建conf对象
    Configuration conf = HBaseConfiguration.create();
    //通过连接工厂创建连接对象
    Connection conn = ConnectionFactory.createConnection(conf);
    //通过连接查询tableName对象
    TableName tname = TableName.valueOf("ns1:t1");
    //获得table
    Table table = conn.getTable(tname);

    //通过bytes工具类创建字节数组(将字符串)
    byte[] rowid = Bytes.toBytes("row1");

    //创建put对象
    Put put = new Put(rowid);

    byte[] f1 = Bytes.toBytes("f1");
    byte[] id = Bytes.toBytes("name");
    byte[] value = Bytes.toBytes("sam");
    put.addColumn(f1, id, value);

    //执行插入
    table.put(put);
}

 

get操作

从表中获取数据,shell命令行:

hbase(main):017:0> get 'ns1:t1','row1'

 

java api代码示例:

@Test
public void get() throws Exception {
    //创建conf对象
    Configuration conf = HBaseConfiguration.create();
    //通过连接工厂创建连接对象
    Connection conn = ConnectionFactory.createConnection(conf);
    //通过连接查询tableName对象
    TableName tname = TableName.valueOf("ns1:t1");
    //获得table
    Table table = conn.getTable(tname);

    //通过bytes工具类创建字节数组(将字符串)
    byte[] rowid = Bytes.toBytes("row1");
    Get get = new Get(Bytes.toBytes("row1"));
    Result r = table.get(get);
    byte[] idvalue = r.getValue(Bytes.toBytes("f1"), Bytes.toBytes("id"));
    System.out.println(Bytes.toInt(idvalue));
}

delete操作

删除数据,对应时间戳之前的标记为删除,shell命令行:

$hbase>delete 'nd1:t3','row1','f1:name',148989875645

 

java api代码示例:

@Test
public void deleteData() throws IOException {
    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    TableName tname = TableName.valueOf("ns1:t1");

    Table table = conn.getTable(tname);
    Delete del = new Delete(Bytes.toBytes("row0001"));
    del.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("id"));
    del.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("name"));
    table.delete(del);
    System.out.println("over");
}

TTL

设置存活时间:

$hbase>create 'ns1:tx' , {NAME=>'f1',TTL=>10,VERSIONS}


删除表操作

删除表前需要先将表置微不可用后才能删除

hbase(main):020:0> disable 'ns1:t1'
hbase(main):021:0> drop 'ns1:t1'

 

java api代码示例:

@Test
public void disableTable() throws Exception {
    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    Admin admin = conn.getAdmin();
    //禁用表 enable(...) disableTable(...)
    admin.deleteTable(TableName.valueOf("ns2:t2"));
}

 

scan操作

从表中获取数据,shell命令行:

hbase(main):018:0> scan 'ns1:t1'
hbase(main):026:0> scan 'hbase:meta'

 

@Test
public void getScanCache() throws IOException {
    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    TableName tname = TableName.valueOf("ns1:t1");
    Scan scan = new Scan();
 //   scan.setCaching(5000);
    Table t = conn.getTable(tname);
    ResultScanner rs = t.getScanner(scan);
    long start = System.currentTimeMillis() ;
    Iterator<Result> it = rs.iterator();
     while(it.hasNext()){
         Result r = it.next();
         System.out.println(r.getColumnLatestCell(Bytes.toBytes("f1"), Bytes.toBytes("name")));
    }
    System.out.println(System.currentTimeMillis() - start);
}
 
@Test
public void bigInsert() throws Exception {

    DecimalFormat format = new DecimalFormat();
    format.applyPattern("0000");

    long start = System.currentTimeMillis();
    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    TableName tname = TableName.valueOf("ns1:t1");
    HTable table = (HTable) conn.getTable(tname);
    //不要自动清理缓冲区
    table.setAutoFlushTo(false);

    for (int i = 1; i < 10000; i++) {
        Put put = new Put(Bytes.toBytes("row" + format.format(i)));
        //关闭写前日志
        put.setWriteToWAL(false);
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("id"), Bytes.toBytes(i));
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("name"), Bytes.toBytes("tom" + i));
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("age"), Bytes.toBytes(i % 100));
        table.put(put);

        if (i % 2000 == 0) {
            table.flushCommits();
        }
    }
    //
    table.flushCommits();
    System.out.println(System.currentTimeMillis() - start);
}

 
 @Test
    public void scan() throws IOException {
        Configuration conf = HBaseConfiguration.create();
        Connection conn = ConnectionFactory.createConnection(conf);
        TableName tname = TableName.valueOf("ns1:t1");
        Table table = conn.getTable(tname);
        Scan scan = new Scan();
        scan.setCaching(1000);
        scan.setStartRow(Bytes.toBytes("row5000"));
        scan.setStopRow(Bytes.toBytes("row8000"));
        ResultScanner rs = table.getScanner(scan);
        StopWatch watch = new StopWatch();
        watch.start();
        Iterator<Result> it = rs.iterator();
        while (it.hasNext()) {
            Result r = it.next();
            byte[] name = r.getValue(Bytes.toBytes("f1"), Bytes.toBytes("name"));
//            System.out.println(Bytes.toString(name));
        }
        watch.stop();
        System.out.println("处理时间(ms):" + watch.getTime());
    }

高级用法

缓存和批处理

为降低客户端与服务器端的请求次数,提高查询效率可使用缓存和批处理。开启缓存可以使一次RPC请求获取多行数据。缓存可以在表层面和当前扫描实例层面做。

如果行数据量很大,这样对内存的占用很大,可以使用批处理,批量可以控制每次next函数返回的列的个数。

java代码示例如下:

 

/**
 * 测试缓存和批处理
 */
@Test
public void testBatchAndCaching() throws IOException {

    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    TableName tname = TableName.valueOf("ns1:t7");
    Scan scan = new Scan();
    scan.setCaching(2);
    scan.setBatch(4);
    Table t = conn.getTable(tname);
    ResultScanner rs = t.getScanner(scan);
    Iterator<Result> it = rs.iterator();
    while (it.hasNext()) {
        Result r = it.next();
        System.out.println("========================================");
        //得到一行的所有map,key=f1,value=Map<Col,Map<Timestamp,value>>
        NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> map = r.getMap();
        //
        for (Map.Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> entry : map.entrySet()) {
            //得到列族
            String f = Bytes.toString(entry.getKey());
            Map<byte[], NavigableMap<Long, byte[]>> colDataMap = entry.getValue();
            for (Map.Entry<byte[], NavigableMap<Long, byte[]>> ets : colDataMap.entrySet()) {
                String c = Bytes.toString(ets.getKey());
                Map<Long, byte[]> tsValueMap = ets.getValue();
                for (Map.Entry<Long, byte[]> e : tsValueMap.entrySet()) {
                    Long ts = e.getKey();
                    String value = Bytes.toString(e.getValue());
                    System.out.print(f + "/" + c + "/" + ts + "=" + value + ",");
                }
            }
        }
        System.out.println();
    }
}

 

过滤器

还可以通过在客户端增加过滤器实现提高查询表数据的效果,hbase提供了多种类型的过滤器。

常用的过滤器如下:

  • RowFilter
  • FamilyFilter
  • QualifierFilter
  • ValueFilter
  • SingleColumValueFilter
  • ...

如下

select * from t7 where ((age <= 13) and (name like '%t')
or (age > 13) and (name like 't%'))

java 实现如下:

 

@Test
public void testComboFilter() throws IOException {

    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    TableName tname = TableName.valueOf("ns1:t5");
    Scan scan = new Scan();

    //where ... f2:age <= 13
    SingleColumnValueFilter ftl = new SingleColumnValueFilter(
            Bytes.toBytes("f2"),
            Bytes.toBytes("age"),
            CompareFilter.CompareOp.LESS_OR_EQUAL,
            new BinaryComparator(Bytes.toBytes("13"))
    );

    //where ... f2:name like %t
    SingleColumnValueFilter ftr = new SingleColumnValueFilter(
            Bytes.toBytes("f2"),
            Bytes.toBytes("name"),
            CompareFilter.CompareOp.EQUAL,
            new RegexStringComparator("^t")
    );
    //ft
    FilterList ft = new FilterList(FilterList.Operator.MUST_PASS_ALL);
    ft.addFilter(ftl);
    ft.addFilter(ftr);

    //where ... f2:age > 13
    SingleColumnValueFilter fbl = new SingleColumnValueFilter(
            Bytes.toBytes("f2"),
            Bytes.toBytes("age"),
            CompareFilter.CompareOp.GREATER,
            new BinaryComparator(Bytes.toBytes("13"))
    );

    //where ... f2:name like %t
    SingleColumnValueFilter fbr = new SingleColumnValueFilter(
            Bytes.toBytes("f2"),
            Bytes.toBytes("name"),
            CompareFilter.CompareOp.EQUAL,
            new RegexStringComparator("t$")
    );
    //ft
    FilterList fb = new FilterList(FilterList.Operator.MUST_PASS_ALL);
    fb.addFilter(fbl);
    fb.addFilter(fbr);


    FilterList fall = new FilterList(FilterList.Operator.MUST_PASS_ONE);
    fall.addFilter(ft);
    fall.addFilter(fb);

    scan.setFilter(fall);
    Table t = conn.getTable(tname);
    ResultScanner rs = t.getScanner(scan);
    Iterator<Result> it = rs.iterator();
    while (it.hasNext()) {
        Result r = it.next();
        byte[] f1id = r.getValue(Bytes.toBytes("f1"), Bytes.toBytes("id"));
        byte[] f2id = r.getValue(Bytes.toBytes("f2"), Bytes.toBytes("id"));
        byte[] f1name = r.getValue(Bytes.toBytes("f1"), Bytes.toBytes("name"));
        byte[] f2name = r.getValue(Bytes.toBytes("f2"), Bytes.toBytes("name"));
        System.out.println(f1id + " : " + f2id + " : " + Bytes.toString(f1name) + " : " + Bytes.toString(f2name));
    }
}


计数器

基于一种类似CAS的原理的原子操作

@Test
public void testIncr() throws IOException {

    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    TableName tname = TableName.valueOf("ns1:t8");
    Table t = conn.getTable(tname);
    Increment incr = new Increment(Bytes.toBytes("row1"));
    incr.addColumn(Bytes.toBytes("f1"),Bytes.toBytes("daily"),1);
    incr.addColumn(Bytes.toBytes("f1"),Bytes.toBytes("weekly"),10);
    incr.addColumn(Bytes.toBytes("f1"),Bytes.toBytes("monthly"),100);
    t.increment(incr);

}


协处理器

在服务器端执行的,类似于关系型数据库的存储过程或者触发器。

  • observer:基于事件的,发生动作时,回调相应方法。
    • RegionObserver: 与表的region相关联,用户可以用这种协处理器处理数据修改事件。
    • MasterObserver: 可以被用做管理或DDL操作,Master节点的相关事件。
    • WALObserver: 处理WAL事件。
  • endpoint: endpoint通过添加一些远程调用来动态拓展RPC协议。

 

事务性操作

事务的并发控制早期有两种策略:

  • rowlock:
    通过rowlock进行锁定,但最新版本将rowlock删除了,主要是影响性能,同时安全性也存在死锁的可能。
  • checkAndPut:
    使用HTable.checkAndPut()等原子性操作。

rowKey设计

  • 热点选取:

HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。
然而糟糕的rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。设计良好的数据访问模式以使集群被充分,均衡的利用。

为了避免写热点,设计rowkey使得不同行在同一个region,但是在更多数据情况下,数据应该被写入集群的多个region,而不是一个。

  • 长度原则:

rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes,以byte[] 形式保存,一般设计成定长。
建议越短越好,不要超过16个字节。

  • 散列原则:

如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,
建议将rowkey的高位采用散列字段处理,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。以防造成热点问题,会降低查询效率。

  • 唯一原则:

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

  • 盐析(salt):

盐析是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。
分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。

  • 哈希:

哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据

  • 反转:

第三种防止热点的方法反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。

 

一个简单话单的业务场景:

1.主叫、被叫、通话时长、通话时间、通话状态

rowkey:

n + callerNumber + callType + timestamp +calledNumber

n = hashcode(callerNumber + date(timestamp)) % 100

 

xx18612345678+0+timestamp(20180727)+0000...

xx18612345678+0+timestamp(20180728)+0000...

java 代码示例如下:

@Test
public void put() throws Exception{
    Configuration conf = HBaseConfiguration.create();
    //通过连接工厂创建连接对象
    Connection conn = ConnectionFactory.createConnection(conf);
    TableName tname = TableName.valueOf("ns1:callRecord");
    Table table = conn.getTable(tname);
    String callerNumber = "18612345678";
    String calledNumber = "18612345678";
    SimpleDateFormat sdf = new SimpleDateFormat();
    sdf.applyPattern("yyyyMMddHHmmss");
    String callTime = sdf.format(new Date());
    //0为主叫1为被叫
    int callType = 0;
    int hash = (callerNumber + callTime).hashCode() % 100;
    DecimalFormat format = new DecimalFormat();
    format.applyPattern("00");
    String regionNo = sdf.format(hash);

    String rowKey = regionNo + "," + callerNumber + "," + callType + "," +
            callTime;
    byte[] rowid = Bytes.toBytes(rowKey);
    Put put = new Put(rowid);
    put.addColumn(Bytes.toBytes("f1"),Bytes.toBytes("status"),Bytes.toBytes("通话成功"));
    put.addColumn(Bytes.toBytes("f1"),Bytes.toBytes("duration"),Bytes.toBytes(100));
    table.put(put);
    System.out.println("success");
}

性能优化

  • 垃圾回收
  • 本地memstore分配缓冲区
  • 压缩
  • 优化拆分和合并
  • 负载均衡

HBase总结

HBase适合场景和不足:

  • HBase适合大量插入同时又有读的情况
  • 具有搞扩展性,即使数据增加也不会降低相应的处理速度(特别是写入速度)
  • HBase的瓶颈是硬盘传输速度,关系型数据库的瓶颈是硬盘寻道时间。
  • HBase很适合寻找按照时间排序top n的场景
  • 只能做简单的Key value查询,复杂的sql统计做不到。
  • 只能在row key上做快速查询

HBase VS ES VS TiDB VS mysql

 MysqlHBaseESTiDB
存储方式行存储列存储索引存储行存储
存储引擎/存储容器innodb/myisamhdfs硬盘、hdfs、s3rocksDB
开发语言C++javajava/lucene

TiDB:go

TiKV:rust

rocksDB:C++

写入能力一般极强一般
查询能力强,但随着数据量的提高有性能瓶颈极强
存储拓展性单机,拓展性较差水平拓展水平拓展水平拓展
集群高可用

不支持集群,支持主从。

master-slave架构主要思路是:

master负责业务的读写请求,

然后通过binlog复制到slave节点,

这样如果主库因为不可抗拒因素无法恢复时,

从库可以提供服务。

Zookeeper一般在分布式系统中的成员之间协调共享的状态信息,

Region Server和活跃的HMaster通过会话连接到Zookeeper,

ZooKeeper维护短暂的阶段,通过心跳机制用于活跃的会话。
每个Region Server创建一个短暂的节点,

HMaster监控这些节点发现可用的Region Server,

同时HMaster 也监控这些节点的服务器故障。

HMaster 通过创建一个临时的节点,Zookeeper决定其中一个HMaster作为活跃的。

活跃的HMaster 给ZooKeeper发送心跳信息,

不活跃的HMaster在活跃的HMaster出现故障时,接受通知。
如果一个Region Server或是一个活跃的HMaster在发送

心跳信息时失败或是出现了故障,则会话过期,

相应的临时节点将被删除,监听器将因这些删除的节点更新通知信息,

活跃的HMaster将监听Region Server,

并且将会恢复出现故障的Region Server,

不活跃的HMaster 监听活跃的HMaster故障,如果一个活跃的HMaster出现故障,则不活跃的HMaster将会变得活跃

一个运行中的 Elasticsearch 实例称为一个 节点,

而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成,

它们共同承担数据和负载的压力。

当有节点加入集群中或者从集群中移除节点时,

集群将会重新平均分布所有的数据。

当一个节点被选举成为主节点时,

它将负责管理集群范围内的所有变更,

例如增加、删除索引,或者增加、删除节点等。

而主节点并不需要涉及到文档级别的变更和搜索等操作,

所以当集群只拥有一个主节点的情况下,

即使流量的增加它也不会成为瓶颈。

任何节点都可以成为主节点。

TiDB Server 节点失效,只会影响该节点上的 session,

应用连接失败重试后可通过前端负载均衡中间件

将请求发送到其他正常的 TiDB Server 节点。
PD 节点失效,若非 Raft leader 节点(PD Cluster 通过

Raft 协议保证自身数据一致性)则无影响,

否则会重新选取 leader,期间该无法对外提供服务(约 3 秒钟)。
TiKV 节点失效,会影响该节点上的所有 region,

若 region 非 Raft leader(TiKV Cluster 也通过 Raft 协议保证节点间的数据一致性),

则服务不受影响,否则服务会中断,待重新选举 leader 后恢复。

如果 PD 确认了失效的 TiKV 节点已经不能恢复,

则会自动将该节点的数据调度至其他正常的 TiKV 节点。

secordary index支持不支持支持支持
事务支持不支持不支持支持
全文索引支持,鸡肋不支持支持支持,鸡肋

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值