Hbase设计原理及架构简介

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fibonacci2015/article/details/83418690

0、传统数据库弊端
    尽管已经有许多数据存储和访问的策略和实现方法,但事实上大多数解决方案,特别是一些关系类型的,在构建时并没有考虑超大规模和分布式的特点。许多开发通过复制和分区的方法来扩充数据库使其突破单个节点的界限,但这些功能通常都是事后增加的,安装和维护都和复杂。同时,也会影响RDBMS的特定功能,例如联接、复杂的查询、触发器、视图和外键约束这些操作在大型的RDBMS上的代价相当高,甚至根本无法实现。

1、简介
    1.1 HBase是一个构建在HDFS上的分布式列存储系统
        1.1.1 存储不仅限于hadoop的HDFS,还包括亚马逊的S3,或者其他的文件存储系统,甚至是自己写一个。
        1.1.2 选择使用HDFS的原因:
            ①与Mapreduce耦合,能够充分利用其并行流式的处理能力。
            ②有较好的扩展性,系统可靠性和自动冗余功能。
            ③利用Mapreduce的并行能力可以执行批量导入数据的功能,最大限度利用磁盘和宽带。
    1.2 HBase是基于Google BigTable模型开发的,典型的key/value系统
    1.3 HBase是Apache Hadoop生态系统中的重要一员,主要用于海量结构化数据存储
    1.4 从逻辑上讲,HBase将数据按照表、行和列进行存储
    1.5 与hadoop一样,HBase目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力

2、HBase的特点
    2.1 面向列
        2.1.1 面向列表(簇)的存储和权限控制,列(簇)独立检索。
        2.1.2 HBase最基本的单元是列(column),一列或多列形成一行,并由唯一的主键(row key)来确定存储。若干列又可以构成一个列族(column family)。
        2.1.3 一个列族的所有列存储在同一个底层的存储文件里,这个存储文件叫做Hfile。
        2.1.4 列族需要在表创建的时候就定义好,并且不能修改的太过频繁,数量也不能太多,当前的HBase版本有少量已知缺陷,使得列族的数量只限于几十,实际情况可能还小的多。但是列没有限制。
    2.2 大:一个表可以有上亿行,上百万列。
    2.3 稀疏:对于为空(NULL)的列,并不占用存储空间,因此,表可以设计的非常稀疏。 张表中不同的行可以有截然不同的列。
    2.4 数据多版本:每个单元中的数据可以有多个版本,默认情况下,版本号自动分配,也可以用户显示设置,版本号就是单元格插入时的时间戳。
        2.4.1 数据存储模式:(Table,Rowkey,Family,Column,TimeStamp) → Value
        2.4.2sortedMap<Rowkey,List<sortedMap<Column,list<Value,Timestamp>>>>
        2.4.3 一列数据

            2.4.4 上面的数据转成表

            尽管这些值插入的次数不同,并且存在多个版本,但是任然能将行看作是所有列以及这些列的最新版本的组合。
    2.5 数据类型单一:HBase中的数据都是字符串,没有类型,底层存储为byte数组。
    2.6 自动分区
            hbase中扩展和负载均衡的基本单位成为region,region的本质上是以行健排序的连续存储区间。如果region太大,系统会把它们动态拆分,相反的,把多个region合并。
            每一个region只能由一台region服务器加载,每一台region服务器合一同时加载多个region。

    2.7 小结
    数十亿行 × 数百万列 × 数千个版本 = TB级或PB级别的存储

3、HBase安装
下篇介绍

4、API操作
    4.1 CRUD(Create,Read,Update,Delete)
        4.1.1 0.99.0版本以前,HBase的主要客户端接口都是 org.apache.hadoop.hbase.client 包中的HTable类提供。
            Configuration conf = HbaseConfiguration.create();
            Htable table = new Htable(conf,"testtable");
        4.1.2 0.99.0版本以后,HBase的Table类由 org.apache.hadoop.hbase.client 包中的 ConnectionFactory获取
            Connection connection = ConnectionFactory.createConnection(config);
            Table table = connection.getTable(TableName.valueOf("testtable"));
    4.2 put方法
         4.2.1 单行put
                void put(Put put) throws IOException
            4.2.1.1 创建put实例
                    1)以单个Put或存储在列表中的一组Put对象作为输入参数,其中put对象由以下几个构造函数创建:
                    put (byte[] row);
                    put (byte[] row, RowLock rowLock);
                    put (byte[] row, long ts);
                    put (byte[] row, long ts, RowLock rowLock);
                    2)创建put实例,需要提供一个rowkey,并且是唯一的行健作为标识。
                    RowLock :行锁,如果用户需要频繁的修改某些行,则必须创建带RowLock的实例来防止其他客户端访问这些行。
                    ts:版本号,时间戳
                    3)为什么要用字节数组byte[]存储?
                    使用这种底层存储类型的目的是,允许存储任意类型的数据,并且可以有效的只存储所需的字节,这保证了最少的内部数据结构开销。另一个原因,每一个字数组都有一个offset参数和一个length参数,它允许用户提交一个已存在的字节数组,并进行效率很高的字节级别的操作。
            4.2.1.2 添加数据
                Put add(byte[] family,byte[] qualifer, byte[] value);
                Put add(byte[] family,byte[] qualifer, long ts, byte[] value);
                Put add(KeyValue kv) throw IOException;
                table.put(put );  // 提交数据
                1)每次调用add()都可以特定的添加一列数据
                2)时间戳可选,如果用不不指定,则由region服务器指定。
                3)KeyValue 实例包含其完整地址(行健,列族,列限定符以及时间戳)和实际数据,是HBase中最底层的类,通过get()方法获取。
            4.2.1.3 检查是否存在特定单元格
                boolean has(byte[] family,byte[] qualifer);
                boolean has(byte[] family,byte[] qualifer,long ts);
                boolean has(byte[] family,byte[] qualifer,byte[] value);
                boolean has(byte[] family,byte[] qualifer,long ts,byte[] value);
            4.2.1.4 数据的版本化
                1)HBase的一个特殊功能是,能为一个单元格存储多个版本的数据。这是通过每个版本使用一个时间戳,并按降序排列存储来实现。
                2)注意:必须保证服务器时间是正确的,并且各个节点的时间一致,定时校验时间。吃过亏!!!
                3)默认情况下,HBase会保留3个版本的数据
            4.2.1.5 客户端的缓冲
                1)每一个put操作实际上都是一个RPC操作,将客户端的数据传送到服务器然后返回,这只适合小数据量的操作。如果应用需要每秒存储上千行数据到hbase,这样处理就不合适了。
                2)HBase 的API配备了一个客户端的写缓冲区(write bugger),缓冲区负责收集put操作,然后调用RPC一次性将put送往服务器。
                3)默认情况,客户端缓冲区是禁止的,可以通过 table.setAutoFlush(false); 来开启。
                4)存储的put实例都保存在客户端的进程内存中,当需要强制把数据写入服务器时,可以调用 void flushCommit() throw IOException
                5)通常不必要手动刷写缓冲区,可以设置缓冲区的大小。超过阈值,就会隐式地调用刷写命令。
                6)开启客户端缓存后,在没commit之前,通过get()查询不到数据,因为还没提交到服务器。
                7)开启客户端缓存后,不能在运行时终止程序,如果发生这种情况,那些尚未尚未刷写的数据就会丢失。
        4.2.2 Put列表
            客户端的API可以插入单个put实例,同时也有批量操作的高级特性。
        4.3.1 创建列表
            List<Put> puts = new ArrayList<Put>();
            table.put(puts);  // 提交数据列表
            1)由于提交的数据列表可能涉及到多行,所以会有部分修改失败。如果远程服务器的put调用出现问题,通过IOException反馈给用户。
            2)服务端出错(向不存在的列族插入数据),正确的put会被插入,错误的会返回,保存在本地写缓冲区。
            3)客户端出错(put没指定column,空的put实例),出错以前的put会插入缓存区,之后的则不会。
            4)基于列表的put调用时,需注意,用户无法控制服务端执行put的顺序,如果要保证写入顺序,则要小心使用这个功能
        4.2.3 原子性操作
            有一种特别的put调用,能保证自身操作的原子性:检查写(check and put),检查成功则执行操作。
            boolean checkAndPut(byte[] row,byte[] family,byte[] qualifier,byte[] value,Put put) throw IOException;
            主要被用于账户结余,状态转换,或数据处理场景
    4.3 Get方法
    4.4 Delete方法

5.shell命令
    1.hbase shell:启动
    2.help:帮助
    3.exit:退出
    4.命令行支持 二进制,八进制,十六进制,必须用双引号,参数用逗号分割
    get 'table1',"key\x00\x6c\x65\x6f\x6e"
    5.创建表
        create table 'testtable',{NAME => 'colfam1',VERSION => 1,TTL => 2592000,BLOCKCACHE => true}
    6.删除表
        先 disable 'testtable'   // 为什么先disable表,因为表的region在hbase启动的时候,会对外提供服务,先要停掉服务,才能删表
        再 drop 'testtable'
    7.查看表结构
        describe 'testtable'
    8.添加数据
        语法:put <table>,<rowkey>,<family:column>,<value>,<timestamp>
                   put 'testtable','rowkey001','f1:col1','value01'
    9.获取数据
        语法:get <table>,<rowkey>,[<family:column>,....]
                   get 'testtable','rowrowkey0011'    // 获取谋行
                   get 'testtable','rowrowkey0011','f1:col1'    // 获取谋行某列
    10.扫描表
        语法:scan <table>, {COLUMNS => [ <family:column>,.... ], LIMIT => num}
                   scan 't1',{LIMIT=>5}    // 扫描表,获取前5行
    11.查询表中行数
        语法:count <table>, {INTERVAL => intervalNum, CACHE => cacheNum}
        INTERVAL设置多少行显示一次及对应的rowkey,默认1000;CACHE每次去取的缓存区大小,默认是10,调整该参数可提高查询速度
        count 'testtable', {INTERVAL => 100, CACHE => 500}
    12.删除行中的某个列值
        语法:delete <table>, <rowkey>,  <family:column> , <timestamp>,必须指定列名
                   delete 'testtable','rowkey001','f1:col1'
    13.删除行
        语法:deleteall <table>, <rowkey>,  <family:column> , <timestamp>,可以不指定列名,删除整行数据
                   deleteall 'testtable','rowkey001'
    14.删除所有数据
        语法:truncate <table>
                   truncate 'testtable'
    15.带修饰词
        scan ‘testtable′, {FILTER => “(PrefixFilter (‘row2′) AND (QualifierFilter (>=, ‘binary:xyz'))) AND (TimestampsFilter ( 123, 456))”}
        谁的值=fib
        scan 'testtable', {FILTER=>"ValueFilter(=,'binary:fib')"}
        谁的值包含fib
        scan 'testtable', {FILTER=>"ValueFilter(=,'substring:fib')"}
        rowkey从rowkey001开始,rowkey包含rowkey0
        scan 'test1', {STARTROW=>'rowkey001', FILTER => "PrefixFilter ('rowkey0')"}

6、架构
    6.1 数据存储
    HBase上的数据是以StoreFile(HFile)二进制流的形式存储在HDFS上block块儿中;但是HDFS并不知道的hbase存的是什么,它只把存储文件是为二进制文件,也就是说,hbase的存储数据对于HDFS文件系统是透明的

        6.1.1 Table中所有行都按照row key的字典序排列;
        6.1.2 Table在行的方向上分割为多个Region;
        6.1.3 Region按大小分割的,每个表开始只有一个region,随着数据增多,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region,之后会有越来越多的region;
        6.1.4 Region是HBase中分布式存储和负载均衡的最小单元,不同Region分布到不同RegionServer上。

        6.1.5 Region虽然是分布式存储的最小单元,但并不是存储的最小单元。Region由一个或者多个Store组成,每个store保存一个columns family;每个Strore又由一个memStore和0至多个StoreFile组成,StoreFile包含HFile;memStore存储在内存中,StoreFile存储在HDFS上。

        6.1.6 每个region由以下信息标识:
            1.< 表名,startRowkey,创建时间>
            2.由目录表(-ROOT-和.META.)记录该region的endRowkey
    6.2 系统架构

        6.2.1 基本组件说明
        1)Client
            包含访问HBase的接口,并维护cache来加快对HBase的访问,比如region的位置信息
        2)HMaster
            为Region server分配region
            负责Region server的负载均衡
            发现失效的Region server并重新分配其上的region
            管理用户对table的增删改查操作
            HDFS上的垃圾文件回收
        3)HRegion Server
            Regionserver维护region,处理对这些region的IO请求
            Regionserver负责切分在运行过程中变得过大的region
        4)Zookeeper作用
            通过选举,保证任何时候,集群中只有一个master,Master与RegionServers 启动时会向ZooKeeper注册
            存贮所有Region的寻址入口
            实时监控Region server的上线和下线信息。并实时通知给Master
            存储HBase的schema和table元数据
            默认情况下,HBase 管理ZooKeeper 实例,比如, 启动或者停止ZooKeeper
            Zookeeper的引入使得Master不再是单点故障
        5)Store
            每一个region由一个或多个store组成,至少是一个store,hbase会把一起访问的数据放在一个store里面,即为每个 ColumnFamily建一个store,如果有几个ColumnFamily,也就有几个Store。一个Store由一个memStore和0或者 多个StoreFile组成。 HBase以store的大小来判断是否需要切分region。
        6)MemStore
            memStore 是放在内存里的。保存修改的数据即keyValues。当memStore的大小达到一个阀值(默认64MB)时,memStore会被flush到文 件,即生成一个快照。目前hbase 会有一个线程来负责memStore的flush操作。
        7)StoreFile
            memStore内存中的数据写到文件后就是StoreFile,StoreFile底层是以HFile的格式保存。
        8)HLog
            HLog(WAL log):WAL意为write ahead log,用来做灾难恢复使用,HLog记录数据的所有变更,一旦region server 宕机,就可以从log中进行恢复。
            每个HRegionServer中都有一个HLog对象,HLog是一个实现Write Ahead Log的类,在每次用户操作写入MemStore的同时,也会写一份数据到HLog文件中(HLog文件格式见后续),HLog文件定期会滚动出新的,并删除旧的文件(已持久化到StoreFile中的数据)。当HRegionServer意外终止后,HMaster会通过Zookeeper感知到,HMaster首先会处理遗留的 HLog文件,将其中不同Region的Log数据进行拆分,分别放到相应region的目录下,然后再将失效的region重新分配,领取 到这些region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复
    6.3 访问流程
        HBase HRegion servers集群中的所有的region的数据在服务器启动时都是被打开的,并且在内冲初始化一些memstore,相应的这就在一定程度上加快系统响 应;
        数据访问流程如下:
        1.Client会通过内部缓存的相关的-ROOT-中的信息和.META.中的信息直接连接与请求数据匹配的HRegion server;
        2.然后直接定位到该服务器上与客户请求对应的Region,客户请求首先会查询该Region在内存中的缓存——Memstore(Memstore是一个按key排序的树形结构的缓冲区);
        3.如果在Memstore中查到结果则直接将结果返回给Client;
        4.在Memstore中没有查到匹配的数据,接下来会读已持久化的StoreFile文件中的数据。前面的章节已经讲过,StoreFile也是按 key排序的树形结构的文件——并且是特别为范围查询或block查询优化过的,;另外HBase读取磁盘文件是按其基本I/O单元(即 HBase Block)读数据的。
        步骤3,4的具体操作如下:
        如果在BlockCache中能查到要造的数据则这届返回结果,否则就读去相应的StoreFile文件中读取一block的数据,如果还没有读到要查的 数据,就将该数据block放到HRegion Server的blockcache中,然后接着读下一block块儿的数据,一直到这样循环的block数据直到找到要请求的数据并返回结果;如果将该 Region中的数据都没有查到要找的数据,最后接直接返回null,表示没有找的匹配的数据。当然blockcache会在其大小大于一的阀值(heapsize * hfile.block.cache.size * 0.85)后启动基于LRU算法的淘汰机制,将最老最不常用的block删除。
    6.4 HRegion 定位流程

         1)寻找RegionServer
        ZooKeeper--> -ROOT-(单Region)--> .META.--> 用户表
        1.通过zk里的文件/hbase/rs得到-ROOT-表的位置。-ROOT-表只有一个region(可以理解为分表,-ROOT-中只有一张表)。
        2.通过-ROOT-表查找.META.表的第一个表中相应的region的位置。其实-ROOT-表是.META.表的第一个region;.META.表中的每一个region 在-ROOT-表中都是一行记录。
        3.通过.META.表找到所要的用户表region的位置。用户表中的每个region在.META.表中都是一行记录。
        4.最多跳转三次就能找到对应数据所在的服务器
        2)-ROOT-
        表包含.META.表所在的region列表,该表只会有一个Region;
        Zookeeper中记录了-ROOT-表的location。
        3).META.
        表包含所有的用户空间region列表,以及RegionServer的服务器地址。
    6.5 hbase put流程

    客户端:
        客户端发起Put写请求,讲put写入writeBuffer,如果是批量提交,写满缓存后自动提交
        根据rowkey将put吩咐给不同regionserver
    服务端:
        RegionServer将put按rowkey分给不同的region
        Region首先把数据写入wal
        wal写入成功后,把数据写入memstore
        Memstore写完后,检查memstore大小是否达到flush阀值
        如果达到flush阀值,将memstore写入HDFS,生成HFile文件
7.HBase查询为什么那么快
    如果快速查询(从磁盘读数据),hbase是根据rowkey查询的,只要能快速的定位rowkey,  就能实现快速的查询,主要是以下因素:
    1.hbase是可划分成多个region,可以简单的理解为关系型数据库的多个分区。
    2.键是排好序了的
    3.按列存储的
    首先,能快速找到行所在的region(分区),假设表有10亿条记录,占空间1TB,   分列成了500个region,  1个region占2个G. 最多读取2G的记录,就能找到对应记录; 
    其次,是按列存储的,其实是列族,假设分为3个列族,每个列族就是666M, 如果要查询的东西在其中1个列族上,1个列族包含1个或者多个HStoreFile,假设一个HStoreFile是128M, 该列族包含5个HStoreFile在磁盘上. 剩下的在内存中。
再次,是排好序了的,你要的记录有可能在最前面,也有可能在最后面,假设在中间,我们只需遍历2.5个HStoreFile共300M
    最后,每个HStoreFile(HFile的封装),是以键值对(key-value)方式存储,只要遍历一个个数据块中的key的位置,并判断符合条件可以了。 一般key是有限的长度,假设跟value是1:19(忽略HFile上其它块),最终只需要15M就可获取的对应的记录,按照磁盘的访问100M/S,只需0.15秒。 加上块缓存机制(LRU原则),会取得更高的效率。

猜你喜欢

转载自blog.csdn.net/fibonacci2015/article/details/83418690