HBase

https://www.csdn.net/gather_2e/MtTaEg0sMzE3Ni1ibG9n.htmlphp

  • 來源:
  • 應用:
  • 行業:
  • Hbase定義:
  • Hbase特性:
  • Hbase shell
    • namespace
    • DDL
    • DML
  • Hbase Java Api
    • 依賴
    • HbaseUtils
    • HbaseDemo
    • Hbase過濾器
  • Hbase原理
    • 架構
  • Hbase讀寫流程
    • 寫數據流程
    • Hbase的存儲機制
      • 存儲模型
      • 布隆過濾器
    • 2.6.10 Hbase的尋址機制
    • 讀數據流程
  • StoreFile合併
  • Region分割
  • Hbase2Hdfs
  • Hdfs2Hbase

 

Hbase

來源:

  • 解決隨機近實時的高效的讀寫
  • 解決非結構化的數據存儲

應用:

  • 能夠存儲非結構化的數據(用戶、商品、文章的畫像屬性)css

  • 被用來作實時(整合flume、storm、streaming等)html

  • 存儲歷史明細數據(較少)java

  • 存儲結果數據(數倉,Kylin預執行數據就是放到Hbase中)node

行業:

  • 通訊、銀行、金融等

Hbase定義:

  • Hadoop的數據庫
  • Hadoop的分佈式、開源的、多版本的非關係型數據庫
  • Hbase存儲Key-Value格式,面向列存儲,Hbase底層爲字節數據,沒有數據類型一說

Hbase特性:

  • 線性和模塊化可擴展性
  • 嚴格一致的讀寫
  • 表的自動和可配置分片
  • RegionServer之間的自動故障轉移支持
  • 方便的基類,用於經過Apache HBase表備份Hadoop MapReduce做業
  • 易於使用的Java API用於客戶端訪問
  • 塊緩存和布隆過濾器用於實時查詢
  • 經過服務器端過濾器查詢謂詞下推
  • Thrift網關和支持XML,Protobuf和二進制數據編碼選項的REST-ful Web服務
  • 可擴展的基於Jruby的(JIRB)外殼
  • 支持經過Hadoop指標子系統將指標導出到文件或Ganglia;或經過JMX

Hbase shell

namespace

1. list_namespace:查詢全部命名空間 hbase(main):001:0> list_namespace NAMESPACE default hbase 2. list_namespace_tables : 查詢指定命名空間的表 hbase(main):014:0> list_namespace_tables 'hbase' TABLE meta namespace 3. create_namespace : 建立指定的命名空間 hbase(main):018:0> create_namespace 'myns' hbase(main):019:0> list_namespace NAMESPACE default hbase myns 4. describe_namespace : 查詢指定命名空間的結構 hbase(main):021:0> describe_namespace 'myns' DESCRIPTION {NAME => 'myns'} 5. alter_namespace :修改命名空間的結構 hbase(main):022:0> alter_namespace 'myns', {METHOD => 'set', 'name' => 'eRRRchou'} hbase(main):023:0> describe_namespace 'myns' DESCRIPTION {NAME => 'myns', name => 'eRRRchou'} 修改命名空間的結構=>刪除name hbase(main):022:0> alter_namespace 'myns', {METHOD => 'unset', NAME => 'name'} hbase(main):023:0> describe_namespace 'myns' 6. 刪除命名空間 hbase(main):026:0> drop_namespace 'myns' hbase(main):027:0> list_namespace NAMESPACE default hbase 7. 利用新添加的命名空間建表 hbase(main):032:0> create 'myns:t1', 'f1', 'f2' 

DDL

1. 查詢全部表 hbase(main):002:0> list TABLE HelloHbase kylin_metadata myns:t1 3 row(s) in 0.0140 seconds => ["HelloHbase", "kylin_metadata", "myns:t1"] 2. describe : 查詢表結構 hbase(main):003:0> describe 'myns:t1' {NAME => 'f1', BLOOMFILTER => 'ROW', VERSIONS => '1', IN_MEMORY => 'false', KEEP _DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', COMP RESSION => 'NONE', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '6553 6', REPLICATION_SCOPE => '0'} {NAME => 'f2', BLOOMFILTER => 'ROW', VERSIONS => '1', IN_MEMORY => 'false', KEEP _DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', COMP RESSION => 'NONE', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '6553 6', REPLICATION_SCOPE => '0'} 3. 建立分片表 hbase(main):007:0> create 'myns:t2', 'f1', SPLITS => ['10', '20', '30', '40'] 4. 修改表,添加修改列簇信息 hbase(main):009:0> alter 'myns:t1', {NAME=>'info1'} hbase(main):010:0> describe 'myns:t1' 5. 刪除列簇 hbase(main):014:0> alter 'myns:t1', {'delete' => 'info1'} hbase(main):015:0> describe 'myns:t1' 6. 刪除表 hbase(main):016:0> disable 'myns:t1' hbase(main):017:0> drop 'myns:t1' 

DML

用到的表建立語句:
hbase(main):011:0> create 'myns:user_info','base_info','extra_info' 1. 插入數據(put命令,不能一次性插入多條) hbase(main):012:0> put 'myns:user_info','001','base_info:username','張三' 2. scan掃描 hbase(main):024:0> scan 'myns:user_info' 3. 經過指定版本查詢 hbase(main):024:0> scan 'myns:user_info', {RAW => true, VERSIONS => 1} hbase(main):024:0> scan 'myns:user_info', {RAW => true, VERSIONS => 2} 4. 查詢指定列的數據 hbase(main):014:0> scan 'myns:user_info',{COLUMNS => 'base_info:username'} 5. 分頁查詢 hbase(main):021:0> scan 'myns:user_info', {COLUMNS => ['base_info:username'], LIMIT => 10, STARTROW => '001'} 6. get查詢 hbase(main):015:0> get 'myns:user_info','001','base_info:username' hbase(main):017:0> put 'myns:user_info','001','base_info:love','basketball' hbase(main):018:0> get 'myns:user_info','001' 7. 根據時間戳查詢 是一個範圍,包頭不包尾 hbase(main):029:0> get 'myns:user_info','001', {'TIMERANGE' => [1571650017702, 1571650614606]} 8. hbase排序 插入到hbase中去的數據,hbase會自動排序存儲: 排序規則: 首先看行鍵,而後看列族名,而後看列(key)名; 按字典順序 9. 更新數據 hbase(main):010:0> put 'myns:user_info', '001', 'base_info:name', 'rock' hbase(main):011:0> put 'myns:user_info', '001', 'base_info:name', 'eRRRchou' 10. incr計數器 hbase(main):053:0> incr 'myns:user_info', '002', 'base_info:age3' 11. 刪除 hbase(main):058:0> delete 'myns:user_info', '002', 'base_info:age3' 12. 刪除一行 hbase(main):028:0> deleteall 'myns:user_info','001' 13. 刪除一個版本 hbase(main):081:0> delete 'myns:user_info','001','extra_info:feature', TIMESTAMP=>1546922931075 14. 刪除一個表 hbase(main):082:0> disable 'myns:user_info' hbase(main):083:0> drop 'myns:user_info' 15. 判斷表是否存在 hbase(main):084:0> exists 'myns:user_info' 16. 表生效和失效 hbase(main):085:0> enable 'myns:user_info' hbase(main):086:0> disable 'myns:user_info' 17. 統計表行數 hbase(main):088:0> count 'myns:user_info' 18. 清空表數據 hbase(main):089:0> truncate 'myns:user_info' 

Hbase Java Api

依賴

<dependencies> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-client</artifactId> <version>1.4.10</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>compile</scope> </dependency> </dependencies> 

HbaseUtils

public class HbaseUtils { public static Configuration configuration = null; public static ExecutorService executor = null; public static HBaseAdmin hBaseAdmin = null; public static Admin admin = null; public static Connection conn = null; public static Table table; static { //1. 獲取鏈接配置對象 configuration = new Configuration(); //2. 設置鏈接hbase的參數 configuration.set("hbase.zookeeper.quorum", "mini01:2181,mini02:2181,mini03:2181"); //3. 獲取Admin對象 try { executor = Executors.newFixedThreadPool(20); conn = ConnectionFactory.createConnection(configuration, executor); hBaseAdmin = (HBaseAdmin)conn.getAdmin(); } catch (Exception e) { e.printStackTrace(); } } public static HBaseAdmin getHbaseAdmin(){ return hBaseAdmin; } public static Table getTable(TableName tableName) throws IOException { return conn.getTable(tableName); } public static void close(HBaseAdmin admin){ try { admin.close(); } catch (IOException e) { e.printStackTrace(); } } public static void close(HBaseAdmin admin,Table table){ try { if(admin!=null) { admin.close(); } if(table!=null) { table.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void close(Table table){ try { if(table!=null) { table.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void showResult(Result result) throws IOException { CellScanner scanner = result.cellScanner(); while(scanner.advance()){ Cell cell = scanner.current(); System.out.print("\t" + new String(CellUtil.cloneFamily(cell),"utf-8")); System.out.print(" : " + new String(CellUtil.cloneQualifier(cell),"utf-8")); System.out.print("\t" + new String(CellUtil.cloneValue(cell),"utf-8")); } } } 

HbaseDemo

public class HbaseDemo { private HBaseAdmin hBaseAdmin = null; private Admin admin = null; @Before public void init(){ hBaseAdmin = HbaseUtils.getHbaseAdmin(); } @After public void after(){ HbaseUtils.close(hBaseAdmin); } @Test public void tableExists() throws IOException { //檢查表是否存在 //4. 檢驗指定表是否存在,來判斷是否鏈接到hbase boolean flag = hBaseAdmin.tableExists("myns:user_info"); //5. 打印 System.out.println(flag); } @Test public void listNamespace() throws IOException { //遍歷命名空間 NamespaceDescriptor[] namespaceDescriptors = hBaseAdmin.listNamespaceDescriptors(); // 打印 for(NamespaceDescriptor namespaceDescriptor:namespaceDescriptors){ System.out.println(namespaceDescriptor); } } @Test public void listTables() throws Exception{ //獲取表的名字 //獲取指定命名空間下的表 TableName[] tables = hBaseAdmin.listTableNamesByNamespace("myns"); System.out.println("對應命名空間下的表名:"); for (TableName table:tables){ System.out.println(table); } tables = hBaseAdmin.listTableNames(); System.out.println("全部表名:"); for (TableName table:tables){ System.out.println(table); } } @Test public void createNamespace() throws Exception{ //建立namespace hBaseAdmin.createNamespace(NamespaceDescriptor.create("eRRRchou").build()); } @Test public void createTable() throws Exception{ //建立表 HTableDescriptor descriptor = new HTableDescriptor(TableName.valueOf("myns:user_info")); //建立列簇 HColumnDescriptor columnDescriptor1 = new HColumnDescriptor("base_info"); columnDescriptor1.setVersions(1, 5); //設置列簇版本從1到5 columnDescriptor1.setTimeToLive(24*60*60); //秒 //建立列簇 HColumnDescriptor columnDescriptor2 = new HColumnDescriptor("extra_info"); columnDescriptor2.setVersions(1, 5); columnDescriptor2.setTimeToLive(24*60*60); // 秒爲單位 //綁定關係 descriptor.addFamily(columnDescriptor1); descriptor.addFamily(columnDescriptor2); //建立表 hBaseAdmin.createTable(descriptor); } @Test public void deleteTable() throws Exception{ //刪除Family hBaseAdmin.disableTable("myns:user_info"); hBaseAdmin.deleteTable("myns:user_info"); } @Test public void modifyFamily() throws Exception{ //修改列簇 TableName tableName = TableName.valueOf("myns:user_info"); //HTableDescriptor descriptor = new HTableDescriptor(tableName);//原來的列簇消失 new了個新的 HTableDescriptor descriptor = hBaseAdmin.getTableDescriptor(tableName); //得到原來的描述 HColumnDescriptor columnDescriptor = new HColumnDescriptor("extra_info"); columnDescriptor.setVersions(1, 5); //設置列簇版本從1到5 columnDescriptor.setTimeToLive(24*60*60); //秒 descriptor.addFamily(columnDescriptor); hBaseAdmin.modifyTable(tableName,descriptor); } @Test public void deleteFamily() throws Exception{ //刪除Family hBaseAdmin.deleteColumn("myns:user_info","extra_info"); } @Test public void deleteColumeFamily() throws Exception{ ///刪除Family TableName tableName = TableName.valueOf("myns:user_info"); HTableDescriptor tableDescriptor = hBaseAdmin.getTableDescriptor(tableName); tableDescriptor.removeFamily("extra_info".getBytes()); hBaseAdmin.modifyTable(tableName,tableDescriptor); } @Test public void listFamily() throws Exception{ //遍歷Family TableName tableName = TableName.valueOf("myns:user_info"); HTableDescriptor tableDescriptor = hBaseAdmin.getTableDescriptor(tableName); HColumnDescriptor[] columnFamilies = tableDescriptor.getColumnFamilies(); for(HColumnDescriptor columnFamilie:columnFamilies){ System.out.println(columnFamilie.getNameAsString()); System.out.println(columnFamilie.getBlocksize()); System.out.println(columnFamilie.getBloomFilterType()); } } @Test public void getTable() throws IOException { Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); HbaseUtils.close(table); } @Test public void putDatas() throws IOException { Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); Put put = new Put(Bytes.toBytes("001")); put.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("userName"),Bytes.toBytes("zhangsan")); put.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("age"),Bytes.toBytes(18)); put.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("sex"),Bytes.toBytes("male")); //提交 table.put(put); HbaseUtils.close(table); } @Test public void batchPutDatas() throws IOException { Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); //0. 建立集合 List<Put> list = new ArrayList<Put>(); //1. 建立put對象指定行鍵 Put rk004 = new Put(Bytes.toBytes("002")); Put rk005 = new Put(Bytes.toBytes("003")); Put rk006 = new Put(Bytes.toBytes("004")); //2. 建立列簇 rk004.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("name"),Bytes.toBytes("gaoyuanyuan")); rk005.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("age"),Bytes.toBytes("18")); rk005.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("sex"),Bytes.toBytes("2")); rk006.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("name"),Bytes.toBytes("fanbinbin")); rk006.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("age"),Bytes.toBytes("18")); rk006.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("sex"),Bytes.toBytes("2")); //3. 添加數據 list.add(rk004); list.add(rk005); list.add(rk006); table.put(list); } @Test public void getData() throws Exception{ Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); Get get = new Get(Bytes.toBytes("001")); Result result = table.get(get); NavigableMap<byte[], byte[]> base_infos = result.getFamilyMap(Bytes.toBytes("base_info")); for(Map.Entry<byte[], byte[]> base_info:base_infos.entrySet()){ String k = new String(base_info.getKey()); String v = ""; if(k.equals("age")) { v = String.valueOf(Bytes.toInt(base_info.getValue())); }else{ v = new String(base_info.getValue()); } System.out.println(k+":"+v); } } @Test public void getData2() throws IOException { Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); //1. 獲Get對象 Get get = new Get(Bytes.toBytes("004")); //2. 經過table獲取結果對象 Result result = table.get(get); //3. 獲取表格掃描器 CellScanner cellScanner = result.cellScanner(); System.out.println("rowkey : " + result.getRow()); //4. 遍歷 while (cellScanner.advance()) { //5. 獲取當前表格 Cell cell = cellScanner.current(); //5.1 獲取全部的列簇 byte[] familyArray = cell.getFamilyArray(); System.out.println(new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength())); //5.2 獲取全部列 byte[] qualifierArray = cell.getQualifierArray(); System.out.println(new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength())); //5.3 獲取全部的值 byte[] valueArray = cell.getValueArray(); System.out.println(new String(valueArray, cell.getValueOffset(), cell.getValueLength())); } } @Test public void getData3() throws IOException { Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); //1. 得到Get對象 Get get = new Get(Bytes.toBytes("004")); //2. 經過table獲取結果對象 Result result = table.get(get); //3. 獲取表格掃描器 CellScanner cellScanner = result.cellScanner(); //4.遍歷 while(cellScanner.advance()){ Cell cell = cellScanner.current(); //獲取全部的列簇 System.out.println(new String(CellUtil.cloneFamily(cell),"utf8")); System.out.println(new String(CellUtil.cloneQualifier(cell),"utf8")); System.out.println(new String(CellUtil.cloneValue(cell),"utf8")); } } @Test public void batchGetData() throws IOException { //1. 建立集合存儲get對象 Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); List<Get> gets = new ArrayList<Get>(); //2. 建立多個get對象 Get get1 = new Get(Bytes.toBytes("004")); get1.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("name")); get1.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("sex")); get1.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("age")); Get get2 = new Get(Bytes.toBytes("001")); get2.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("name")); get2.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("sex")); Get get3 = new Get(Bytes.toBytes("003")); get3.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("sex")); get3.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("age")); gets.add(get1); gets.add(get2); gets.add(get3); Result[] results = table.get(gets); for (Result result:results){ HbaseUtils.showResult(result); } } @Test public void scanTable() throws IOException { //1. 建立掃描器 Scan scan = new Scan(); //2. 添加掃描的行數包頭不包尾 Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); scan.setStartRow(Bytes.toBytes("001")); scan.setStopRow(Bytes.toBytes("006" + "\001")); //小技巧 //3. 添加掃描的列 scan.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("name")); //4. 獲取掃描器 ResultScanner scanner = table.getScanner(scan); Iterator<Result> it = scanner.iterator(); while (it.hasNext()){ Result result = it.next(); HbaseUtils.showResult(result); } } @Test public void deleteData() throws IOException { Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); //1. 建立集合用於批量刪除 List<Delete> dels = new ArrayList<Delete>(); //2. 建立刪除數據對象 Delete del = new Delete(Bytes.toBytes("004")); del.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("name")); //3. 添加到集合 dels.add(del); //4. 提交 table.delete(dels); } } 

Hbase過濾器

@Test public void filter() throws IOException { //RegexStringComparator 正則 //SubstringComparator; subString比較器 //BinaryComparator 二進制比較器 //and條件 FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL); SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(Bytes.toBytes("base_info"), Bytes.toBytes("name"), CompareFilter.CompareOp.LESS_OR_EQUAL,Bytes.toBytes("gaoyuanyuan")); filterList.addFilter(nameFilter); Scan scan = new Scan(); scan.setFilter(filterList); Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); ResultScanner scanner = table.getScanner(scan); Iterator<Result> it = scanner.iterator(); while (it.hasNext()){ Result result = it.next(); HbaseUtils.showResult(result); } } @Test public void familyFilter() throws IOException { //RegexStringComparator 正則 //SubstringComparator; subString比較器 //BinaryComparator 二進制比較器 //and條件 RegexStringComparator regexStringComparator = new RegexStringComparator("^base"); //2. 建立FamilyFilter:結果中只包含知足條件的列簇信息 FamilyFilter familyFilter = new FamilyFilter(CompareFilter.CompareOp.EQUAL, regexStringComparator); //4.建立掃描器進行掃描 Scan scan = new Scan(); //5. 設置過濾器 scan.setFilter(familyFilter); //6. 獲取表對象 Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); //7. 掃描表 ResultScanner scanner = null; try { scanner = table.getScanner(scan); //8. 打印數據 Iterator<Result> iterator = scanner.iterator(); while (iterator.hasNext()) { Result result = iterator.next(); HbaseUtils.showResult(result); } } catch (IOException e) { } finally { try { table.close(); } catch (IOException e) { } } } @Test public void rowFiter() throws IOException { //1. 建立RowFilter BinaryComparator binaryComparator = new BinaryComparator(Bytes.toBytes("002")); RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL, binaryComparator); //4.建立掃描器進行掃描 Scan scan = new Scan(); //5. 設置過濾器 scan.setFilter(rowFilter); //6. 獲取表對象 Table table = HbaseUtils.getTable(TableName.valueOf("myns:user_info")); //7. 掃描表 ResultScanner scanner = null; try { scanner = table.getScanner(scan); //8. 打印數據 Iterator<Result> iterator = scanner.iterator(); while (iterator.hasNext()) { Result result = iterator.next(); HbaseUtils.showResult(result); } } catch (IOException e) { } finally { try { table.close(); } catch (IOException e) { } } } 

Hbase原理

在這裏插入圖片描述

架構

一、Hmasterlinux

  • 負責管理Hbase的元數據,表結構,表的Region信息
  • 負責表的建立,刪除和修改
  • 負責爲HRegionServer分配Region,分配後將元數據寫入相應位置

二、HRegionServerios

  • 含有多個HRegion
  • 處理Client端的讀寫請求(根據從HMaster返回的元數據找到對應的HRegionServer)
  • 管理Region的Split分裂、StoreFile的Compaction合併。

三、HRegiongit

  • 一個HRegion裏可能有1個或多個Store。
  • HRegionServer維護一個HLog。
  • HRegion是分佈式存儲和負載的最小單元。
  • 表一般被保存在多個HRegionServer的多個Region中。

四、Storegithub

  • Store是存儲落盤的最小單元,由內存中的MemStore和磁盤中的若干StoreFile組成。
  • 一個Store裏有1個或多個StoreFile和一個memStore。
  • 每一個Store存儲一個列族。

Hbase讀寫流程

寫數據流程

在這裏插入圖片描述

  • Client訪問ZK,根據ROOT表獲取meta表所在Region的位置信息,並將該位置信息寫入Client Cache。
    (注:爲了加快數據訪問速度,咱們將元數據、Region位置等信息緩存在Client Cache中)。web

  • Client讀取meta表,再根據meta表中查詢獲得的Namespace、表名和RowKey等相關信息,獲取將要寫入Region的位置信息(此過程即Region三層定位,以下圖),最後client端會將meta表寫入Client Cache。

  • Hbase使用memstore和storefile存儲對錶的更新,數據在更新時首先寫入hlog和memstore,memstore是排序的。

  • 當memstore積累到必定的閾值時,就會建立一個新的memstore,並將老的memstore加入flush隊列,由單獨的線程flush到磁盤上,成爲一個StoreFile

  • 系統Zookeeper中記錄一個checkpoint,表示這個時刻以前的數據變動已經持久化,發生故障只須要恢復checkpoint的數據

  • storefile是隻讀的,一旦建立以後就不可修改,當一個store的storefile達到必定的閥值後,就會進行一次合併操做,將對同一個key的修改合併到一塊兒,同時進行版本合併和數據刪除,造成一個大的storefile。當storefile的大小達到必定的閥值後,又會對storefile進行切分操做,等分爲兩個storefile。

  • Hbase中只有增添數據,全部的更新和刪除操做都是在後續的合併中進行的,使得用戶的寫操做只要進入內存就能夠馬上返回,實現了hbase的高速存儲。

(1) Client經過Zookeeper的調度,向RegionServer發出寫數據請求,在Region中寫數據。
(2) 數據被寫入Region的MemStore,直到MemStore達到預設閾值。
(3) MemStore中的數據被Flush成一個StoreFile。
(4) 隨着StoreFile文件的不斷增多,當其數量增加到必定閾值後,觸發Compact合併操做,將多個StoreFile合併成一個StoreFile,同時進行版本合併和數據刪除。
(5) StoreFiles經過不斷的Compact合併操做,逐步造成愈來愈大的StoreFile。
(6) 單個StoreFile大小超過必定閾值後,觸發Split操做,把當前Region Split成2個新的Region。父Region會下線,新Split出的2個子Region會被HMaster分配到相應的RegionServer上,使得原先1個Region的壓力得以分流到2個Region上。

Hbase的存儲機制

存儲模型

在這裏插入圖片描述

1. 每一次的插入操做都會先進入MemStore(內存緩衝區),
2. 當 MemStore達到上限的時候,Hbase會將內存中的數據輸出爲有序的StoreFile文件數據(根據Rowkey、版本、列名排序,這裏已經和列簇無關了由於Store裏都屬於同一個列簇)。
3. 這樣會在Store中造成不少個小的StoreFile,當這些小的File數量達到一個閥值的時 候,Hbase會用一個線程來把這些小File合併成一個大的File。這樣,Hbase就把效率低下的文件中的插入、移動操做轉變成了單純的文件輸出、 合併操做。

由上可知,在Hbase底層的Store數據結構中,

    1) 每一個StoreFile內的數據是有序的,
    2) 可是StoreFile之間不必定是有序的,
    3) Store只 須要管理StoreFile的索引就能夠了。

	這裏也能夠看出爲何指定版本和Rowkey能夠增強查詢的效率,由於指定版本和Rowkey的查詢能夠利用 StoreFile的索引跳過一些確定不包含目標數據的數據。

在這裏插入圖片描述

布隆過濾器

它的時間複雜度是O(1),可是空間佔用取決其優化的方式。它是布隆過濾器的基礎。
布隆過濾器(Bloom Filter)的核心實現是一個超大的位數組(或者叫位向量)和幾個哈希函數。假設位數組的長度爲m,哈希函數的個數爲k
假設集合裏面有3個元素{x, y, z},哈希函數的個數爲3。

Step1:將位數組初始化,每位都設置爲0。

Step2:對於集合裏面的每個元素,將元素依次經過3個哈希函數進行映射,每次映射都會產生一個哈希值,哈希值對應位數組上面的一個點,將該位置標記爲1。

Step3:查詢W元素是否存在集合中的時候,一樣的方法將W經過哈希映射到位數組上的3個點。

Step4:若是3個點的其中有一個點不爲1,則能夠判斷該元素必定不存在集合中。反之,若是3個點都爲1,則該元素可能存在集合中。注意:此處不能判斷該元素是否必定存在集合中,可能存在必定的誤判率。
	能夠從圖中能夠看到:假設某個元素經過映射對應下標爲4,5,6這3個點。雖然這3個點都爲1,可是很明顯這3個點是不一樣元素通過哈希獲得的位置,所以這種狀況說明元素雖然不在集合中,也可能對應的都是1,這是誤判率存在的緣由。

在這裏插入圖片描述

  • 布隆過濾器應用在Hbase
當咱們隨機讀get數據時,若是採用hbase的塊索引機制,hbase會加載不少塊文件。若是採用布隆過濾器後,它可以準確判斷該HFile的全部數據塊中,是否含有咱們查詢的數據,從而大大減小沒必要要的塊加載,從而增長hbase集羣的吞吐率。這裏有幾點細節:
	
1. 布隆過濾器的存儲在哪?
	對於hbase而言,當咱們選擇採用布隆過濾器以後,HBase會在生成StoreFile(HFile)時包含一份布隆過濾器結構的數據,稱其爲MetaBlock;MetaBlock與DataBlock(真實的KeyValue數據)一塊兒由LRUBlockCache維護。因此,開啓bloomfilter會有必定的存儲及內存cache開銷。可是在大多數狀況下,這些負擔相對於布隆過濾器帶來的好處是能夠接受的。
	
2. 採用布隆過濾器後,hbase如何get數據?
	在讀取數據時,hbase會首先在布隆過濾器中查詢,根據布隆過濾器的結果,再在MemStore中查詢,最後再在對應的HFile中查詢。
	
3. 採用ROW仍是ROWCOL布隆過濾器?
	這取決於用戶的使用模式。若是用戶只作行掃描,使用更加細粒度的行加列布隆過濾器不會有任何的幫助,這種場景就應該使用行級布隆過濾器。當用戶不能批量更新特定的一行,而且最後的使用存儲文件都含有改行的一部分時,行加列級的布隆過濾器更加有用。
	
tip:
ROW和ROWCOL只是名字上有聯繫,可是ROWCOL並非ROW的擴展,也不能取代ROW

2.6.10 Hbase的尋址機制

讀數據流程

在這裏插入圖片描述

(1) Client訪問Zookeeper,查找-ROOT-表,獲取.META.表信息。
    (2) 從.META.表查找,獲取存放目標數據的Region信息,從而找到對應的RegionServer。
    (3) 經過RegionServer獲取須要查找的數據。
    (4) Regionserver的內存分爲MemStore和BlockCache兩部分,MemStore主要用於寫數據,BlockCache主要用於讀數據。讀請求先到MemStore中查數據,查不到就到BlockCache中查,再查不到就會到StoreFile上讀,並把讀的結果放入BlockCache。

StoreFile合併

目的:減小StoreFile數量,提高數據讀取效率。

Compaction分爲兩種:

major compaction
將Store下面全部StoreFile合併爲一個StoreFile,此操做會刪除其餘版本的數據(不一樣時間戳的)

minor compaction
選取Store下的部分StoreFile,將它們合併爲一個StoreFile,此操做不會刪除其餘版本數據。

Region分割

目的:實現數據訪問的負載均衡。

作法:利用Middle Key將當前Region劃分爲兩個等分的子Region。須要指出的是:Split會產生大量的I/O操做,Split開始前和Split完成後,HRegionServer都會通知HMaster。Split完成後,因爲Region映射關係已變動,故HRegionServer會更新meta表。

Hbase2Hdfs

class HbaseMapper extends TableMapper<Text, NullWritable> { private Text k = new Text(); @Override protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException { //0. 定義字符串存放最終結果 StringBuffer sb = new StringBuffer(); //1. 獲取掃描器進行掃描解析 CellScanner cellScanner = value.cellScanner(); //2. 推動 while (cellScanner.advance()) { //3. 獲取當前單元格 Cell cell = cellScanner.current(); //4. 拼接字符串 sb.append(new String(CellUtil.cloneQualifier(cell))); sb.append(":"); sb.append(new String(CellUtil.cloneValue(cell))); sb.append("\t"); } //5. 寫出 k.set(sb.toString()); context.write(k, NullWritable.get()); } } public class Hbase2Hdfs implements Tool { private Configuration configuration; private final static String HBASE_CONNECT_KEY = "hbase.zookeeper.quorum"; private final static String HBASE_CONNECT_VALUE = "mini01:2181,mini02:2181,mini03:2181"; private final static String HDFS_CONNECT_KEY = "fs.defaultFS"; private final static String HDFS_CONNECT_VALUE = "hdfs://qf/"; private final static String MAPREDUCE_CONNECT_KEY = "mapreduce.framework.name"; private final static String MAPREDUCE_CONNECT_VALUE = "yarn"; @Override public int run(String[] strings) throws Exception { Job job = Job.getInstance(configuration, "hbase2hdfs"); job.setJarByClass(Hbase2Hdfs.class); TableMapReduceUtil.initTableMapperJob("myns:user_info", getScan(), HbaseMapper.class, Text.class, NullWritable.class, job); FileOutputFormat.setOutputPath(job,new Path("/hbaseout/04")); boolean b = job.waitForCompletion(true); return b ? 1 : 0; } @Override public void setConf(Configuration configuration) { configuration.set(HBASE_CONNECT_KEY, HBASE_CONNECT_VALUE); // 設置鏈接的hbase configuration.set(HDFS_CONNECT_KEY, HDFS_CONNECT_VALUE); // 設置鏈接的hadoop configuration.set(MAPREDUCE_CONNECT_KEY, MAPREDUCE_CONNECT_VALUE); // 設置使用的mr運行平臺 this.configuration = configuration; } @Override public Configuration getConf() { return configuration; } public static void main(String[] args) throws Exception { ToolRunner.run(HBaseConfiguration.create(), new Hbase2Hdfs(), args); } private static Scan getScan() { return new Scan(); } } 

Hdfs2Hbase

public class Hdfs2Hbase implements Tool { private void createTable(String tablename) { //1. 獲取admin對象 HBaseAdmin admin = HbaseUtils.getHbaseAdmin(); //2. try { boolean isExist = admin.tableExists(TableName.valueOf(tablename)); if(isExist) { admin.disableTable(TableName.valueOf(tablename)); admin.deleteTable(TableName.valueOf(tablename)); } HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf(tablename)); HColumnDescriptor columnDescriptor2 = new HColumnDescriptor("age_info"); columnDescriptor2.setBloomFilterType(BloomType.ROW); columnDescriptor2.setVersions(1, 3); tableDescriptor.addFamily(columnDescriptor2); admin.createTable(tableDescriptor); } catch (IOException e) { e.printStackTrace(); }finally { HbaseUtils.close(admin); } } public static class HBaseMapper extends Mapper<LongWritable, Text,Text,LongWritable>{ Text text = new Text(); LongWritable lw = new LongWritable(1); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); String[] datas = line.split(","); text.set(datas[0]); lw.set(Long.parseLong(datas[1])); context.write(text,lw); } } public static class HBaseReducer extends TableReducer<Text, LongWritable, ImmutableBytesWritable> { @Override protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException { //1. 計數器 long count = 0l; //2. 迭代 Iterator<LongWritable> iterator = values.iterator(); //3. 輸出必定要是能夠修改hbase的對象,put,delete Put put = new Put(Bytes.toBytes(key.toString())); String value = values.iterator().next().toString(); //4. 將結果集寫入put對象 put.addColumn(Bytes.toBytes("age_info"), Bytes.toBytes("age"), Bytes.toBytes(value)); //5. 寫 context.write(new ImmutableBytesWritable(Bytes.toBytes(key.toString())), put); } } //1. 建立配置對象 private Configuration configuration; private final static String HBASE_CONNECT_KEY = "hbase.zookeeper.quorum"; private final static String HBASE_CONNECT_VALUE = "mini01:2181,mini02:2181,mini03:2181"; //private final static String HDFS_CONNECT_KEY = "fs.defaultFS"; // private final static String HDFS_CONNECT_VALUE = "hdfs://qf/"; //private final static String MAPREDUCE_CONNECT_KEY = "mapreduce.framework.name"; // private final static String MAPREDUCE_CONNECT_VALUE = "yarn"; @Override public int run(String[] strings) throws Exception { Job job = Job.getInstance(configuration); job.setJarByClass(Hdfs2Hbase.class); job.setMapperClass(HBaseMapper.class); job.setReducerClass(HBaseReducer.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(LongWritable.class); String tablename = "user_infomation"; createTable(tablename); FileInputFormat.setInputPaths(job,new Path("D://information.txt")); TableMapReduceUtil.initTableReducerJob(tablename,HBaseReducer.class,job); return job.waitForCompletion(true)?1:0; } @Override public void setConf(Configuration conf) { conf.set(HBASE_CONNECT_KEY, HBASE_CONNECT_VALUE); // 設置鏈接的hbase //conf.set(HDFS_CONNECT_KEY, HDFS_CONNECT_VALUE); // 設置鏈接的hadoop //conf.set(MAPREDUCE_CONNECT_KEY, MAPREDUCE_CONNECT_VALUE); // 設置使用的mr運行平臺 this.configuration = conf; } @Override public Configuration getConf() { return configuration; } public static void main(String[] args) throws Exception { ToolRunner.run(HBaseConfiguration.create(), new Hdfs2Hbase(), args); } } 
2018-12-08 10:53:54 baichoufei90 閱讀數 3821

HBase學習

1 摘要

本文是一篇HBase學習綜述,將會介紹HBase的特色、對比其餘數據存儲技術、架構、存儲、數據結構、使用、過濾器等。

關於Phoenix on HBase,即Sql化的HBase服務,能夠參考Phoenix學習

未完成

2 HBase基礎概念

2.1 HBase是什麼

  • 起源
    HBase源於Google 2005年的論文Bigtable。由Powerset公司在2007年發佈第一個版本,2008年成爲Apache Hadoop子項目,2010年單獨升級爲Apache頂級項目。

  • 設計目標
    HBase的設計目標就是爲了那些巨大的表,如數十億行、數百萬列。

  • 一句話歸納
    HBase是一個開源的、分佈式的、版本化、列式存儲的非關係型數據庫。

  • 面向列
    準確的說是面向列族。每行數據列能夠不一樣。
    HBase表

2.2 HBase相對於RDMBS能解決什麼問題

  擴展性 表設計 負載均衡 failover 事務 適用數據量
RDBMS 靈活性較弱 同步實現 支持 萬級
HBase 十億級行,百萬級列;動態列,每行列可不一樣。且引入列族和數據多版本概念。 各組件都支持HA MVCC, Produce LOCK;行級事務 億級

2.3 HBase特色

HBase特色

  • HDFS支持的海量存儲,鏈家PC存儲PB級數據仍能有百毫秒內的響應速度。(擴展性十分好)
  • 極易擴展(可添加不少RS節點進行處理能力擴展,也可添加多個HDFS DataNode進行存儲能力擴展),表自動分片,且支持自動Failover
  • 高效地、強一致性地讀寫海量數據,CP
  • MapReduce可讀寫HBase
  • JavaAPI, Thrift / REST API, Shell
  • 依賴Blockcache和布隆過濾器提供實時查詢
  • 服務端側的過濾器實現謂詞下推,加速查詢
  • 可經過JMX監控HBase各指標
  • 面向列,列式存儲,且列能夠按需動態增長
  • 稀疏。空Cell不佔空間
  • 數據多版本
  • 數據類型單一,都是字符串,無類型(要用類型可用Phoenix實現)

2.4 HBase與Hadoop

HBaseHadoop
做爲曾經Hadoop項目的子項目,HBase仍是與Hadoop生態關係密切。HBase底層存儲用了HDFS,並可直接用MapReduce操做HBase

2.5 HBase與CAP

2.5.1 CAP簡介

CAP定理指出,分佈式系統能夠保持如下三個特徵中的兩個:

  • Consistency,一致性
    請求全部節點都看到相同的數據
  • Availability,可用性
    每一個請求都能及時收到響應,不管成功仍是失敗。
  • Partition tolerance,分區容忍
    即便其餘組件沒法使用,系統也會繼續運行。

2.5.2 HBase的取捨-CP

HBase選擇的是一致性和分區容忍即CP。

這篇文章給出了爲何分區容忍重要的緣由you-cant-sacrifice-partition-tolerance

已經有測試證實 HBase面對網絡分區狀況時的正確性。

2.6 HBase使用場景

2.6.1 適用場景

  • 持久化存儲大量數據(TB、PB)
  • 對擴展伸縮性有要求
  • 須要良好的隨機讀寫性能
  • 簡單的業務KV查詢(不支持複雜的查詢好比表關聯等)
  • 可以同時處理結構化和非結構化的數據

訂單流水、交易記錄、須要記錄歷史版本的數據等

2.6.2 不適用場景

  • 幾千、幾百萬那種還不如使用RDBMS
  • 須要類型列(不過已經能夠用Phoniex on HBase解決這個問題)
  • 須要跨行事務,目前HBase只支持單行事務,須要跨行必須依賴第三方服務
  • SQL查詢(不過能夠用Phoniex on HBase解決這個問題)
  • 硬件太少,由於HBase依賴服務挺多,好比至少5個HDFS DataNode,1個HDFS NameNode(爲了安全還須要個備節點),一個Zookeeper集羣,而後還須要HBase自身的各節點
  • 須要表間Join。HBase只適合Scan和Get,雖然Phoenix支持了SQL化使用HBase,但Join性能依然不好。若是非要用HBase作Join,只能再客戶端代碼作

2.7 行/列存儲

2.7.1 簡介

HBase是基於列存儲的。本節對比下行列兩種存儲格式。
行/列存儲
從上圖能夠看到,行列存儲最大的不一樣就是表的組織方式不一樣。

2.7.2 數據壓縮

列式存儲,意味着該列數據每每類型相同,能夠採用某種壓縮算法進行統一壓縮存儲。

好比下面這個例子,用字典表的方式壓縮存儲字符串:

字典表
查詢Customers列爲MillerMaterial列爲Regrigerator的流程以下:
字典表查詢

  1. 分別去兩列的字典表找到對應的數字
  2. 將該數字回原表查詢,獲得行號組成的BitSet,即知足條件的行號位置的bit設爲1,其他爲0
  3. 將兩個BitSet相與,獲得最終結果BitSet
  4. 獲得最終行號爲6,去字典表拿出結果組裝返回便可

2.7.3 行列存儲對比

 
優勢 1.便於按行查詢數據,OLTP每每是此場景
2.便於行級插入、刪除、修改
3.易保證行級一致性
1.便於按列使用數據,如對列分組、排序、聚合等,OLAP不少是這樣
2.列數據同類型,便於壓縮
3.表設計靈活,易擴展列
缺點 1.當只需查詢某幾個列時,仍是會讀整行數據
2.擴展列代價每每較高
1.不便於按行使用數據
2.很難保證行級一致性
優化思想 讀取過程儘可能減小不須要的數據 提升讀寫效率
優化措施 1.設計表時儘可能減小冗餘列
2.內存中累積寫入到閾值再批量寫入
1.多線程方式並行讀取不一樣列文件
2.行級一致性,可經過加入RDBMS中回滾機制、校驗碼等
3.內存中累積寫入到閾值再批量寫入
應用場景 OLTP OLAP

3 HBase架構

HBase架構

3.1 Client

Client有訪問Hbase的接口,會去meta表查詢目標region所在位置(此信息會放入緩存),並鏈接對應RegionServer進行數據讀寫。

當master rebalance region時,Client會從新進行查找。

3.2 Zookeeper

  1. HMaster和RegionSerer都註冊到ZK上,使HMaster可感知RegionServer上下線。
  2. 選舉,保證HMaster HA。
  3. 保存.META.表所在RegionServer位置

3.3 HMaster

  1. 監控RegionServe狀態,併爲之分配Region,以維護整個集羣的負載均衡
  2. 經過HMasterInterface接口維護集羣的元數據信息,管理用戶對table的增刪改查操做
  3. Region Failover:發現失效的Region,就到正常的RegionServer上恢復該Region
  4. RegionSever Failover:由HMaster對其上的region進行遷移

3.4 HRegionServer

3.4.1 主要職責

  1. 處理用戶讀寫請求,並和底層HDFS的交互。咱們說RegionServer擁有某個region意思是這個region讀寫、flush之類的操做都是由當前regionserver管理的。若是該RegionServer本地沒有HDFS DataNode 底層數據就要從其餘DataNode節點遠程讀寫。
  2. 負責Region變大之後的split
  3. 負責Storefile的合併工做

3.4.2 HRegionServer組件

RegionServer

  • 一個RegionServer上存在多個Region和一個HLog讀寫實例。

  • HLog的就是WAL(Write-Ahead-Log),至關於RDBMS中的redoLog,寫數據時會先寫一份到HLog。能夠配置MultiWAL,多Region時使用多個管道來並行寫入多個WAL流。

    一個RS共用一個HLog的緣由是減小磁盤IO開銷,減小磁盤尋道時間。

  • Region屬於某個表水平拆分的結果(初始一個Region),每一個表的Region分部到多個RegionServer。

  • Region上按列族劃分爲多個Store

  • 每一個Store有一個MemStore,當有讀寫請求時先請求MemStore

  • 每一個Store又有多個StoreFile

  • HFiles是數據的實際存儲格式,他是二進制文件。StoreFile對HFile進行了封裝。HBase的數據在底層文件中時以KeyValue鍵值對的形式存儲的,HBase沒有數據類型,HFile中存儲的是字節,這些字節按字典序排列。

  • BlockCache

3.5 HDFS

爲HBase提供最終的底層數據存儲服務,多副本保證高可用性 .

  • HBase表的HDFS目錄結構以下
/hbase /data /<Namespace> (集羣裏的Namespaces) /<Table> (該集羣的Tables) /<Region> (該table的Regions) /<ColumnFamily> (該Region的列族) /<StoreFile> (該列族的StoreFiles) 
  • HLog的HDFS目錄結構以下
/hbase /WALs /<RegionServer> (RegionServers) /<WAL> (WAL files for the RegionServer) 

3.6 Region

3.6.1 概述

一個Region水平切分的示例:
Region水平切分

  • 一個RegionServer上存在多個Region和一個Hlog實例。
  • Region屬於某個表水平拆分的結果(初始一個Region),每一個表的Region分部到多個RegionServer。
  • Region上按列族劃分爲多個Store
  • 每一個Store有一個MemStore,當有讀寫請求時先請求MemStore。MemStore內部是根據RowKeyColumnVersion排序
  • 每一個Store又有多個StoreFile

3.6.2 Region分配

  • HMaster使用AssignmentManager,他會經過.META.表檢測Region分配的合法性,當發現不合法(如RegionServer掛掉)時,調用LoadBalancerFactory來重分配Region到其餘RS。
  • 分配完成並被RS打開後,須要更新.META.表。

3.6.3 Region Merge(合併)

可參考:

3.6.3.1 手動合併

注意,這裏說的是Region級別的合併,一旦手動觸發,HBase會不作不少自動化檢查,直接執行合併。

  • 手動合併目的
    一般是爲了執行major compaction,通常有三種目的:
    1. 線上業務可能由於自動major compaction影響讀寫性能,所以選擇低峯期手動觸發;
    2. 用戶在執行完alter操做以後但願馬上生效;
    3. 管理員發現硬盤容量不夠,手動觸發major compaction刪除大量過時數據;

該過程對Client來講是異步的,是Master和RegionServer共同參與,步驟以下:

  1. Client經過RPC發送請求給Master
  2. Master將regions移動到目標RS
  3. Master發送合併請求給此RS
  4. RS運行合併
3.6.3.2 自動合併

這裏指的是StoreFile級別的合併。

  • 緣由
    當MemStore不斷flush到磁盤,StoreFile會愈來愈多,從而致使查詢時IO次數增長,效率下降。以下圖
    在這裏插入圖片描述

  • 時機
    合併根據許多因素,可能有益於形同表現也有多是負面影響。

    • Memstore Flush
      flush後會檢查該Store的StoreFile數量是否超過閾值,超過就將該Region下的全部Store的StoreFile進行Compact。還須要注意的是,Flush後若是該Store的StoreFile數量若是超過了hbase.hstore.blockingStoreFiles,則會阻塞該Region的更新寫入操做,直到有Compact發生減小了StoreFile數量或等待until hbase.hstore.blockingWaitTime超時,而後繼續正常Flush。
    • 後臺線程週期性檢查
      除了判斷StoreFile數是否超限還要檢查Store中最先的StoreFile更新時間是否早於某個值,這樣的目的是爲了刪除過時數據。
    • 手動觸發
      好比避免業務高峯期MajorCompact影響業務、使用Alter命令後須要馬上生效、硬盤不夠須要緊急刪除大量過時、無效數據。
  • Compact過程
    HBase Compact過程,就是RegionServer按期將多個小StoreFile合併爲大StoreFile,具體以下:

    1. 篩選出須要合併的HFile list。
    2. HRegion建立StoreFileScannaer,來將待合併文件中的全部KeyValue讀出再按從小到大排序後寫入位於./tmp目錄下的臨時文件。此時就會忽略TTL過時數據。
    3. 將該臨時文件移動至對應Region的數據目錄。
    4. 將Compact輸入(合併前的StoreFile)/輸出(合併後的StoreFile)文件路徑封裝爲KeyValue後寫入WAL,並打上Compact標記,而後強制sync刷入磁盤。
    5. 最後將合併前的StoreFile刪除便可。這也就是LSM小樹合併爲大樹思想。
  • Compact影響

    • 讀放大
      合併操做的目的是增長讀的性能,不然搜索時要讀取多個文件,固然合併過程會有短期的IO消耗因此影響讀響應時間形成所謂讀放大,但能夠是的後續查詢延遲下降,以下圖:
      在這裏插入圖片描述

    • 寫放大
      若是在合併時不限制寫請求,當HFile生成速度大於合併速度時可能使得HFile愈來愈多,讀性能不斷降低,因此必須對此時寫請求進行限制。具體來講,若是任何一個Region的Store中存在超過hbase.hstore.blockingStoreFiles的StoreFiles,則會阻塞此Region的更新,直到Compact使得文件數低於該值或阻塞時間超出hbase.hstore.blockingWaitTime。這種阻塞行爲可在RS的日誌中查看到。

    • 影響小結
      Compact會使得數據讀取延遲一直比較平穩,但付出的代價是大量的讀延遲毛刺和必定的寫阻塞。

自動合併分爲Minor Compact和Major Compact:

Minor Compact

  • 合併概述:
    僅會挑選Store內少許小的、臨近的StoreFile進行合併,最理想是選到IO負載高但size較小的文件,合併後就能讀取較少的文件。Minor Compact結果是更少、更大的StoreFile。

    Minor Compact會合並TTL過時的數據:合併時會刪除這些TTL過時數據,再也不寫入合併後的StoreFile。(注意TTL刪除的數據無墓碑)

    Minor Compact通常速度很快,對業務的影響也比較小,就是使用短期的IO消耗以及帶寬消耗換取後續查詢的低延遲。

  • 合併過程

    1. 分別讀取出待合併的StoreFile文件的KeyValues,並順序地寫入到位於./tmp目錄下的臨時文件中
    2. 將臨時文件移動到對應的Region目錄中
    3. 將合併前的待合併文件路徑和輸出的已合併文件路徑封裝成KeyValues寫入WAL日誌,並打上compaction標記,最後強制自行sync
    4. 將對應region數據目錄下的合併的輸入文件所有刪除,合併完成
  • 輸出
    每一個Store合併完成的輸出是少許較大的StoreFile。

Major Compact

  • 合併過程
    將一個Store下的全部StoreFile爲一個StoreFile,此時會刪除那些無效的數據,耗費大量資源,持續時間較長,可能對服務能力有較大影響。通常來講線上業務會關閉自動Major Compact,而是選擇在業務低峯期手動來觸發:
    • 有更新時,老的數據就無效了,最新的那個<key, value>就被保留
    • 被刪除的數據,將墓碑<key,del>和舊的<key,value>都刪掉
    • 經過maxVersion制定了最大版本的數據,超出的舊版本數據會在合併時被清理掉再也不寫入合併後的StoreFile
    • 有Split的父Region的數據會遷移到拆分後的子Region
  • 輸出
    每一個Store合併完成的輸出是一個較大的StoreFile。
3.6.3.3 合併策略
3.6.3.3.1 關於Major Compact

默認Major Compact 7天執行一次,可能會致使異常開銷,影響系統表現,因此能夠進行手動調優。

3.6.3.3.2 ExploringCompactionPolicy

當前版本(1.2.0版)採用的合併策略爲ExploringCompactionPolicy,挑選最佳的StoreFiles集合以使用最少許的工做進行壓縮,能夠減小合併帶來的消耗。使用ExploringCompactionPolicy,主要Major Compact的頻率要低得多,由於Minor Compaction效率更高。

流程以下:

  1. 列出目標Store中全部現有的StoreFiles,待過濾此列表以提出將被選擇用於Compact的HFile子集。

  2. 若是這是手動執行的Compact,則嘗試執行手動請求的壓縮類型。

    請注意,即便用戶請求Major Compact,也可能沒法執行。緣由多是列族中某些StoreFiles不可Compact,或者由於列族中的StoreFiles太多。

  3. 某些StoreFiles會被自動排除掉:

    • 大於hbase.hstore.compaction.max.size的StoreFile
    • 使用hbase.mapreduce.hfileoutputformat.compaction.exclude排除的批量加載操做的StoreFile
  4. 遍歷步驟1中的列表,並列出全部可合併的StoreFiles Set,將他們合併成一個StoreFile。這裏的Set是指那些列表中的按hbase.hstore.compaction.min大小連續分組的StoreFile。對於每一個備選Set執行一些檢查來肯定最佳合併Set:

    1. 排除那些StoreFile數小於hbase.hstore.compaction.min或大於hbase.hstore.compaction.max的Set
    2. 比較該Set和list中已檢查過的最小的Set,若是本Set更小就做爲算法卡住時的fall back
    3. 依次對這組Set中的StoreFile進行基於大小的檢查:
      1. 排除大小大於hbase.hstore.compaction.max.size的StorFile
      2. 若是大小大於或等於hbase.hstore.compaction.min.size,則根據基於文件的比率進行健全性檢查,以查看它是否太大而排除掉。 若是符合如下條件,則完整性檢查成功:
        • 此Set中只有一個StoreFile
        • 對於每一個StoreFile的文件大小,須要小於該Set中的其餘全部HFile的總大小乘以hbase.hstore.compaction.ratio(若是配置非高峯時間且檢查時剛好是非高峯時間,則爲hbase.hstore.compaction.ratio.offpeak)的結果。(好比FileX = 5MB, FileY = 2MB, FileZ = 3MB,此時5 <= 1.2 x (2 + 3)=6,因此FileX篩選經過;FileX大小大於6MB就不經過Compact篩選。)
  5. 若是該Set經過篩選,請將其與先前選擇的最佳Compact Set進行比較。若是更好就替換。

  6. 當處理完整個潛在CompactFileList後,執行咱們已經找到的最佳Compact。

  7. 若是沒有成功選擇出Compact Set,但存在多個StoreFiles,則認爲此事屬於算法卡住,就執行步驟4.2中找到的最小Set進行Compact。

RatioBasedCompactionPolicy想找到第一個」可行解「便可,而ExploringCompactionPolicy卻在儘量的去尋求一種自定義評價標準中的」最優解「。

3.6.3.3.3 RatioBasedCompactionPolicy

HBase 0.96.x以前使用RatioBasedCompactionPolicy,從老到新掃描潛在可選CompactStoreFile,選擇那些沒有在CompactQueue且比比正在Compact的StoreFile更新的StoreFile組成List且按SequenceId排序。此時若是算法卡住,則強制執行開銷巨大的Major Compact。做爲對比,更優的ExploringCompactionPolicy則是Minor Compact最小Set。

接下來的步驟和ExploringCompactionPolicy基本相同。

但若是剩餘未遍歷的待合併文件list數量少於hbase.hstore.compaction.min,則放棄Minor Compact。一旦找到知足條件的第一個StoreFile Set就中止掃描並開始Compact。Minor Compact的peak規則以下:

  • 此StoreFile大小 < 其餘StoreFile總大小 * hbase.hstore.compaction.ratio。也可以使用hbase.hstore.compaction.ratio.offpeakhbase.offpeak.start.hour hbase.offpeak.end.hour配置非高峯期選項。

最後,若是檢查到最近一次Major Compact是好久之前的而且目前須要合併多個StoreFile,則會運行一個Major Compact,即便本應是Minor Compact。

3.6.3.3.4 DateTieredCompaction

以上合併策略選取文件未考慮最近寫入數據每每也更容易被讀取這一特色,因此仍是有缺陷。大量合併那些讀的不多的老文件是沒有必要的,由於他們合併後也不會對讀性能有不少提高。

DateTieredCompaction(日期分層合併)是一種日期感知的StoreFile合併策略,將StoreFile按日期分爲多個不一樣分區,並加入時間窗口概念,有利於的時序數據的time-range scan

性能提高:

  • Compact提高:
    具體來講,這樣可以使得新老數據在不一樣的Date分區,Compact也發生在不一樣的Date分區。
    • 老的數據不多讀不須要頻繁Compact,且特別老的數據永再也不合並。
    • 新的數據讀頻繁,合併後可減小掃描的文件數,減小了Compact開銷。
  • 讀提高
    讀數據也可直接從指定的Date分區讀取,在按時間讀取數據時候的效率提高很多,不需再遍歷全部文件。

適合場景:

  • 有限時間範圍的數據讀取,尤爲是對最近數據的scam
  • 好比微信朋友圈或者微博,新發的最有可能被別人看,但很長時間之前發的沒人或不多有人閱讀。這類HFile就能夠分爲舊和新兩類數據,將較新的和新的HFile合併,舊的一塊兒合併。

不適合的場景:

  • 無界的時間範圍的隨機Get
  • 頻繁刪除和更新
  • 因爲頻繁的亂序數據寫入會致使長尾,尤爲是具備將來時間戳的寫入
  • 頻繁的批量加載,且時間範圍有很大重疊

性能提高:

  • DateTieredCompaction性能測試代表,time-range scan的性能在有限的時間範圍內有很大改善,特別是對最近數據的scan。

要爲表或列族啓用DateTieredCompaction:

  • 需將hbase.hstore.engine.class設置爲org.apache.hadoop.hbase.regionserver.DateTieredStoreEngine
  • 需將hbase.hstore.blockingStoreFiles設置爲較大數字,例如60,而不是默認值12)。
  • 需將hbase.hstore.compaction.max設置爲與hbase.hstore.blockingStoreFiles相同的值,避免Major Compat時發生寫入阻塞。

DateTieredCompaction主要參數以下:

參數 含義 默認值
hbase.hstore.compaction.date.tiered.max.storefile.age.millis Storefile的最大Timestamp值比該參數還小的永不會被合併 Long.MAX_VALUE
hbase.hstore.compaction.date.tiered.base.window.millis 毫秒級的基礎時間窗口大小,後面會愈來愈大 6小時
hbase.hstore.compaction.date.tiered.windows.per.tier 每一個層級的增長的窗口倍數,好比爲2,則窗口大小變更爲6小時->12小時->24小時 4
hbase.hstore.compaction.date.tiered.incoming.window.min 在incoming窗口中Compact的最小文件數。 將其設置爲窗口中預期的文件數,以免浪費資源進行極少文件Compact 6
hbase.hstore.compaction.date.tiered.window.policy.class    
在同一時間窗口內挑選Storefile的策略,該策略不適用於incoming窗口。 ExploringCompactionPolicy  
hbase.regionserver.throughput.controller 推薦將Compact節流閥設爲org.apache.hadoop.hbase.regionserver.compactions.PressureAwareCompactionThroughputController,由於分層Compact中全部急羣衆的RS會同時提高窗口到高層級 -

下面是基礎窗口爲1小時,窗口成長倍數爲2,最小合併文件數爲3的一個例子:
在這裏插入圖片描述
能夠看到 [0-1)->[1->3)->[3-7) 三個窗口依次尋找,只有[3-7)這個窗口有3個文件知足了最小合併文件數,因此會被Compact。

若是HFile跨窗口,則會被計入時間更老的窗口。

3.6.3.3.5 StripeCompactionPolicy

在這裏插入圖片描述
StripeCompactionPolicy使用分層策略,分爲L0和L1層:

  • MemStore數據Flush後的HFile屬於L0,當L0的文件數達到可配的閾值後觸發寫入,即將L0數據讀取後寫入L1。

  • L1的數據按ROWKEY範圍進行劃分,劃分結果是多個戶不重疊的Stripe,思想可類比將Region拆分多個子Region。

    從L0寫入L1的KeyValue數據就是根據Key來定位到具體的某個Stripe。

StripeCompactionPolicy提高以下:

  • Compact提高
    注意,Stripe可類比子Region,因此Stripe內部也是會執行Minor/Major Compaction。可是由於作了拆分,因此相對來講說Compact操做消耗較小。具體來講,原來Major Compact須要合併Store下全部StoreFile,而如今只須要合併某Stripe內部全部StoreFile。
  • 讀取提高
    數據讀取的時候,直接根據Key來查找Stripe並查找便可
  • 讀寫穩定性提高

StripeCompactionPolicy的適用場景:

  • Region巨大。
    小的Region使用Stripe切分反而帶來額外沒必要要開銷。通常考慮的Region大小閾值爲2GB。
  • RowKey須要具備統一格式,才能進行Stripe切分。好比時間格式Key數據,就可讓老的數據不合並,只合並接收新數據的那些Stripe。
3.6.3.4 CompactThreadPool

HBase CompactSplitThead線程負責Compact和Split,內部又分爲Split線程池和用於Compact的largeCompactions、smallCompactions線程池。分入哪一個線程池的判斷依據:

  • 待compact的文件總大小大於hbase.regionserver.thread.compaction.throttle,則用largeCompactions。該閾值默認值爲2 x hbase.hstore.compaction.max(默認10) x hbase.hregion.memstore.flush.size(默認128MB)
  • 不然使用smallCompactions
  • 這兩個Compact線程池默認單線程,可經過hbase.regionserver.thread.compaction.largehbase.regionserver.thread.compaction.small修改。
3.6.3.5 合併/scan併發問題

scan查詢時遇到合併正在進行,解決此問題方案點這裏

3.6.3.5 合併限流

Compact或多或少會影響HBase其餘功能的表現,因此HBase在1.5以後有對Compact進行限流,2.x後默認會自動限流Compact,在壓力大時下降合併吞吐量,壓力小時增長。須要注意的是Compact限流是RegionServer級別,而非Compact級別。

具體來講:

  1. HBase根據系統外部壓力(即至關對於最大文件數閾值的當前StoreFile數量,越多則壓力越高),調整容許的合併時的吞吐量閾值(每秒寫入的字節範圍),可在給定的上下界之間變更(Compact實際會工做在吞吐量爲lower + (higer – lower) * pressureRatio的限制下,其中ratio是一個取值範圍在(0,1),它由當前store中待參與Compation的Hfile數量動態決定。文件數量越多,ratio越小,反之越大)。
  2. 若是HFile數量達到了hbase.hstore.blockingStoreFiles限制則阻塞MemStore Flush,直到Compact使得文件數低於該值或阻塞時間超出hbase.hstore.blockingWaitTime
  3. 若是MemStore Flush達到hbase.hregion.memstore.block.multiplier 乘以 hbase.hregion.memstore.flush.size字節時,會阻塞寫入,主要是爲了防止在update高峯期間MemStore大小失控,形成其flush的文件須要很長時間來compact或split,甚至形成OOM服務直接down掉。內存足夠大時,可調大該值。
  • 關於pressureRatio
    上文提到的pressureRatio默認爲flushPressurethroughput.controller設爲PressureAwareCompactionThroughputController時爲compactionPressure
    • compactionPressure=(當前HFile數-minFilesToCompact)/(blockingStoreFiles-minFilesToCompact),其中 minFilesToCompacthbase.hstore.compaction.min。因此該值越大說明堆積的HFile越多,越可能達到閾值致使寫入阻塞,須要加快合併,因此吞吐量限制閾值會變高。當pressureRatio大於1時,即當前HFile數大於blockingStoreFiles,發生寫入阻塞,此時會直接再也不限制合併吞吐量,瘋狂Compact。若是當前HFile數小於minFilesToCompact則不會發生合併。
    • flushPressure= globalMemstoreSize(當前MemStore總大小) / memstoreLowerLimitSize(hbase.regionserver.global.memstore.lowerLimit,RS級別MemStore flush下界)
      即MemStore總大小越大或memstoreLowerLimitSize越小則flush限制越寬鬆,發生flush後的HFile數越多,更可能形成flush阻塞。

主要配置以下:

參數 釋義 默認值
hbase.hstore.compaction.throughput.lower.bound 吞吐量下界,默認50MB/s 52428800
hbase.hstore.compaction.throughput.higher.bound 吞吐量上界,默認100MB/s 104857600
hbase.regionserver.throughput.controller RS吞吐量控制器,若想無限制設爲org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController;控制合併相關指標org.apache.hadoop.hbase.regionserver.compactions.PressureAwareCompactionThroughputController; 控制刷寫相關指標:PressureAwareFlushThroughputController
hbase.hstore.blockingStoreFiles 若是任何一個Region的Store中存在超過hbase.hstore.blockingStoreFiles的StoreFiles,則會阻塞此Region的MemStore flush,直到Compact使得文件數低於該值或阻塞時間超出hbase.hstore.blockingWaitTime。這種阻塞行爲可在RS的日誌中查看到。 16
hbase.hstore.compaction.ratio 對於MinorCompac,此比率用於肯定大於hbase.hstore.compaction.min.size的StoreFile是否有資格進行Compact,目的是限制大型StoreFile Compact。 1.2F
hbase.offpeak.start.hour 非高峯期的起始小時,[0-23] -1(禁用)
hbase.offpeak.end.hour 非高峯期的終止小時,[0-23] -1(禁用)
hbase.hstore.compaction.ratio.offpeak 非高峯時段使用的Compact ratio,默認很激進的策略,用來決定非高峯期時段內大型StoreFile被涵蓋在Compact內的策略。 表示爲浮點小數。 這容許在設定的時間段內更積極(或更低級,若是您將其設置爲低於hbase.hstore.compaction.ratio)的Compact。 若是禁用非高峯時則忽略本參數。 本參數與hbase.hstore.compaction.ratio的工做方式相同。具體能夠參考ExploringCompactionPolicy 5.0F

3.6.4 Region Split(拆分)

可參考Region切分細節

默認狀況下,HBase表初始建立時只有一個Region,放在一個RegionServer上。HBase有自動Split,也能夠pre-split或手動觸發split。

3.6.4.1 自動Split策略

本段轉自Hbase 技術細節筆記(下)

用到的參數主要是hbase.hregion.max.filesize,即HFile大小超過此值就Split。

HBase Region的拆分策略有比較多,好比除了3種默認過的策略,還有DelimitedKeyPrefixRegionSplitPolicy、KeyPrefixRegionSplitPolicy、DisableSplitPolicy等策略,這裏只介紹3種默認的策略。分別是ConstantSizeRegionSplitPolicy策略、IncreasingToUpperBoundRegionSplitPolicy策略和SteppingSplitPolicy策略。

  1. ConstantSizeRegionSplitPolicy
    是0.94版本以前的默認拆分策略,這個策略的拆分規則是:當region大小達到hbase.hregion.max.filesize(默認10G)後拆分。

    這種拆分策略對於小表不太友好,按照默認的設置,若是1個表的Hfile小於10G就一直不會拆分。注意10G是壓縮後的大小,若是使用了壓縮的話。若是1個表一直不拆分,訪問量小也不會有問題,可是若是這個表訪問量比較大的話,就比較容易出現性能問題。這個時候只能手工進行拆分。仍是很不方便。

  2. IncreasingToUpperBoundRegionSplitPolicy

    是Hbase的0.94~2.0版本默認的拆分策略,這個策略相較於ConstantSizeRegionSplitPolicy策略作了一些優化,該策略的算法爲:min(r^2*flushSize,maxFileSize ),最大爲maxFileSize 。

    從這個算是咱們能夠得出flushsize爲128M、maxFileSize爲10G的狀況下,能夠計算出Region的分裂狀況以下:
    第一次拆分大小爲:min(10G,11128M)=128M
    第二次拆分大小爲:min(10G,33128M)=1152M
    第三次拆分大小爲:min(10G,55128M)=3200M
    第四次拆分大小爲:min(10G,77128M)=6272M
    第五次拆分大小爲:min(10G,99128M)=10G
    第六次拆分大小爲:min(10G,1111128M)=10G

    從上面的計算咱們能夠看到這種策略可以自適應大表和小表,可是這種策略會致使小表產生比較多的小region,對於小表仍是不是很完美。

  3. SteppingSplitPolicy
    SteppingSplitPolicy是在Hbase 2.0版本後的默認策略,拆分規則爲:

    if region=1 then: flush size * 2 else: MaxRegionFileSize 

    仍是以flushsize爲128M、maxFileSize爲10場景爲列,計算出Region的分裂狀況以下:
    第一次拆分大小爲:2*128M=256M
    第二次拆分大小爲:10G

    從上面的計算咱們能夠看出,這種策略兼顧了ConstantSizeRegionSplitPolicy策略和IncreasingToUpperBoundRegionSplitPolicy策略,對於小表也有比較好的適配。

通常狀況下使用默認切分策略便可,也能夠在cf級別設置region切分策略,命令爲:

create ’table’, {NAME => ‘cf’, SPLIT_POLICY => ‘org.apache.hadoop.hbase.regionserver. ConstantSizeRegionSplitPolicy'} 
3.6.4.2 自動Split

可參考

注意:Split過程是RegionServer進行的,沒有Master參與。

  1. 當Region大小超過必定閾值後,RS會把該Region拆分爲兩個(Split),
  2. 將原Region作離線操做
  3. 把新生成的Region放入.META.表。
  4. 打開新Region,以使得可訪問
  5. 通知Master該次Split,可按需用自動平衡等機制遷移子Region到其餘RegionServer。
    RegionSplit

上圖中,綠色箭頭爲客戶端操做;紅色箭頭爲Master和RegionServer操做:

  1. RegionServer決定拆分Region,並準備拆分。此時,Split事務已經開始。RegionServer在表上獲取共享讀鎖,以防止在他人在拆分過程當中修改表的schema;而後在ZK的/hbase/region-in-transition/region-name下建立一個znode,並將該節點狀態設置爲SPLITTING

  2. Master在/hbase/region-in-transition設置了Watcher,因此會感知到這個znode變動,從而得知該split事件,在Master頁面RIT模塊能夠看到region執行split的狀態信息。

  3. RegionServer在HDFS的/hbase/region-in-transition/region-name目錄下建立一個名爲.splits的子目錄。

  4. RegionServer關閉該待split的Region,並在其本地數據結構中將該Region標記爲離線狀態。被分裂的Region如今處於離線狀態。此時,若是客戶端請求該Region將拋出NotServingRegionException,客戶端將自動重試

  5. RegionServer在父Region.splits目錄下爲子Region A和B建立目錄和必要的數據結構。而後它將拆分StoreFiles,在子Region目錄中爲每一個父Region的StoreFile建立兩個指針reference文件來指向父Region的文件。

    reference文件名的前半部分是父Region對應的HFile文件名,.號後的部分是父Region名稱。

    文件內容主要有兩部分構成:

    • 切分點splitkey
    • boolean類型的變量(true或者false),true表示該reference文件引用的是父文件的上半部分(top),而false表示引用的是下半部分 (bottom)。
  6. RegionServer爲子Region們在HDFS中建立實際的Region目錄,並移動每一個子Region的指針文件。

  7. RegionServer向.META表發送Put請求,將父Region設置爲離線,並添加子Region的信息(此時客戶端不可見)。在.META表更新即將父Region Offline列設爲true,Region拆分將由Master推動。
    在這裏插入圖片描述
    若是在 RPC 成功以前 region server 就失敗了,master和下次打開parent region的region server 會清除關於此次split的髒狀態。可是當RPC返回結果給到parent region ,即.META.成功更新以後,region split的流程還會繼續進行下去。至關因而個補償機制,下次在打開這個parent region的時候會進行相應的清理操做。

  8. RegionServer並行打開子Region A和B.

  9. RegionServer將子Region A和B的信息添加到.META表,具體來講在.META表更新即將子Region Offline列設爲false。此時,這些子Region如今處於在線狀態。在此以後,客戶端能夠發現新Region並向他們發出請求了。客戶端會緩存.META到本地,但當他們向RegionServer或.META表發出請求時,原先的ParentRegion的緩存將失效,此時將從.META獲取新Region信息。

  10. RegionServer將ZooKeeper中的znode/hbase/region-in-transition/region-name更新爲狀態SPLIT,Master能夠感知到該事件。若有必要,平衡器能夠自由地將子Region從新分配給其餘RegionServer。Split拆分事務現已完成

  11. 拆分完成後,.META和HDFS仍將包含對父Region的引用。當子Region進行Major Compact,讀取父Regionx相應數據進行數據文件重寫時,才刪除這些引用。當檢查線程發現SPLIT=TRUE的父Region對應的子Region已經沒有了索引文件時,就刪除父Region文件。Master的GC任務會按期檢查子Region是否仍然引用父Region的文件。若是不是,則將刪除父Region。

    也就是說,Region自動Split並不會有數據遷移,而只是在子目錄建立了到父Region的引用。而當Major Compact時纔會進行數據遷移,在此以前查詢子Region數據流程以下:
    子Region查詢
    在這裏插入圖片描述

  • 關於rollback:
    若是上述execute階段出現異常,則將執行rollback操做,根據當前進展到哪一個子階段來清理對應的垃圾數據,代碼中使用 JournalEntryType 來表徵各階段:
    在這裏插入圖片描述
  • 關於RegionSplit的事務性
    在HBase2.0以後,實現了新的分佈式事務框架Procedure V2(HBASE-12439),將會使用HLog存儲這種單機事務(DDL、Split、Move等操做)的中間狀態,可保證在事務執行過程當中參與者發生了宕機依然能夠使用HLog做爲協調者對事務進行回滾操做或者重試提交
  • 關於中間狀態RIT和HBCK
    可參考HBase HBCK2
3.6.4.3 pre-split預分區

在如下狀況能夠採用預分區(預Split)方式提升效率:

  • rowkey按時間遞增(或相似算法),致使最近的數據所有讀寫請求都累積到最新的Region中,形成數據熱點。

  • 擴容多個RS節點後,能夠手動拆分Region,以均衡負載

  • BulkLoad大批數據前,可提早拆分Region以免後期因頻繁拆分形成的負載

  • 爲避免數據rowkey分佈預測不許確形成的Region數據熱點問題,最好的辦法就是首先預測split的切分點作pre-splitting,之後都讓auto-split來處理將來的負載均衡。

  • 官方建議提早爲預分區表在每一個RegionServer建立一個Region。若是過多可能會形成不少表擁有大量小Region,從而形成系統崩潰。

  • 注意合理分區方式
    好比採用Admin.createTable(byte[] startKey, byte[] endKey, 10)構建的"0000000000000000" 到 "ffffffffffffffff"的預分區以下:
    在這裏插入圖片描述
    這種分區方式就會由於數據的rowkey範圍是[0-9][a-f]從而使得僅有1,2,10號Region有數據,而其餘Region無數據。

  • 例子

    # 建立了t1表的f列族,有4個預分區 create 'test_0807_1','cf1',SPLITS => ['10','20','30'] # 指定切分點建預分區表 create 'test_0807_2','cf1',SPLITS => ['\x10\x00', '\x20\x00', '\x30\x00', '\x40\x00'] # 建表,使用隨機字節數組來進行預分4個Region create 'test_0807_3','cf1', { NUMREGIONS => 4 , SPLITALGO => 'UniformSplit' } # 建表,假設RowKey都是十六進制字符串來進行拆分,預分5個Region create 'test_0807_4','cf1', { NUMREGIONS => 5, SPLITALGO => 'HexStringSplit' } 
3.6.4.4 pre-split算法

通常來講,手動拆分是彌補rowkey設計的不足。咱們拆分region的方式必須依賴數據的特徵:

  • 字母/數字類rowkey
    可按範圍劃分。好比A-Z的26個字母開頭的rowkey,可按[A, D]…[U,Z]這樣的方式水平拆分Region。

  • 自定義算法
    HBase中的RegionSplitter工具可根據特色,傳入算法、Region數、列族等,自定義拆分:

    • HexStringSplit
      假設RowKey都是十六進制字符串來進行拆分。

    只需傳入要拆分的Region數量,會將數據從00000000FFFFFFFF之間的數據長度按照N等分,並算出每一分段的startKey和endKey來做爲拆分點。

    • UniformSplit
      假設RowKey都是隨機字節數組來進行拆分。與HexStringSplit不一樣的是,起始結束不是String而是byte[]。
    • DecimalStringSplit
      假設RowKey都是00000000到99999999範圍內的十進制字符串
    • 可以使用SplitAlgorithm開發自定義拆分算法
3.6.4.5 手動強制split

HBase 容許客戶端強制執行split,在hbase shell中執行如下命令:

//其中forced_table 爲要split的table , ‘b’ 爲split 點
split 'forced_table', 'b' 

更多內容能夠閱讀這篇文章Apache HBase Region Splitting and Merging

3.6.5 Region狀態

HBase的HMaster負責爲每一個Region維護了狀態並存在META表,持久化到Zookeeper。

  • OFFLINE: Region離線且未打開
  • OPENING: Region正在打開過程當中
  • OPEN: Region已經打開,且RegionServer已經通知了Master
  • FAILED_OPEN: RegionServer打開該Region失敗
  • CLOSING: Region正在被關閉過程當中
  • CLOSED: Region已經關閉,且RegionServer已經通知了Master
  • FAILED_CLOSE: RegionServer關閉該Region失敗
  • SPLITTING: RegionServer通知了Master該Region正在切分(Split)
  • SPLIT: RegionServer通知了Master該Region已經結束切分(Split)
  • SPLITTING_NEW: 該Region是由正在進行的切分(Split)建立
  • MERGING: RegionServer通知了Master該Region和另外一個Region正在被合併
  • MERGED: RegionServer通知了Master該Region已經被合併完成
  • MERGING_NEW: 該Region正在被兩個Region的合併所建立

Region狀態轉移
上圖顏色含義以下:

  • 棕色:離線狀態。是一個特殊的瞬間狀態。
  • 綠色:在線狀態,此時Region能夠正常提供服務接受請求
  • 淺藍色:瞬態
  • 紅色:失敗狀態,須要引發運維人員會系統注意,手動干預
  • 黃色:Region切分/合併後的引發的終止狀態
  • 灰色:由切分/合併而來的Region的初始狀態

具體狀態轉移說明以下:

  1. OFFLINE->OPENING
    Master將Region從OFFLINE狀態移動到OPENING狀態,並嘗試將該Region分配給RegionServer。 Master會重試發送請求直到響應經過或重試次數達到閾值。RegionServer收到該請求後開始打開該Region。

  2. OPENING->CLOSING
    若是Master沒有重試,且以前的請求超時,就認爲失敗,而後將該Region設爲CLOSING並試圖關閉它。即便RegionServer已經開始打開該區域也會這麼作。若是Master沒有重試,且以前的請求超時,就認爲失敗,而後將該Region設爲CLOSING並試圖關閉它。即便RegionServer已經開始打開該區域也會這麼作。

  3. OPENING->OPEN
    在RegionServer打開該Region後,通知Master直到Master將該Region狀態變動爲OPEN狀態並通知RegionServer。

  4. OPENING->CLOSED
    若是RegionServer沒法打開該Region,則會通知Master將Region轉移爲CLOSED,並嘗試在其餘的RegionServer上打開該Region。

  5. OPENING->FAILED_OPEN
    若是Master沒法在任何RegionServer中打開該Region,則會將該Region設爲到FAILED_OPENHBase shell進行手動干預或服務器中止以前不會再有其餘操做

  6. OPEN->CLOSING
    Master將Region從OPEN狀態轉到CLOSING狀態。持有該Region的RegionServer可能已/未收到該關閉請求。Master重試發送關閉請求,直到RPC經過或重試次數達到閾值。

  7. CLOSING->OFFLINE
    若是RegionServer離線或拋出NotServingRegionException,則Master將該Region移至OFFLINE狀態,並將其從新分配給其餘RegionServer。

  8. CLOSING->FAILED_CLOSE
    若是RegionServer處於在線狀態,但Master重試發送關閉請求達到閾值,則會將該Region設爲FAILED_CLOSE狀態,而且在管理員從HBase shell進行干預或服務器已中止以前不會採起進一步操做。

  9. CLOSING->CLOSED
    若是RegionServer獲取到關閉Region請求,它將關閉該Region並通知Master將該Region設爲CLOSED狀態,並將其從新分配給其餘RegionServer。

  10. CLOSED->OFFLINE
    在分配Region以前處於CLOSED狀態,則Master會自動將Region移動到OFFLINE狀態。

  11. OPEN->SPLITING
    當RegionServer即將拆分Region時,它會通知Master將要拆分的Region從OPEN切換到SPLITTING狀態,並將拆分後要建立的兩個新Region添加到RegionServer。這兩個Region最初處於SPLITTING_NEW狀態。

  12. SPLITING->SPLIT
    通知Master後,RegionServer開始拆分Region。一旦超過no return點,RegionServer就會再次通知Master以便更新META表。但在通知拆分完成以前,Master不會真正更新Region狀態。若是拆分紅功,則拆分區域將從SPLITTING移至SPLIT狀態,而且兩個新Region將從SPLITTING_NEW移至OPEN狀態(13)。

  13. SPLITTING->OPEN, SPLITTING_NEW->OFFLINE
    若是拆分失敗,則拆分Region將從SPLITTING移回OPEN狀態,而且建立的兩個新Region將從SPLITTING_NEW移至OFFLINE狀態。

  14. OPEN->MERGING
    當RegionServer即將合併兩個Region時會首先通知Master。Master將要合併的兩個Region從OPEN遷移到MERGING狀態,並將用來保存合併後內容的新Region添加到RegionServer。新Region最初處於MERGING_NEW狀態。

  15. MERGING->MERGED, MERGING_NEW->OPEN
    通知Master後,RegionServer開始合併這兩個Region。一旦超過no return點,RegionServer就會再次通知r,以便Master能夠更新META。但在通知合併已完成以前,Master不會更新區域狀態。若是合併成功,則兩個合併Region從MERGING狀態轉移到MERGED狀態;新Region從MERGING_NEW移動到OPEN狀態。

  16. MERGING->OPEN, MEGING_NEW->OFFLINE
    若是兩個Region合併失敗,則他們會從MERGING移回到OPEN狀態;而那個用於存放合併後內容的新Region則從MERGING_NEW轉移到OFFLINE狀態。

  17. FAILED_CLOSE->CLOSING, FAILED_OPEN->CLOSING
    對於處於FAILED_OPENFAILED_CLOSE狀態的Region,當Master經過HBase Shell從新分配它們時會先嚐試再次關閉它們。

3.6.6 負載均衡

由Master的LoadBalancer線程週期性的在各個RegionServer間移動region維護負載均衡。

3.6.7 Failover

請點擊這裏

3.6.8 Region不能過多

可參考官網-Why should I keep my Region count low?
官方推薦每一個RegionServer擁有100個左右region效果最佳,控制數量的緣由以下:

  1. MSLAB
    在這裏插入圖片描述
    在這裏插入圖片描述
    HBase的一個特性MSLAB(MemStore-local allocation buffer,Memstore內存本地分配緩衝,將JVM Heap分爲不少Chunk) ,它有助於防止堆內存的碎片化,減輕Full GC的問題(CMS會由於是標記-清除算法而致使老年代內存碎片,碎片太小沒法分配新對象致使FullGC整理內存),默認開啓,但他與MemStore一一對應,每一個就佔用2MB空間。好比一個HBase表有1000個region,每一個region有2個CF,那也就是不存儲數據就佔用了3.9G內存空間,若是極多可能形成OOM須要關閉此特性。

    HBase不適用TLAB線程私有化分配的緣由是一個線程管理了多個Region的多個MemStore,沒法隔離各個MemStore內存。好比5個Region(A-E),而後分別寫入ABCDEABCEDDAECBACEBCED,而後B的MemStore發生了Flush,內存狀況如今是:A CDEA CEDDAEC ACE CED,顯然產生了內存碎片!若是後面的寫入仍是如以前同樣的大小,不會有問題,但一旦超過就沒法分配。也就是說,一個Region的MemStore內的內容其實在老年代內存物理地址上並不連續。因而HBase參考TLAB實現了一套以MemStore爲最小分配單元的內存管理機制MSLAB。

    RegionServer維護了一個全局MemStoreChunkPool實例,而每一個MemStore實例又維護了一個MemStoreLAB實例,每一個MemStore獨佔若干Chunk。因此MemStore收到KeyValue數據後先從MemStoreChunkPool中申請一個Chunk放入數據(放入curChunk並移動偏移量),放滿了就從新申請Chunk來放數據。該過程是LockFree的,基於CAS。

    若是MemStore由於flush而釋放內存,則以chunk爲單位來清理內存,避免內存碎片。注意,雖然能解決內存碎片,但會由於Chunk放小數據而下降內存利用率。

    MSLAB還有一個好處是使得本來分開的MemStore內存分配變爲老年代中連續的Chunk內分配。

    最大的好處就是幾乎徹底避免了GC STW!

  2. 每一個Region的每一個列族就有一個Memstore,Region過多那麼MemStore更多,總內存過大頻繁觸發Region Server級別閾值致使Region Server級別flush,會對用戶請求產生較大的影響,可能阻塞服務響應或產生compaction storm(由於Region過多致使StoreFile也過多,不斷合併)。

  3. HMaster要花大量的時間來分配和移動Region,且過多Region會增長ZooKeeper的負擔。

  4. 默認MR任務一個region對應一個mapper,region太多會形成mapper任務過多。

推薦的每一個RegionServer的Region數量公式以下:
((RS memory) * (total memstore fraction)) / ((memstore size)*(# column families))

3.6.9 Region不能過大

在生產環境中若是Region過大會形成compaction尤爲是major compaction嚴重影響性能,目前推薦的region最大10-20Gb,最優5-10Gb。參數hbase.hregion.max.filesize控制單個region的HFile總大小最大值,再大就會觸發split了:

  1. hbase.hregion.max.filesize比較小時,觸發split的機率更大,系統的總體訪問服務會出現不穩定現象。
  2. 當hbase.hregion.max.filesize比較大時,因爲長期得不到split,所以同一個region內發生屢次compaction的機會增長了。這樣會下降系統的性能、穩定性,所以平均吞吐量會受到一些影響而降低。

3.6.10 Region-RegionServer Locality(本地性)

RegionServer本地性是經過HDFS Block副本實現。

當某個RS故障後,其餘的RS也許會由於Region恢復而被Master分配非本地的Region的StoreFiles文件(其實就是以前掛掉的RS節點上的StoreFiles的HDFS副本)。但隨着新數據寫入該Region,或是該表被合併、StoreFiles重寫等以後,這些數據又變得相對來講本地化了。

3.7 Region元數據-META表

3.7.1 概述

Region元數據詳細信息存於.META.表(沒錯,也是一張HBase表,只是HBase shelllist命令看不到)中(最新版稱爲hbase:meta表),該表的位置信息存在ZK中。

該表的結構以下:

  • Key
    該Region的key信息,格式:([table],[region start key],[region id])

  • Values

    • info:regioninfo
      該Region對應的序列化了的HRegionInfo實例
    • info:server
      包含該Region的RegionServer的server:port
    • info:serverstartcode
      包含該Region的RegionServer的進程啓動時間

3.7.2 表結構

  • Key
    ([table],[region start key],[region id])
  • Values
    info:regioninfo
    info:server (包含該Region的RegionServer之server:port)
    info:serverstartcode (包含該Region的RegionServer啓動時間)

3.8 MemStore

3.8.1 概述

一個Store有一個MemStore,保存數據修改。當flush後,當前MemStore就被清理了。

注意,MemStorez中的數據按 RowKey 字典升序排序。

  • MemStore存在的意義:
  1. HBase須要將寫入的數據順序寫入HDFS,但因寫入的數據流是未排序的及HDFS文件不可修改特性,因此引入了MemStore,在flush的時候按 RowKey 字典升序排序進行排序再寫入HDFS。
  2. 充當內存緩存,在更可能是訪問最近寫入數據的場景中十分有效
  3. 可在寫入磁盤前進行優化,好比有多個對同一個cell進行的更新操做,那就在flush時只取最後一次進行刷盤,減小磁盤IO。

3.8.2 Flush

  • 注意:Memstore Flush最小單位是Region,而不是單個MemStore。
3.8.2.1 flush過程

爲了減小flush過程對讀寫影響,HBase採用了相似於2PC的方式,將整個flush過程分爲三個階段:

  1. prepare
    遍歷當前Region中的全部Memstore,將Memstore中當前數據集kvset作一個快照snapshot,對後來的讀請求提供服務,讀不到再去BlockCache/HFile中查找。

    而後再新建一個新的kvset Memstore(SkipList),服務於後來的寫入。

    prepare階段須要加一把寫鎖對寫請求阻塞,結束以後會釋放該鎖。由於此階段沒有任何費時操做,所以持鎖時間很短。

  2. flush
    遍歷全部Memstore,將prepare階段生成的snapshot持久化爲臨時文件,臨時文件會統一放到目錄.tmp下。這個過程由於涉及到磁盤IO操做,所以相對比較耗時,但不會影響讀寫。

  3. commit
    遍歷Region全部的Memstore,將flush階段生成的臨時文件移到指定的ColumnFamily目錄下,針對HFile生成對應的storefileReader,隨後把storefile添加到HStore的storefiles列表中。最後清空prepare階段生成的snapshot。

  4. 整個flush過程還可能涉及到compact和split

3.8.2.2 Snapshot-快照讀

當Flush發生時,當前MemStore實例會被移動到一個snapshot中,而後被清理掉。在此期間,新來的寫操做會被新的MemStore和剛纔提到的備份snapshot接收,直到flush成功後,snapshot纔會被廢棄。

3.8.2.3 MemStore Flush時機
  • Region級別-跨列族
    Region內的其中一個MemStore大小達到閾值(hbase.hregion.memstore.flush.size),該Region全部MemStore一塊兒發生Flush,輸入磁盤。

  • RegionServer級別
    當一個RS內的所有MemStore使用內存總量所佔比例達到了閾值(hbase.regionserver.global.memstore.upperLimit),那麼會一塊兒按Region的MemStore用量降序排列flush,直到下降到閾值(hbase.regionserver.global.memstore.lowerLimit)如下。

    另有一個新的參數hbase.regionserver.global.memstore.size,設定了一個RS內所有Memstore的總大小閾值,默認大小爲Heap的40%,達到閾值之後就會阻塞更新請求,並開始RS級別的MemStore flush,和上述行爲相同。

  • HLog-WAL文件
    當region server的WAL的log數量達到hbase.regionserver.max.logs,該server上多個region的MemStore會被刷寫到磁盤(按照時間順序),以下降WAL的大小。不然會致使故障恢復時間過長。

  • 手動觸發
    經過HBase shell或Java Api手動觸發MemStore flush

3.8.2.4 其餘Flush重要點
  • MemStore Flush不會更新BlockCache
    不會形成從BlockCache讀到髒數據:

    • 若是是Get最新版本,則會先搜索MemStore,若是有就直接返回,不然須要查找BlockCache和HFile且作歸併排序找到最新版本返回。
    • 若是是查找多個版本,則會先搜索MemStore,若是有足夠的版本就返回,不然還須要查找BlockCache和HFile且作歸併排序找到足夠多的的最新版本返回。
  • Flush阻塞
    當MemStore的數據達到hbase.hregion.memstore.block.multiplier 乘以 hbase.hregion.memstore.flush.size字節時,會阻塞寫入,主要是爲了防止在update高峯期間MemStore大小失控,形成其flush的文件須要很長時間來compact或split,甚至形成OOM服務直接down掉。內存足夠大時,可調大該值。

    因此,針對此,咱們須要避免Region數量或列族數量過多形成MemStore太大。

3.8.2.5 HBase 2.0 Flush

可參考hbase實踐之flush and compaction

增長了內存中Compact邏輯。MemStore變爲由一個可寫的Segment,以及一個或多個不可寫的Segments構成。

3.8.3 Snapshot

MemStore Flush時,爲了不對讀請求的影響,MemStore會對當前內存數據kvset建立snapshot,並清空kvset的內容。

讀請求在查詢KeyValue的時候也會同時查詢snapshot,這樣就不會受到太大影響。可是要注意,寫請求是把數據寫入到kvset裏面,所以必須加鎖避免線程訪問發生衝突。因爲可能有多個寫請求同時存在,所以寫請求獲取的是updatesLockreadLock,而snapshot同一時間只有一個,所以獲取的是updatesLockwriteLock

3.8.4 寫入

數據修改操做先寫入MemStore,在該內存爲有序狀態。

3.8.5 讀取

先查MemStore,查不到再去查StoreFile。

Scan具體讀取步驟以下:

  1. 客戶端對table發起scan操做時,HBase的RegionServer會爲每一個region構建一個RegionScanner

  2. RegionScanner包含一個StoreScanner列表,每一個列族建立了一個StoreScanner

  3. StoreScanner又爲每一個StoreFile建立了一個StoreFileScanner構成list,以及爲MemStore傳了一個KeyValueScanner列表。

  4. 上述兩個列表最終會合併爲一個最小堆(實際上是優先級隊列),其中的元素是上述的兩類scanner,元素按seek到的keyvalue大小按升序排列。

    HBase中,Key大小首先比較RowKey,RowKey越小Key就越小;RowKey若是相同就看CF,CF越小Key越小;CF若是相同看Qualifier,Qualifier越小Key越小;Qualifier若是相同再看Timestamp,Timestamp越大表示時間越新,對應的Key越小;若是Timestamp還相同,就看KeyType,KeyType按照DeleteFamily -> DeleteColumn -> Delete -> Put 順序依次對應的Key愈來愈大。

  5. 當構建StoreFileScanner後,會自動關聯一個MultiVersionConcurrencyControl Read Point,他是當前的MemStore版本,scan操做只能讀到這個點以前的數據。ReadPoint以後的更改會被過濾掉,不能被搜索到。這也就是所謂的讀提交(RC)。

  6. 查詢的scanner會組成最小堆,每次pop出堆頂的那個scanner seek到的KeyValue,進行以下斷定:

  • 檢查該KeyValue的KeyType是不是Deleted/DeletedCol等,若是是就直接忽略該列全部其餘版本,跳到下列(列族)
  • 檢查該KeyValue的Timestamp是否在用戶設定的Timestamp Range範圍,若是不在該範圍,忽略
  • 檢查該KeyValue是否知足用戶設置的各類filter過濾器,若是不知足,忽略
  • 檢查該KeyValue是否知足用戶查詢中設定的versions,好比用戶只查詢最新版本,則忽略該cell的其餘版本;反之,若是用戶查詢全部版本,則還須要查詢該cell的其餘版本。
  • 上一步檢查KeyValue檢查完畢後,會對當前堆頂scanner執行next方法檢索下一個scanner,並從新組織最小堆,又會按KeyValue排序規則從新排序組織。不斷重複這個過程,直到一行數據被所有查找完畢,繼續查找下一行數據。

更多關於數據讀取流程具體到scanner粒度的請閱讀HBase原理-數據讀取流程解析

3.9 Storefile

3.9.1 概述

一個Store有>=0個SotreFiles(HFiles)。

StoreFiles由塊(Block)組成。塊大小( BlockSize)是基於每一個列族配置的。壓縮是以塊爲單位。

3.9.2 HFile

可參考:

注:目前HFile有v1 v2 v3三個版本,其中v2是v1的大幅優化後版本,v3只是在v2基礎上增長了tag等一些小改動,本文介紹v2版本。

HFile格式基於BigTable論文中的SSTable。StoreFile對HFile進行了輕度封裝。HFile是在HDFS中存儲數據的文件格式。它包含一個多層索引,容許HBase在沒必要讀取整個文件的狀況下查找數據。這些索引的大小是塊大小(默認爲64KB),key大小和存儲數據量的一個重要因素。

注意,HFile中的數據按 RowKey 字典升序排序。

  • 數據分佈
    在這裏插入圖片描述
    • Scanned block section
      scan(順序掃描)HFile時全部的Block將被讀取
    • Non-scanned block section
      scan時數據不會被讀取
    • Load-on-open-section
      RegionServer啓動時須要加載到內存
    • Trailer
      記錄了HFile的基本信息,保存了上述每一個段的偏移量(即起始位置)
  • HFIle Block
    在這裏插入圖片描述
    這裏的Block是指一個寬泛的概念,主要包括:
    • BlockHeader
      • BlockType
        • DATA – KeyValue數據
        • ROOT_INDEX – 多層DataBlock索引中的根索引塊
        • INTERMEDIATE_INDEX – 多層DataBlock索引中的中間層索引塊
        • LEAF_INDEX – 多層DataBlock索引中的葉節點
        • META – 存放元數據 ,V2後再也不跟布隆過濾器相關
        • FILE_INFO – 文件信息,極小的key-value元數據
        • BLOOM_META – 布隆過濾器元數據
        • BLOOM_CHUNK – 布隆過濾器
        • TRAILER – 固定大小,記錄各Block段偏移值
        • INDEX_V1 – 此塊類型僅用於傳統的HFile v1塊
      • CompressedBlockSize
        DataBlock不包括header的其餘部分的壓縮後大小,可子scan時被用來跳過當前HFile
      • UncompressedBlockSize
        DataBlock不包括header的其餘部分的未壓縮大小
      • PrevBlockOffset
        前一個相同類型的Block的Offset,可被用來尋找前一個Block
    • BlockData
      壓縮後的數據(爲指定壓縮算法時直接存)
  • HFile各部分詳解
    在這裏插入圖片描述
    在這裏插入圖片描述
    • DataBlock

      • 保存表中的數據,可被壓縮,大小默認爲64K(由建表時建立cf時指定或者HColumnDescriptor.setBlockSize(size))。
      • 每個DataBlock由MagicHeader和一些KeyValue組成,key的值是嚴格按照順序存儲的。
      • 在查詢數據時,是以DataBlock爲單位從硬盤load到內存,順序遍歷該塊中的KeyValue。
    • DataIndex
      DataBlock的索引,每條索引的key是被索引的block的第一條記錄的key(StartKey),採用LRU機制淘汰,能夠有多級索引。

      格式爲:(MagicHeader,(DataBlock在HFile的offset + DataBlockLength + DataBlockFirstKey),(DataBlock在HFile的offset + DataBlockLength + DataBlockFirstKey),……..)

    • MetaBlock (可選的)
      保存用戶自定義的KeyValue,可被壓縮,如BloomFilter就是存在這裏。該塊只保留value值,key值保存在元數據索引塊中。每個MetaBlock由header和value組成,能夠被用來快速判斷指定的key是否都在該HFile中。

    • MetaIndex (可選的)
      MetaBlock的索引,只有root級索引,保存在MetaBlock。

      格式爲:(MagicHeader,(MetaBlock在HFile的offset + MetaBlockLength + MetaBlockName),(MetaBlock在HFile的offset + MetaBlockLength + MetaBlockName),……..)

    • BloomIndex
      做爲布隆過濾器MetaData的一部分存儲在RS啓動時加載區域。

    • FileInfo
      HFile的元數據,固定長度,不可壓縮,它紀錄了文件的一些Meta信息,例如:AVG_KEY_LENAVG_VALUE_LENLAST_KEYCOMPARATORMAX_SEQ_ID_KEY等,用戶也能夠在這一部分添加自定義元數據。

    • Trailer
      Trailer是定長的,保存了上述每一個段的偏移量(即起始位置),因此讀取一個HFile時會先讀取Trailer,(段的MagicHeader被用來作安全check),隨後讀取DataBlockIndex到內存中。

      這樣一來,當檢索某個key時,不須要掃描整個HFile,而只需從內存中的DataBlockIndex找到key所在的DataBlock,隨後經過一次磁盤io將整個DataBlock讀取到內存中,再找到具體的KeyValue。

      Trailer部分格式爲:
      在這裏插入圖片描述
      其中CompressionCodec爲int,壓縮算法爲enum類型,表示壓縮算法:LZO-0,GZ-1,NONE-2。

3.9.3 HFile-Block

HFileBlock默認大小是64KB,而HadoopBlock的默認大小爲64MB。順序讀多的狀況下可配置使用較大HFile塊,隨機訪問多的時候可以使用較小HFile塊。

不只是DataBlock,DataBlockIndex和BloomFilter都被拆成了多個Block,均可以按需讀取,從而避免在Region Open階段或讀取階段一次讀入大量的數據而真正用到的數據其實就是不多一部分,可有效下降時延。

HBase同一RegionServer上的全部Region共用一份讀緩存。當讀取磁盤上某一條數據時,HBase會將整個HFile block讀到cache中。此後,當client請求臨近的數據時可直接訪問緩存,響應更快,也就是說,HBase鼓勵將那些類似的,會被一塊兒查找的數據存放在一塊兒。

注意,當咱們在作全表scan時,爲了避免刷走讀緩存中的熱數據,記得關閉讀緩存的功能(由於HFile放入LRUCache後,不用的將被清理)

更多關於HFile BlockCache資料請查看HBase BlockCache 101

3.9.4 HFile索引

初始只有一層,數據多時分裂爲多層索引(最多可支持三層索引,即最底層的Data Block Index稱之爲Leaf Index Block,可直接索引到Data Block;中間層稱之爲Intermediate Index Block,最上層稱之爲Root Data Index,Root Data index存放在一個稱之爲"Load-on-open Section"區域,Region Open時會被加載到內存中),使用LruBlockCache::
在這裏插入圖片描述

  • Load-on-open Section
    存放Root Data index,Region打開時就會被加載到內存中
  • Scanned Block Section
    DataBlock、存放Data Block索引的Leaf Index Block與Bloom Block(Bloom Filter數據)交叉存在

3.9.5 HFile生成

  1. 初始HFile無Block,數據在MemStore

  2. flush發生,HFileWriter初始,DataBlock生成,此時Header爲空

  3. 開始寫入,Header被用來存放該DataBlock的元數據信息

  4. MemStore的KeyValue,寫入DataBlock。若是設置了Data Block Encoding,此時須要進行編碼。

  5. KeyValue達到Block大小,中止寫入

  6. 對KeyValue進行壓縮,再進行加密
    在這裏插入圖片描述

  7. 在Header區寫入對應DataBlock元數據信息,包含{壓縮前的大小,壓縮後的大小,上一個Block的偏移信息,Checksum元數據信息}等信息。
    在這裏插入圖片描述

  8. 生成Checksum校驗和信息

  9. 經過HFileWriter將DataBlock/Checksum寫入HDFS

  10. 爲DataBlock生成包含StartKey,Offset,Size等信息的索引,先寫入內存中的Block Index Chunk,累積到閾值後刷入HDFS,造成Leaf Index Block
    在這裏插入圖片描述

  11. DataBlock和DataBlockIndex(Leaf Index Block)在Scanned Block Section交叉存在。
    在這裏插入圖片描述

  12. 而HBase中還存在Root Index Chunk用來記錄每一個DataBlockIndex的信息,即爲DataBlock的索引的索引。
    在這裏插入圖片描述

  13. 當MemStore Flush中最後一個KeyValue寫入到最後一個DataBlock即最後一個DataBlockIndex時,隨機開始flush Root Index Chunk。

    注意,若是Root Index Chunk大小超出閾值,則會生成位於Non-Scanned Block Section區域的Intermediate Index Block,由Root Index生成索引來指向。

    不管如何,都會輸出位於的Load-On-Open SectionRoot Index Block

  14. 生成FileInfo,記錄HFile的元數據

  15. 寫入BloomFilter元數據與索引數據

  16. 最後寫入Trailer部分信息
    在這裏插入圖片描述

3.9.6 BloomFilter

  • 目的
    HBase中的BloomFilter提供了一個輕量級的內存結構,以便將給定Get(BloomFilter不能與Scans一塊兒使用,而是Scan中的每一行來使用)的磁盤讀取次數減小到僅可能包含所需Row的StoreFiles,並且性能增益隨着並行讀取的數量增長而增長。

  • 和BlockIndex區別
    一個Region有多個Store,一個Store須要掃描多個StoreFile(HFile),每一個HFile有一個BlockIndex,粒度較粗,須要經過key range掃描不少BlockIndex來判斷目標key是否可能在該文件中,但仍須要加載該HFile中的若干Block並scan才能肯定是否真的存在目標key。(好比一個1GB的HFile,就包含16384個64KB的Block,且BlockIndex只有StartKey信息。查詢一個給點key則可能落在兩個Block的StartKey範圍之間,須要所有load-scan)。

    而BloomFilter對於Get操做以及部分Scan操做能夠過濾掉不少確定不包含目標Key的HFile文件,大大減小實際IO次數,提升隨機讀性能。

  • 使用場景
    每一個數據條目大小至少爲KB級

  • 存儲位置
    在這裏插入圖片描述
    BloomFilter的Hash函數和BloomFilterIndex存儲在每一個HFile的啓動時加載區中;而具體的存放數據的BloomFilterBlock,會隨着數據變多而變爲多個Block以便按需一次性加載到內存,這些BloomFilterBlock散步在HFile中掃描Block區,不須要更新(由於HFile的不可變性),只是會在刪除時會重建BloomFilter,因此不適合大量刪除場景。

  • 加載時機
    當由於Region部署到RegionServer而打開HFile時,BloomFilter將加載到內存。

  • HBase中的BloomFilter實現
    KeyValue在寫入HFile時,通過若干hash函數的映射將對應的數組位改成1。當Get時也進行相同hash運算,若是遇到某位爲0則說明數據確定不在該HFile中,若是都爲1則提示高几率命中。

    固然,由於HBase爲了權衡內存使用和命中率等,將BloomFilter數組進行了拆分,並引入了BloomIndex,查找時先經過StartKey找到對應的BloomBlock再進行上述查找過程。

  • BloomFilterIndex
    在這裏插入圖片描述
    如上圖,BloomFilterIndex中的BloomIndexEntry中有個BlockKey存有真實數據KeyValue的StartKey,因此每次須要據此來查找BloomFilterBlock進行使用。

  • 彈性
    HBase包括一些調整機制,用於摺疊(fold)BloomFilter以減少大小並將誤報率保持在所需範圍內。

  • 行-列模式

    • 行模式
      默認使用行模式BloomFilter,使用RowKey來過濾HFile,適用於行Scan、行+列Get,不適用於大量列Put場景(一行數據此時由於按列插入而分佈到多個HFile,這些HFile上的BF會爲每一個該RowKey的查詢都返回true,增長了查詢耗時)。
    • 行+列模式
      可設置某些表使用行+列模式的BloomFilter。除非每行只有一列,不然該模式會爲了存儲更多Key而佔用更多空間。不適用於整行scan。
    • 可禁用BloomFilter
    • 例子
      假設某表有3個HFile文件:
      h1:[ kv1(r1 -> cf1:c1,v1), kv2(r2 -> cf1:c1,v2)]
      h2:[ kv3(r3 -> cf1:c1,v3), kv4(r4 -> cf1:c1,v4)]
      h3:[ kv5(r1 -> cf1:c2,v5), kv6(r2 -> cf1:c2,v6)]
      • 行模式BloomFilter,Get(r1)時可過濾掉h2;Get(r4)時可過濾掉h1和h3;Get(r1,c1)也只會過濾掉h2而不會過濾h3
      • 行+列模式BloomFilter,Get(r1,c1)過濾掉h2和h3
  • 指標
    blockCacheHitRatio可觀察到RegionServer上的BlockCache緩存命中率,若是開啓BloomFilter來過濾那些不包含目標key的Block,則blockCacheHitRatio應該增長。

  • 建立BloomFilter
    建立HBase表時可用HColumnDescriptor.setBloomFilterType(NONE/ROW (default)/ ROWCOL)或用如下命令建立BloomFilter:

    create 'mytable',{NAME => 'colfam1', BLOOMFILTER => 'ROWCOL'} 

3.10 BlockCache

可參考HBase BlockCache系列 - 探求BlockCache實現機制

HBase提供兩種不一樣的BlockCache實現,來緩存從HDFS讀取的數據:

  • 堆內的LRUBlockCache。
    • 內存由JVM管理,LRU規則,具體是由MinMaxPriorityQueue實現
    • 默認佔HBase用的Java堆大小的40%
    • 分爲single-access區(25%,存隨機讀入的Block塊)、mutil-access區(50%,single區中數據被屢次讀就移入本區)、in-memory區(25%,存儲訪問頻繁且量下的數據,如元數據)
    • 數據、META表(永遠開啓緩存)、HFile、key、BloomFilter等大量使用LRUBlockCache。
    • 默認狀況下,對全部用戶表都啓用了塊緩存,也就是說任何讀操做都將加載LRU緩存
    • 缺點是隨着multi-access區的數據愈來愈多,會形成CMS FULL GC,致使應用程序長時間暫停
  • 一般在堆外的BucketCache
    • 申請多種不一樣規格的多個Bucket,每種存儲指定Block大小的DataBlock。當某類Bucket不夠時,會從其餘Bucket空間借用內存,提升資源利用率。以下就是兩種規格的Bucket,注意他們的總大小都是2MB。
      在這裏插入圖片描述
    • 三種工做模式:
      • heap
        表示從JVM Heap中申請的Bucket,有GC開銷,分配內存須要從OS分配後拷貝到JVM heap過程。
      • offheap
        使用NIO DirectByteBuffer技術,實現堆外內存管理。讀緩存時須要從OS拷貝到JVM heap讀取。
      • file
        使用相似SSD的高速緩存文件來存儲DataBlock ,可存儲更多數據,提高緩存命中。
  • 實踐
    • 通常可用LRUBlockCache保存 DataBlockIndex 和BloomFilter,其餘數據(好比最主要的DataBlock)放在BucketCache

3.11 KeyValue

3.11.1 概述

HBaseKeyValue
在這裏插入圖片描述

  • KeyValue的構成
    KeyValue是HBase的最核心內容。他主要由keylength, valuelength, key, value 四部分組成:

    • key
      包括了RowKey、列族、列(ColumnQualifier)、時間戳、KeyType(PutDelete、 DeleteColumnDeleteFamily)等信息
    • value
      二進制格式存儲的數據主體信息
  • KeyValue與BloomFilter
    KeyValue在寫入HFile時會用到BloomFilter,通過若干Hash函數計算將某些位置設爲1。當查詢時也是針對目標RowKey,拿出要查詢的HFile上的BloomFilter進行相同hash運算,若是遇到某位置的數爲0說明確定目標數據確定不存在該HFile中。

    固然,實際上HBaseHFile可能特別大,那麼所使用的數組就會相應的變得特別大,因此不可能只用一個數組,因此又加入了BloomIndexBlock來查找目標RowKey位於哪一個BloomIndex,而後是上述BloomFilter查找過程。

3.11.2 例子

一個put操做以下:

Put #1: rowkey=row1, cf:attr1=value1 

他的key組成以下:

rowlength -----------→ 4
row -----------------→ row1
columnfamilylength --→ 2
columnfamily --------→ cf
columnqualifier -----→ attr1
timestamp -----------→ server time of Put keytype -------------→ Put 

3.11.3 小結

因此咱們在設計列族、列、rowkey的時候,要儘可能簡短,否則會大大增長KeyValue大小。

3.12 WAL(Write-Ahead Logging)-HLog

3.12.1 基本概念

  • WAL(Write-Ahead Logging)是一種高效的日誌算法,至關於RDBMS中的redoLog,幾乎是全部非內存數據庫提高寫性能的不二法門,基本原理是在數據寫入以前首先順序寫入日誌,而後再寫入緩存,等到緩存寫滿以後統一落盤。

    之因此可以提高寫性能,是由於WAL將一次隨機寫轉化爲了一次順序寫加一次內存寫。提高寫性能的同時,WAL能夠保證數據的可靠性,即在任何狀況下數據不丟失。假如一次寫入完成以後發生了宕機,即便全部緩存中的數據丟失,也能夠經過恢復日誌還原出丟失的數據(若是RegionServer崩潰可用HLog重放恢復Region數據)。

  • 一個RegionServer上存在多個Region和一個WAL實例,注意並非只有一個WAL文件,而是滾動切換寫新的HLog文件,並按策略刪除舊的文件。

  • 一個RS共用一個WAL的緣由是減小磁盤IO開銷,減小磁盤尋道時間。

  • 能夠配置MultiWAL,多Region時使用多個管道來並行寫入多個WAL流。

  • 當WAL文件數量達到最大個數的時候,就觸發該RegionServer上的全部MemStore 按FIFO順序進行Flush,直到WAL數量降到hbase.regionserver.max.logs如下。此後,那些對應的HLog被視爲過時,會被移動到.oldlogs,隨後被自動刪除

  • WAL的意義就是和Memstore一塊兒將隨機寫轉爲一次順序寫+內存寫,提高了寫入性能,並能保證數據不丟失。

3.12.2 生命週期

注:本段轉自Hbase 技術細節筆記(上)

Hlog從產生到最後刪除須要經歷以下幾個過程:

  • 產生
    全部涉及到數據的變動都會先寫HLog,除非是你關閉了HLog

  • 滾動
    HLog的大小經過參數hbase.regionserver.logroll.period控制,默認是1個小時,時間達到hbase.regionserver.logroll.period 設置的時間,HBase的一個後臺線程就會建立一個新的Hlog文件。這就實現了HLog滾動的目的。HBase經過hbase.regionserver.maxlogs參數控制Hlog的個數。滾動的目的,爲了控制單個HLog文件過大的狀況,方便後續的過時和刪除。

  • 過時與sequenceid
    Hlog的過時依賴於對sequenceid的判斷。HBase會將HLog的sequenceid和HFile最大的sequenceid(刷新到的最新位置)進行比較,若是該Hlog文件中的sequenceid比flush的最新位置的sequenceid要小,那麼這個HLog就過時了,對應HLog會被移動到.oldlogs目錄。

    這裏有個問題,爲何要將過時的Hlog移動到.oldlogs目錄,而不是直接刪除呢?
    答案是由於HBase還有一個主從同步的功能,這個依賴Hlog來同步HBase的變動,有一種狀況不能刪除HLog,那就是HLog雖然過時,可是對應的HLog並無同步完成,所以比較好的作好是移動到別的目錄。再增長對應的檢查和保留時間。

  • 刪除
    若是HLog開啓了replication,當replication執行完一個Hlog的時候,會刪除Zoopkeeper上的對應Hlog節點。在Hlog被移動到.oldlogs目錄後,HBase每隔hbase.master.cleaner.interval(默認60秒)時間會去檢查.oldlogs目錄下的全部Hlog,確認對應的Zookeeper的Hlog節點是否被刪除,若是Zookeeper 上不存在對應的Hlog節點,那麼就直接刪除對應的Hlog。

    hbase.master.logcleaner.ttl(默認10分鐘)這個參數設置Hlog在.oldlogs目錄保留的最長時間。

3.12.3 WAL持久化等級

  • SYNC_WAL: 默認. 全部操做先被執行sync操做到HDFS(不保證落盤),再返回.
  • FSYNC_WAL: 全部操做先被執行fsync操做到HDFS(強制落盤),再返回。最嚴格,但速度最慢。
  • SKIP_WAL: 不寫WAL。提高速度,但有極大丟失數據風險!
  • ASYNC_WAL: 異步寫WAL,可能丟失數據。

3.12.4 WAL數據結構

前面提到過,一個RegionServer共用一個WAL。下圖是一個RS上的3個Region共用一個WAL實例的示意圖:
HLog數據結構
數據寫入時,會將若干數據對<HLogKey,WALEdit>按照順序依次追加到HLog,即順序寫入。

  • HLogKey主要包括:

    • LogSequenceNumber(SequenceId)
      日誌寫入時分配給數據的一個Region級別的自增數字,決定了HLog的過時,也就決定了HLog的生命週期
    • WriteTime
    • RegioNname
    • TableName
    • ClusterIds
      用於將日誌複製到集羣中其餘機器上
  • WALEdit
    用來表示一個事務中的更新集合,在目前的版本,若是一個事務中對一行row R中三列c1,c2,c3分別作了修改,那麼HLog爲了行級事務原子性日誌片斷以下所示:
    <logseq#-for-entire-txn>:<WALEdit-for-entire-txn>
    其中WALEdit會被序列化爲格式<-1, # of edits, <KeyValue>, <KeyValue>, <KeyValue>>,好比<-1, 3, <keyvalue-for-edit-c1>, <keyvalue-for-edit-c2>, <keyvalue-for-edit-c3>>,其中-1做爲標示符表徵這種新的日誌結構。

3.12.5 WAL寫入流程

這裏

4 HBase數據模型

4.1 邏輯模型

4.1.1 邏輯視圖與稀疏性

HBase邏輯模型
上表是HBase邏輯視圖,其中空白的區域並不會佔用空間。這也就是爲何成爲HBase是稀疏表的緣由。

4.1.2 HBase數據模型基本概念

  • Namespace
    相似RDBMS的庫。建表時指定,不然會被分配default namespace。

  • Table
    相似RDBMS的表

  • RowKey

    • 是Byte Array(字節數組),是表中每條記錄的「主鍵」,即惟一標識某行的元素,方便快速查找,RowKey的設計很是重要。
    • MemStore和HFile中按RowKey的字典升序排列。
    • 且RowKey符合最左匹配原則。如設計RowKey爲uid + phone + name,那麼能夠匹配一下內容:
      RowKey最左匹配
      可是沒法用RowKey支持如下搜索:
      RowKey最左匹配2
  • Column Family
    即列族,擁有一個名稱(string),包含一個或者多個列,物理上存在一塊兒。好比,列courses:history 和 courses:math都是 列族 courses的成員.冒號(:)是列族的分隔符。建表時就必需要肯定有幾個列族。每一個

  • Column
    即列,屬於某個columnfamily,familyName:columnName。列可動態添加

  • Version Number
    即版本號,類型爲Long,默認值是系統時間戳timestamp,也可由用戶自定義。相同行、列的cell按版本號倒序排列。多個相同version的寫,只會採用最後一個。

  • Value(Cell)
    {row, column, version} 組一個cell即單元格,其內容是byte數組。

  • Region
    表水平拆分爲多個Region,是HBase集羣分佈數據的最小單位。

4.2 物理模型

  • HBase表的同一個region放在一個目錄裏
    HBaseRegion

  • 一個region下的不一樣列族放在不一樣目錄
    HBaseRegionColumnFamilies

HBaseRegionColumnFamilies2

4.3 有序性

每行的數據按rowkey->列族->列名->timestamp(版本號)逆序排列,也就是說最新版本數據在最前面。

4.4 ACID

請查看事務章節。

5 HBase 容錯

5.1 RegionServer

在這裏插入圖片描述

  • Region Failover:發現失效的Region,先標記這些Region失效,而後到正常的RegionServer上恢復他們
  • RegionSever Failover:由HMaster對其上的region進行遷移

具體來講

  1. RegionServer定時向Zookeeper發送心跳

  2. RegionServer掛掉後一段時間(SessionTimeout),在ZK註冊的臨時節點會被刪除,此時該RS上的Region馬上變得不可用

  3. HMaster監聽到ZK事件得知該RS掛掉,HMaster會認爲其上的Region分配非法,開啓Region重分配流程。

    將該RegionServer在HDFS上的那個HLog文件按Region進行切分,並待不可用的那些Region重分配到其餘RegionServer後,對HLog按照Region和sequenceid由小到大進行重放補足HLog中的數據。

  4. 在此過程當中的客戶端查詢會被重試,不會丟失

詳細流程能夠參考:

5.2 Master Failover

Zookeeper選舉機制選出一個新的Leader Master。但要注意在沒有Master存活時:

  • 數據讀寫仍照常進行,由於讀寫操做是經過.META.表進行。
  • 無master過程當中,region切分、負載均衡等沒法進行(由於master負責)

5.3 Zookeeper容錯

Zookeeper是一個可靠地分佈式服務

5.4 HDFS

HDFS是一個可靠地分佈式服務

6 HBase數據流程

6.1 客戶端Region定位和Scan流程

注意,該過程當中Client不會和Master聯繫,只須要配置ZK信息。
在這裏插入圖片描述

  1. Client訪問Zookeeper,找到hbase:meta表所在RegionServer

  2. 根據所查數據的Namespace、表名和rowkeyhbase:meta表順序查找找到對應的Region信息,並會對該Region位置信息進行緩存。

    若是Region由master負載均衡從新分配,或由於相關RegionServer掛掉,則Client將從新查詢hbase:meta表以肯定Region的新位置而後放入緩存。

  3. 下一步就能夠請求Region所在RegionServer了,會初始化三層scanner實例來一行一行的每一個列族分別查找目標數據:

    • RegionScanner
      根據列族個數構建對應數量的StoreScanner
    • StoreScanner
      1. 統籌掃描對應列族數據,構建一個MemStoreScanner和StoreFile個StoreFileScanner。
      2. 隨後還須要根據TimeRange/KeyRange/BloomFilter 過濾掉確定不包含目標Key的 StoreFileScanner和MemStoreScanner。
        • MemStoreScanner
          真正執行掃描MemStore中的數據
        • StoreFileScanner
          真正執行掃描對應StoreFile(HFile)數據,例子可見這裏
          1. 定位DataBlock:從BlockCache中讀取該HFile的索引結構,肯定目標Key所在的DataBlock的Offset
          2. 加載DataBlock:根據Offset,首先在BlockCache中查找,若沒找到就IO讀取HFile來加載目標DataBlock
          3. 定位Key:在DataBlock中二分查找定位目標Key
      3. 隨後將上述MemStoreScanner和StoreFileScanner合併,並用PriorityQueue優先級隊列來構建最小堆,排序規則是RowKey分別比較RowKey(小優先),ColumnFamily(小優先),Qualifier(小優先),TimeStamp(大優先),KeyTypeDeleteFamily -> DeleteColumn -> Delete -> Put),這樣便於客戶端取數據(好比須要獲取最新版本數據時只須要再按版本號排次序便可)
      4. 將堆頂數據出堆,進行檢查,好比是否ttl過時/是否KeyType爲Delete*/是否被用戶設置的其餘Filter過濾掉,若是經過檢查就加入結果集等待返回。若是查詢未結束,則剩餘元素從新調整最小堆,繼續這一查找檢查過程,直到結束。
  4. RegionScanner將多個StoreScanner按列族小優先規則來合併構建最小堆
    在這裏插入圖片描述

  • 具體例子可參考:
    • 網易範欣欣-HBase-數據讀取流程解析

    • 網易範欣欣-HBase-遲到的‘數據讀取流程’部分細節

    • StoreFileScanner查找磁盤。爲了加速查找,使用了快索引和布隆過濾器:
      塊索引和布隆過濾器

      • 塊索引
        塊索引存儲在HFile文件末端,查找目標數據時先將塊索引讀入內存。由於HFile中的KeyValue字節數據是按字典序排列,而塊索引存儲了全部HFile block的起始key,因此咱們可快速定位目標數據可能所在的塊,只將其讀到內存,加快查找速度。

      • 布隆過濾器
        雖然塊索引減小了須要讀到內存中的數據,但依然須要對每一個HFile文件中的塊執行查找。

        而布隆過濾器則能夠幫助咱們跳過那些必定不包含目標數據的文件。和塊索引同樣,布隆過濾器也被存儲在文件末端,會被優先加載到內存中。另外,布隆過濾器分行式和列式兩種,列式須要更多的存儲空間,所以若是是按行讀取數據,不必使用列式的布隆過濾器。布隆過濾器以下圖所示:
        布隆過濾器
        塊索引和布隆過濾器對好比下:

  塊索引 布隆過濾器
功能 快速定位記錄在HFile中可能的塊 快速判斷HFile塊中是否包含目標記錄
  1. 讀寫請求通常會先訪問MemStore

6.2 寫流程

寫流程

6.2.1 Client

  1. Client默認設置autoflush=true,表示put請求直接會提交給服務器進行處理;也可設置autoflush=false,put請求會首先放到本地buffer,等到本地buffer大小超過必定閾值(默認爲2M,能夠經過配置文件配置)以後纔會異步批量提交。很顯然,後者採用批處理方式提交請求,可極大地提高寫入性能,但由於沒有保護機制,若是該過程當中Client掛掉的話會由於內存中的那些buffer數據丟失致使提交的請求數據丟失!
  2. Client定位到數據所在RegionServer。如果批量請求,還會將rowkey按HRegionLocation分組,每一個分組可對應一次RPC請求MultiServerCallable<Row>
  3. Client爲經過rpcCallerFactory.<MultiResponse> newCaller()執行調用,忽略掉失敗從新提交和錯誤處理。

6.2.2 Server

  1. Server嘗試獲取行鎖(行鎖可保證行級事務原子性)來鎖定目標行(或多行),檢索當前的WriteNumber(可用於MVCC的非鎖讀),並獲取Region更新鎖,寫事務開始。
  2. Server把數據構造爲WALEdit對象,而後按順序寫一份到WAL(一個RegionServer共用一個WAL實例)。當RS忽然崩潰時且事務已經寫入WAL,那就會在其餘RS節點上重放。
  3. Server把數據再寫一份到MemStore(每一個Store一個MemStore實例),此時會把獲取到的WriteNumber附加到KeyValue。
    • Flush
      Server待MemStore達到閾值後,會把數據刷入磁盤,造成一個StoreFile文件。若在此過程當中掛掉,可經過HLog重放恢復。成功刷入磁盤後,會清空HLog和MemStore。
    • Compact
      Server待當多個StoreFile文件達到必定的大小後,會觸發Compact合併操做
    • Split
      Server當Region大小超過必定閾值後,會觸發Split拆分操做
  4. Server提交該事務。隨後,ReadPoint(即讀線程能看到的WriteNumber)就能前移,從而檢索到該新的事務編號,使得scanget能獲取到最新數據
  5. Server釋放行鎖和共享鎖。選擇這個時間釋放行鎖的緣由是可儘可能減小持有互斥的行級寫鎖時間,提高寫性能。
  6. Server SyncHLog。此時若是Sync操做失敗,會對寫入Memstore內的數據進行移除,即回滾。
  7. 若是上一步提交,則flush memstore

6.3 讀流程

讀流程
此過程不須要HMbaster參與:

  1. 定位到數據所在RegionServer進行讀請求
  2. 先從寫緩存MemStore找數據
  3. 若是沒有,再到讀緩存HFile BlockCache上讀
  4. 若是仍是沒有,再到HFile文件上讀
  5. 若Region元數據如位置發生了變化,那麼使用.META.表緩存區訪問RS時會找不到目標Region,會進行重試,重試次數達到閾值後會去.META.表查找最新數據並更新緩存。

6.4 刪除

  1. 在執行delete命令時,hbase只是添加<key, del>標記,稱爲墓碑。此時並未真正刪除
  2. 有墓碑時,該key對應數據被查詢時就會被過濾掉。
  3. Major Compact中被刪除的數據和此墓碑標記才從StoreFile會被真正刪除。

6.5 TTL過時

  • CF默認的TTL值是FOREVER,也就是永不過時。

  • 過時數據不會建立墓碑,若是一個StoreFile僅包括過時的rows,會在Minor Compact的時候被清理掉,再也不寫入合併後的StoreFile。

  • TTL分爲兩類即Cell和CF:

    • Cell TTL 以毫秒爲單位,不可超過CF級別的TTL
    • CF TTL以秒爲單位
  • 注意:修改表結構以前,須要先disable 表,不然表中的記錄被清空!

  • 還可參考:

6.6 WAL寫入

總的來講,分爲三個步驟:

  1. 若干<HLogKey,WALEdit>寫入本地Buffer
  2. Buffer Flush到HDFS,此時不保證落盤
  3. HDFS fsync落盤
  • 老寫入模型
    在這裏插入圖片描述
    在老的寫入模型中,每一個寫入線程的WriteHandler都須要分別競爭updateLock和flushLock,效率較低。

  • 新寫入模型
    在這裏插入圖片描述
    新寫入模型採起了多線程模式獨立完成寫HDFS、HDFS fsync,避免了以前多工做線程惡性搶佔鎖的問題。並引入一個Notify線程通知WriteHandler線程是否已經fsync成功,可消除舊模型中的鎖競爭。

    同時,工做線程在將WALEdit寫入本地Buffer以後並無立刻阻塞,而是釋放行鎖以後阻塞等待WALEdit落盤,這樣能夠儘量地避免行鎖競爭,提升寫入性能。

關於此過程詳細可參考網易範欣欣-HBase - 數據寫入流程解析

7 HBase數據結構

7.1 概述

B樹存在的問題:

  • 查找
    從原理來講,b+樹在查詢過程當中應該是不會慢的,但若是數據插入雜亂無序時(好比插入順序是5 -> 10000 -> 3 -> 800,相似這樣跨度很大的數據),就須要先找到這個數據應該被插入的位置而後再插入數據。這個查找過程若是很是離散,且隨着新數據的插入,葉子節點會逐漸分裂成多個節點,邏輯上連續的葉子節點在物理上每每已經再也不不連續,甚至分離的很遠。就意味着每次查找的時候,所在的葉子節點都不在內存中。這時候就必須使用磁盤尋道時間來進行查找了,至關因而隨機IO了。
  • 寫入
    且B+樹的更新基本與插入是相同的,也會有這樣的狀況。且還會有寫數據時的磁盤IO。

總的來講,B樹隨機IO會形成低效的磁盤尋道,嚴重影響性能。

7.2 LSM樹簡介

可參考LSM樹

7.3 LSM樹在HBase的應用

HFile格式基於Bigtable論文中SSTable。

7.3.1 寫入

  1. 先寫入WAL的HBase實現 -> HLog,方式是順序磁盤追加
  2. 而後寫入對應列簇的Store中的MemStore
  3. MemStore大小達到閾值後會被刷入磁盤成爲StoreFile。注意此文件內部是根據RowKeyVersionColumn排序,但多個StoreFile之間在合併前是無序的。
  4. HBase會定時把這些小的StoreFile合併爲大StoreFile(B+樹),減小讀取開銷(相似於LSM中的樹合併)

7.3.2 讀取

  1. 先搜索內存小樹即MemStore,
  2. 不存在就到StoreFile中尋找

7.3.3 讀取優化

  • 布隆過濾器。
    可快速獲得是否數據不在該集合,但不能100%確定數據在這個集合,即所謂假陽性。
  • 合併
    合併後,就不用再遍歷繁多的小樹了,直接找大樹

7.3.4 刪除

添加<key, del>標記,稱爲墓碑。

在Major Compact中被刪除的數據和此墓碑標記纔會被真正刪除。

7.3.5 合併

HBase Compact過程,就是RegionServer按期將多個小StoreFile合併爲大StoreFile,也就是LSM小樹合併爲大樹。這個操做的目的是增長讀的性能,不然搜索時要讀取多個文件。

HBase中合併有兩種:

  • Minor Compact
    僅合併少許的小HFile
  • Major Compact
    合併一個Region上的全部HFile,此時會刪除那些無效的數據(更新時,老的數據就無效了,最新的那個<key, value>就被保留;被刪除的數據,將墓碑<key,del>和舊的<key,value>都刪掉)。不少小樹會合併爲一棵大樹,大大提高度性能。

7.3.4 LSM與B+樹

RDBMS使用B+樹,須要大量隨機讀寫;

而LSM樹使用WALog和Memstore將隨機寫操做轉爲順序寫。

HBase事務

可參考數據庫事務系列-HBase行級事務模型

8.1 ACID

HBase和RDBMS相似,也提供了事務的概念,只不過HBase的事務是行級事務,能夠保證行級數據的ACID性質。

8.1.1 A-原子性

  • 針對同一行(就算是跨列)的全部修改操做具備原子性,全部put操做要麼全成功要麼全失敗。
  • HBase有相似CAS的操做:
boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) throws IOException; 
  • 原子性的原理
    由於寫入時MemSotre中異常容易回滾,因此原子性的關鍵在於WAL。而前面提到過WAL原子性保證原理

8.1.2 C/I-一致性/隔離性

  • 一致性概述

    • Get一致性
      查詢獲得的全部行都是某個時間點的完整行。
    • Scan一致性
    1. scan不是表的一致性視圖,但返回結果中的每一行是一致性的視圖(該行數據同一時間的版本)
    2. scan結果老是能反映scan開始時的數據版本(包括確定反映以前的數據修改後狀態和可能反映在scanner構建中的數據修改狀態)

    以上的時間不是cell中的時間戳,而是事務提交時間。

  • 隔離性-讀提交
    當構建StoreFileScanner後,會自動關聯一個MultiVersionConcurrencyControl Read Point,他是當前的MemStore版本,scan操做只能讀到這個點以前的數據。ReadPoint以後的更改會被過濾掉,不能被搜索到。

    這類事務隔離保證在RDBMS中稱爲讀提交(RC)

  • 不保證任何 Region 之間事務一致性
    注意:因爲 HBase 不保證任何 Region 之間(每一個 Region 只保存在一個 Region Server 上)的一致性,故 MVCC 的數據結果只需保存在每一個 RegionServer 各自的內存中。
    當一臺 RegionServer 掛掉,若是 WAL 已經完整寫入,全部執行中的事務能夠重放日誌以恢復,若是 WAL 未寫完,則未完成的事務會丟掉(相關的數據也丟失了)

8.1.3 V-可見性

當沒有使用writeBuffer時,客戶端提交修改請求並收到成功響應時,該修改當即對其餘客戶端可見。緣由是行級事務。

HBase讀數據時的scanner有一個Readpoint,該取值是寫數據線程寫入WriteNumber到KeyValue並提交事務後更新的。scan結果中會濾掉全部大於該 ReadPoint 的 KeyValues。

當一個 KeyValue 的 memstore timestamp(WriteNumber) 比最老的scanner(實際是 scanner 持有的 ReadPoint)還要老時,會被清零(置爲0),這樣該 KeyValue會對全部的 scanner 可見,固然,此時比該 KeyValue 原 memstore timestamp 更早的 scanner 都已經結束了。

8.1.4 D-持久性

全部可見數據也是持久化的數據。也就是說,每次讀請求不會返回沒有持久化的數據(注意,這裏指hflush而不是fsync到磁盤)。

而那些返回成功的操做,就已是持久化了;返回失敗的,固然就不會持久化。

8.1.5 可調性

HBase默認要求上述性質,但可根據實際場景調整,好比修改持久性爲定時刷盤。

關於ACID更多內容,請參閱HBase-acid-semanticsACID in HBase

8.2 事務原理

可參考:

8.2.1 簡介

HBase支持單行ACID性質,但在HBASE-3584新增了對多操做事務支持,還在HBASE-5229新增了對跨行事務的支持。HBase全部事務都是串行提交的。

爲了實現事務特性,HBase採用了各類併發控制策略,包括各類鎖機制、MVCC機制等,但沒有實現混合的讀寫事務。

8.2.2 行級鎖CountDownLatch

HBase採用CountDownLatch行鎖實現更新的原子性,要麼所有更新成功,要麼失敗。

全部對HBase行級數據的更新操做,都須要首先獲取該行的行鎖,而且在更新完成以後釋放,等待其餘線程獲取。所以,HBase中對多線程同一行數據的更新操做都是串行操做。

行鎖主要相關類爲RowLockRowLockContext

  • RowLockContext存儲行鎖內容包括持鎖線程、被鎖對象以及能夠實現互斥鎖的CountDownLatch對象等
    在這裏插入圖片描述
  • RowLock包含表徵行鎖是否已經釋放的release字段以及一個RowLockContext對象。
    在這裏插入圖片描述
    行鎖加鎖流程:
  1. 使用rowkey以及自身Thread對象生成RowLockContext對象,此時共享的CountDownLatch對象初始化計數爲1
  2. 將rowkey做爲key,RowLockContext對象做爲value,putIfAbsert到全局map lockedRows中,會返回一個existingContext對象,有三種狀況:
  • null
    表示該行鎖沒有被其餘線程持有,可用剛剛建立的RowLockContext來持有該鎖,其餘線程必然插入失敗。
  • 本線程建立的RowLockContext
    直接使用該RowLockContext對象持有該鎖便可。批量更新時可能對某一行數據屢次更新,須要屢次嘗試持有該行數據的行鎖。這也被稱爲可重入鎖的狀況。
  • 其餘線程建立的RowLockContext
    則該線程會調用latch.await方法阻塞在此RowLockContext對象上,直至該行鎖被釋放或者阻塞超時。待行鎖釋放,該線程會從新競爭該鎖,一旦競爭成功就持有該行鎖,不然繼續阻塞。而若是阻塞超時,就會拋出異常,不會再去競爭該鎖。

釋放流程
在線程更新完成操做以後,必須在finally方法中執行行鎖釋放rowLock.release()方法,其主要邏輯爲:

  1. 從全局map中將該row對應的RowLockContext移除
  2. 調用latch.countDown()方法,喚醒其餘阻塞在await上等待該行鎖的線程

CountDownLatch在加鎖時的應用

8.2.3 讀寫鎖ReentrantReadWriteLock

另外一種是基於ReentrantReadWriteLock實現的讀寫鎖,該鎖能夠給臨界資源加上read-lock或者write-lock。其中read-lock容許併發的讀取操做,而write-lock是徹底的互斥操做。HBase利用讀寫鎖實現了Store級別、Region級別的數據一致性

  1. Region更新讀寫鎖
    HBase在執行數據更新操做以前都會加一把Region級別的讀鎖(共享鎖),全部更新操做線程之間不會相互阻塞;然而,HBase在將memstore數據落盤時會加一把Region級別的寫鎖(獨佔鎖)。所以,在memstore數據落盤時,數據更新操做線程(Put操做、Append操做、Delete操做)都會阻塞等待至該寫鎖釋放。

  2. Region Close保護鎖
    HBase在執行close操做以及split操做時會首先加一把Region級別的寫鎖(獨佔鎖),阻塞對region的其餘操做,好比compact操做、flush操做以及其餘更新操做,這些操做都會持有一把讀鎖(共享鎖)

  3. Store snapshot保護鎖
    HBase在執行flush memstore的過程當中首先會基於memstore作snapshot,這個階段會加一把store級別的寫鎖(獨佔鎖),用以阻塞其餘線程對該memstore的各類更新操做;清除snapshot時也相同,會加一把寫鎖阻塞其餘對該memstore的更新操做。

8.2.4 MVCC

8.2.4.1 概述

HBase還提供了MVCC機制實現數據的讀寫併發控制。
在這裏插入圖片描述
上圖中的寫行鎖機制,若是在第二次更新時讀到更新列族1cf1:t2_cf1同時讀到列族2cf2:t1_cf2,這就產生了行數據不一致的狀況。但若是想直接採用讀寫線程公用行鎖來解決此問題,會產生嚴重性能問題。

HBase採用了一種MVCC思想,每一個RegionServer維護一個嚴格單調遞增的事務號:

  • 當寫入事務(一組PUTDELETE命令)開始時,它將檢索下一個最高的事務編號。這稱爲WriteNumber。每一個新建的KeyValue都會包括這個WriteNumber,又稱爲Memstore timestamp注意他和KeyValue的timestamp屬性不一樣
  • 當讀取事務(一次SCANGET)啓動時,它將檢索上次提交的事務的事務編號。這稱爲ReadPoint

由於HBase事務不能跨Region,因此這些MVCC信息就分別保存在RegionServer內存中。

8.2.4.2 事務寫流程

具體來講,MVCC思想優化後的寫流程以下:
在這裏插入圖片描述
在寫事務
上圖是服務端接收到寫請求後的寫事務流程:

  1. 鎖定行(或多行),事務開始。行鎖可保證行級事務原子性。
  2. 開始寫事務,檢索當前的WriteNumber
  3. 將更新應用於WAL。當RS忽然崩潰時且事務已經寫入WAL,那就會在其餘RS節點上重放。
  4. 將更新應用於Memstore,具體來講是把獲取到的WriteNumber附加到KeyValue
  5. 提交該事務。隨後,ReadPoint(即讀線程能看到的WriteNumber)就能前移,從而檢索到該新的事務編號,使得scanget能獲取到最新數據
  6. 釋放行鎖。選擇這個時間釋放行鎖的緣由是可儘可能減小持有互斥的行級寫鎖時間,提高寫性能。
  7. SyncHLog。此時若是Sync操做失敗,會對寫入Memstore內的數據進行移除,即回滾。
  8. 若是上一步提交,則flush memstore
8.2.4.3 事務讀流程
  1. 打開scanner
  2. 獲取的當前ReadPoint。ReadPoint的值是全部的寫操做完成序號中的最大整數
  3. scan時,過濾掉那些WriteNumber(Memstore timestamp) 大於 ReadPoint的 KeyValue
  4. scan完畢,返回結果 。一次讀操做的結果就是讀取點對應的全部cell值的集合

例子:在這裏插入圖片描述
如上圖所示,第一次更新獲取的寫序號爲1,第二次更新獲取的寫序號爲2。讀請求進來時寫操做完成序號中的最大整數爲wn(WriteNumber) = 1,所以對應的讀取點爲wn = 1,讀取的結果爲wn = 1所對應的全部cell值集合,即爲第一次更新鎖寫入的t1_cf1t1_cf2,這樣就能夠實現以無鎖的方式讀取到行一致的數據。

更多詳細討論可見HBase之七:事務和併發控制機制原理

8.3 隔離性+鎖實現

8.3.1 寫寫併發

  1. 寫時先獲取行鎖CountDownLatch
  2. 獲取到鎖的執行寫操做
  3. 沒獲取到的自旋重試等待
  4. 寫操做完成後,釋放鎖
  5. 其餘等待鎖的寫入者競爭鎖

8.3.3 批量寫寫併發

  • 寫入前統一獲取全部行的行鎖,獲取到才進行操做。
  • 執行寫操做
  • 完成後統一釋放全部行鎖,避免死鎖。

8.3.3 讀寫併發

  • 讀寫併發控制緣由
    若是不進行控制,可能讀到寫了一半的數據,好比a列是上個事務寫入的數據,b列又是下一個事務寫入的數據,這就出大問題了。

  • 實現思想
    讀寫併發採用MVCC思想,每一個RegionServer維護一個嚴格單調遞增的事務號。

    • 當寫入事務(一組PUTDELETE命令)開始時,它將檢索下一個最高的事務編號。這稱爲WriteNumber
    • 當讀取事務(一次SCANGET)啓動時,它將檢索上次提交的事務的事務編號。這稱爲ReadPoint
  • 原理
    寫事務會加入到Region級別的自增序列即sequenceId並添加到隊列。當sequenceId更大的事務已提交但較小的事務未提交時,更大的事務也必須等待,對讀請求不可見。例子以下圖:
    在這裏插入圖片描述

8.4 scan和合並

scan時遇到合併正在進行,HBase處理方案以下:

  • 跟蹤scanner使用的最先的ReadPoint,不返回Memstore timestamp大於該ReadPoint的那些KeyValue。
  • Memstore timestamp刪除的時機就是當它比最先的那個scanner還早時,由於這個時候全部scanner都能獲取該數據。

8.5 第三方實現

經過集成Tephra,Phoenix能夠支持ACID特性。Tephra也是Apache的一個項目,是事務管理器,它在像HBase這樣的分佈式數據存儲上提供全局一致事務。HBase自己在行層次和區層次上支持強一致性,Tephra額外提供交叉區、交叉表的一致性來支持可擴展性、一致性。

9 HBase協處理器

可參考官方博客-coprocessor_introduction

9.1 簡介

協處理器可以讓咱們在RegionServer服務端運行用戶代碼,實現相似RDBMS的觸發器、存儲過程等功能。

9.2 風險

  • 運行在協處理器上的代碼能直接訪問數據,因此存在數據損壞、中間人攻擊或其餘惡意數據訪問的風險。
  • 當前沒有資源隔離機制,因此一個初衷良好的協處理器可能實際上會影響集羣性能和穩定性。

9.3 使用場景

在通常狀況下,咱們使用GetScan命令,加上Filter,從HBase獲取數據而後進行計算。這樣的場景在小數據規模(如幾千行)和若干列時性能表現尚好。然而當行數擴大到十億行、百萬列時,網絡傳輸如此龐大的數據會使得網絡很快成爲瓶頸,而客戶端也必須擁有強大的性能、足夠的內存來處理計算這些海量數據。

在上述海量數據場景,協處理器可能發揮巨大做用:用戶可將計算邏輯代碼放到協處理器中在RegionServer上運行,甚至能夠和目標數據在相同節點。計算完成後,再返回結果給客戶端。

9.4 類比

9.4.1 觸發器和存儲過程

  • Observer協處理器
    它相似RDBMS的觸發器,能夠在指定事件(如Get或Put)發生先後執行用戶代碼,不須要客戶端代碼。
    在這裏插入圖片描述

  • Endpoint協處理器
    它相似RDBMS的存儲過程,也就是說能夠在RegionServer上執行數據計算任務。Endpoint須要經過protocl來定義接口實現客戶端代碼進行rpc通訊,以此來進行數據的蒐集歸併。

    具體來講,在各個region上並行執行的Endpoint代碼相似於MR中的mapper任務,會將結果返回給Client。Client負責最終的聚合,算出整個表的指標,相似MR中的Reduce。

9.4.2 MR任務

MR任務思想就是將計算過程放到數據節點,提升效率。思想和Endpoint協處理器相同。

9.4.3 AOP

將協處理看作經過攔截請求而後運行某些自定義代碼來應用advice,而後將請求傳遞到其最終目標(甚至更改目標)。

9.4.4 過濾器

過濾器也是將計算邏輯移到RS上,但設計目標不太相同。

9.5 協處理器的實現

9.5.1 概覽

  1. 實現某個協處理器接口,如 Coprocessor(協處理器祖先接口), RegionObserver(Observer), CoprocessorService(Endpoint)
  2. 配置文件靜態方式或動態加載協處理器
  3. 經過客戶端代碼調用協處理器,由HBase處理協處理器執行邏輯

9.6 協處理器分類

9.6.1 Observer

9.6.1.1 簡介
  • 相似RDBMS的觸發器,能夠在指定事件(如Get或Put)發生前(preGet)後(postGet)執行用戶代碼。

  • 具體執行調用過程由HBase管理,對用戶透明。

  • 通常來講Observer協處理器又分爲如下幾種:

    • RegionObserver
      可觀察Region級別的如Get等各種操做事件
    • RegionServerObserver
      可觀察RegionServer級別的如開啓、中止、合併、提交、回滾等事件
    • MasterObserver
      可觀察Master的如表建立/刪除、schema修改等事件
    • WalObserver
      可觀察WAL相關事件
9.6.1.2 應用
  • 權限驗證
    能夠在preGetprePost中執行權限驗證。
  • 外鍵
    利用prePut,在插入某個表前插入一條記錄到另外一張表
  • 二級索引
    詳見HBase Secondary Indexing,可看後文二級索引

9.6.2 Endpoint

9.6.2.1 簡介
  • 可在數據位置執行計算。

  • 具體執行調用過程必須繼承經過客戶端實現CoprocessorService接口的方法,顯示進行代碼調用實現。

  • Endpoint經過protobuf實現

  • Endpoint 協處理器相似傳統數據庫中的存儲過程,客戶端能夠調用這些 Endpoint 協處理器執行一段 Server 端代碼,並將 Server 端代碼的結果返回給客戶端進一步處理,最多見的用法就是進行彙集操做。

  • 若是沒有協處理器,當用戶須要找出一張表中的最大數據,即 max 聚合操做,就必須進行全表掃描,在客戶端代碼內遍歷掃描結果,並執行求最大值的操做。這樣的方法沒法利用底層集羣的併發能力,而將全部計算都集中到 Client 端統一執行,勢必效率低下。

    利用 Coprocessor,用戶能夠將求最大值的代碼部署到 HBase Server 端,HBase 將利用底層 cluster 的多個節點併發執行求最大值的操做。即在每一個 Region 範圍內執行求最大值的代碼,將每一個 Region 的最大值在 Region Server 端計算出,僅僅將該 max 值返回給客戶端。在客戶端進一步將多個 Region 的最大值進一步處理而找到其中的最大值。這樣總體的執行效率就會提升不少。

9.6.2.2 應用
  • 在一個擁有數百個Region的表上求均值或求和

9.7 加載方法

9.7.1 靜態加載(系統級全局協處理器)

  • 加載
    1. hbase-site.xml中配置一個SumEndPoint

      <property> <name>hbase.coprocessor.region.classes</name> <!-- 具體的協處理器實現類,多個協處理器以逗號分隔 --> <value>org.myname.hbase.coprocessor.endpoint.SumEndPoint</value> </property> 
    2. HBase在服務端用默認的ClassLoader加載上述配置的協處理器,因此說咱們必須將協處理器和相關依賴代碼打成jar後要放到RegionServer上的classpath才能運行。

    3. 這種方式加載的協處理器對全部表的全部Region可用,因此可稱爲system Coprocessor

    4. 列表中首個協處理器擁有最高優先級,後序的優先級數值依次遞增。注意,優先級數值越高優先級越低。調用協處理器時,HBase會按優先級順序調用回調方法。

    5. 重啓HBase便可

  • 卸載
    1. hbase-site.xml中去掉協處理器配置
    2. 重啓HBase
    3. 按需從HBase lib目錄刪除不用的協處理器 JAR文件

9.7.2 動態加載(表級協處理器)

該種方式加載的協處理器只能對加載了的表有效。加載協處理器時,表必須離線。

動態加載,須要先將包含協處理器和全部依賴打包成jar,好比coprocessor.jar,放在了HDFS的某個位置(也可放在每一個RegionServer的本地磁盤,可是顯然很麻煩)。
而後加載方式有如下三種:

  • HBase Shell

    1. 將須要加載協處理器的表離線禁用:

      hbase> disable 'users' 
    2. 加載協處理器:
      下面各個參數用|分隔。其中1073741823表明優先級;arg1=1,arg2=2表明協處理器參數,可選。

      hbase alter 'users', METHOD => 'table_att', 'Coprocessor'=>'hdfs://<namenode>:<port>/user/<hadoop-user>/coprocessor.jar| org.myname.hbase.Coprocessor.RegionObserverExample|1073741823|arg1=1,arg2=2' 
    3. 恢復表可用

      hbase(main):003:0> enable 'users' 
    4. 驗證協處理器可用性

      hbase(main):04:0> describe 'users' 

      能夠在user表的TABLE_ATTRIBUTES屬性中看到已加載的協處理器。

  • Java API

TableName tableName = TableName.valueOf("users"); Path path = new Path("hdfs://<namenode>:<port>/user/<hadoop-user>/coprocessor.jar"); Configuration conf = HBaseConfiguration.create(); Connection connection = ConnectionFactory.createConnection(conf); Admin admin = connection.getAdmin(); admin.disableTable(tableName); HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName); HColumnDescriptor columnFamily1 = new HColumnDescriptor("personalDet"); columnFamily1.setMaxVersions(3); hTableDescriptor.addFamily(columnFamily1); HColumnDescriptor columnFamily2 = new HColumnDescriptor("salaryDet"); columnFamily2.setMaxVersions(3); hTableDescriptor.addFamily(columnFamily2); hTableDescriptor.addCoprocessor(RegionObserverExample.class.getCanonicalName(), path, Coprocessor.PRIORITY_USER, null); admin.modifyTable(tableName, hTableDescriptor); admin.enableTable(tableName); 
  • 動態卸載

    • HBase Shell

      1. 將須要加載協處理器的表離線禁用:

        hbase> disable 'users' 
      2. 移除協處理器:

        alter 'users', METHOD => 'table_att_unset', NAME => 'coprocessor$1' 
      3. 恢復表可用

        hbase(main):003:0> enable 'users' 
    • Java API

    TableName tableName = TableName.valueOf("users"); String path = "hdfs://<namenode>:<port>/user/<hadoop-user>/coprocessor.jar"; Configuration conf = HBaseConfiguration.create(); Connection connection = ConnectionFactory.createConnection(conf); Admin admin = connection.getAdmin(); admin.disableTable(tableName); HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName); // columnFamily2.removeCoprocessor() HColumnDescriptor columnFamily1 = new HColumnDescriptor("personalDet"); columnFamily1.setMaxVersions(3); hTableDescriptor.addFamily(columnFamily1); HColumnDescriptor columnFamily2 = new HColumnDescriptor("salaryDet"); columnFamily2.setMaxVersions(3); hTableDescriptor.addFamily(columnFamily2); admin.modifyTable(tableName, hTableDescriptor); admin.enableTable(tableName); 

9.8 代碼例子

9.8.1 背景

官方文檔例子。

一個users表,擁有兩個列族personalDet(用戶詳情) 和 salaryDet(薪水詳情)
Users Table

9.8.2 Observer例子

該協處理器能阻止在對users表的GetScan操做中返回用戶admin的詳情信息:

  1. 實現RegionObserver接口方法preGetOp(),在該方法中加入代碼判斷客戶端查詢的值是admin。若是是,就返回錯誤提示,不然就返回查詢結果:
public class RegionObserverExample implements RegionObserver { private static final byte[] ADMIN = Bytes.toBytes("admin"); private static final byte[] COLUMN_FAMILY = Bytes.toBytes("details"); private static final byte[] COLUMN = Bytes.toBytes("Admin_det"); private static final byte[] VALUE = Bytes.toBytes("You can't see Admin details"); @Override public void preGetOp(final ObserverContext<RegionCoprocessorEnvironment> e, final Get get, final List<Cell> results) throws IOException { if (Bytes.equals(get.getRow(),ADMIN)) { Cell c = CellUtil.createCell(get.getRow(),COLUMN_FAMILY, COLUMN, System.currentTimeMillis(), (byte)4, VALUE); results.add(c); e.bypass(); } } @Override public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> e, final Scan scan, final RegionScanner s) throws IOException { // 使用filter從scan中排除ADMIN結果 // 這樣的缺點是會覆蓋原有的其餘filter Filter filter = new RowFilter(CompareOp.NOT_EQUAL, new BinaryComparator(ADMIN)); scan.setFilter(filter); return s; } @Override public boolean postScannerNext(final ObserverContext<RegionCoprocessorEnvironment> e, final InternalScanner s, final List<Result> results, final int limit, final boolean hasMore) throws IOException { Result result = null; Iterator<Result> iterator = results.iterator(); while (iterator.hasNext()) { result = iterator.next(); if (Bytes.equals(result.getRow(), ADMIN)) { // 也能夠經過postScanner方式從結果中移除ADMIN iterator.remove(); break; } } return hasMore; } } 
  1. 將協處理器和依賴一塊兒打包爲.jar文件
  2. 上傳該jar文件到HDFS
  3. 用咱們以前提到過的一種方式來加載該協處理器
  4. 寫一個GetScan測試程序來驗證

9.8.3 Endpoint例子

該例子實現一個Endpoint協處理器來計算全部職員的薪水之和:

  1. protobuf標準,建立一個描述咱們服務的.proto文件:

    option java_package = "org.myname.hbase.coprocessor.autogenerated"; option java_outer_classname = "Sum"; option java_generic_services = true; option java_generate_equals_and_hash = true; option optimize_for = SPEED; message SumRequest { required string family = 1; required string column = 2; } message SumResponse { required int64 sum = 1 [default = 0]; } service SumService { rpc getSum(SumRequest) returns (SumResponse); } 
  2. 對以上.proto文件執行protoc命令來生成java代碼Sum.javasrc目錄:

    $ mkdir src $ protoc --java_out=src ./sum.proto 
  3. Endpoint協處理器代碼編寫
    繼承剛纔生成的類,並實現CoprocessorCoprocessorService接口的方法:

    public class SumEndPoint extends Sum.SumService implements Coprocessor, CoprocessorService { private RegionCoprocessorEnvironment env; @Override public Service getService() { return this; } @Override public void start(CoprocessorEnvironment env) throws IOException { if (env instanceof RegionCoprocessorEnvironment) { this.env = (RegionCoprocessorEnvironment)env; } else { throw new CoprocessorException("Must be loaded on a table region!"); } } @Override public void stop(CoprocessorEnvironment env) throws IOException { // do nothing } @Override public void getSum(RpcController controller, Sum.SumRequest request, RpcCallback<Sum.SumResponse> done) { Scan scan = new Scan(); // 列族 scan.addFamily(Bytes.toBytes(request.getFamily())); // 列 scan.addColumn(Bytes.toBytes(request.getFamily()), Bytes.toBytes(request.getColumn())); Sum.SumResponse response = null; InternalScanner scanner = null; try { scanner = env.getRegion().getScanner(scan); List<Cell> results = new ArrayList<>(); boolean hasMore = false; long sum = 0L; do { hasMore = scanner.next(results); // 按cell(rowkey/列/timestamp)遍歷結果 for (Cell cell : results) { // 累加結果 sum = sum + Bytes.toLong(CellUtil.cloneValue(cell)); } results.clear(); } while (hasMore); // 構建帶結果的相應 response = Sum.SumResponse.newBuilder().setSum(sum).build(); } catch (IOException ioe) { ResponseConverter.setControllerException(controller, ioe); } finally { if (scanner != null) { try { // 用完記得關閉scanner scanner.close(); } catch (IOException ignored) {} } } // 返回結果 done.run(response); } } 
  4. 客戶端調用代碼:

    Configuration conf = HBaseConfiguration.create(); Connection connection = ConnectionFactory.createConnection(conf); TableName tableName = TableName.valueOf("users"); Table table = connection.getTable(tableName); // 構建 對salaryDet列族 gross列 求和 的rpc請求 final Sum.SumRequest request = Sum.SumRequest.newBuilder().setFamily("salaryDet").setColumn("gross").build(); try { // 調用Entpoint協處理器方法,獲得結果 Map<byte[], Long> results = table.coprocessorService( Sum.SumService.class, null, /* start key */ null, /* end key */ // 回調方法 new Batch.Call<Sum.SumService, Long>() { @Override public Long call(Sum.SumService aggregate) throws IOException { BlockingRpcCallback<Sum.SumResponse> rpcCallback = new BlockingRpcCallback<>(); // 獲得結果 aggregate.getSum(null, request, rpcCallback); Sum.SumResponse response = rpcCallback.get(); return response.hasSum() ? response.getSum() : 0L; } } ); // 遍歷打印結果 for (Long sum : results.values()) { System.out.println("Sum = " + sum); } } catch (ServiceException e) { e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } 
  5. 加載Endpoint協處理器

  6. 執行上述客戶端代碼,進行測試

9.8.3 HBase同步數據到ES

10 過濾器

可參考Apache HBase Filter

過濾器使用不當會形成性能降低,必須通過嚴格測試才能投入生產環境。

過濾器可按RowKey/CF/Column/Timestamp等過濾數據,他們在於Scan/Get等配合使用時可直接在服務端就過濾掉不須要的數據,大大減小傳回客戶端的數據量。

11 HBase網絡和線程模型

12 HBase實踐

12.1 API

可參考

12.1.1 scan

按指定條件獲取範圍數據

  • scan時會綜合StoreFile和MemStore scanner掃描結果。當構建scanner時,會關聯一個MultiVersionConcurrencyControl Read Point,只能讀到這個點以前的數據。ReadPoint以後的更改會被過濾掉,不能被搜索到。

  • 注意點

  1. scan能夠經過setCachingsetBatchsetMaxResultSize等方法以空間換時間的思想來提升效率;
    • setCaching => .setNumberOfRowsFetchSize (客戶端每次 rpc fetch 的行數,默認Integer.MAX_VALUE)
    • setMaxResultSize => .setMaxResultByteSize (客戶端緩存的最大字節數,默認2MB)
    • setBatch => .setColumnsChunkSize (客戶端每次獲取的列數,默認-1表明全部列)
      詳細說明能夠參考這篇:文章
  2. scan能夠經過setStartRow與setEndRow來限定範圍([start,end)start是閉區間,end是開區間)。範圍越小,性能越高。
    經過巧妙的RowKey設計使咱們批量獲取記錄集合中的元素挨在一塊兒(應該在同一個Region下),能夠在遍歷結果時得到很好的性能。
  3. scan能夠經過setFilter方法添加過濾器,這也是分頁、多條件查詢的基礎(但BloomFilter不適用於Scan)。
  4. ResultScanner使用完後必須關閉
  5. 頻繁訪問的行可setCacheBlocks開啓塊緩存

12.1.2 get

傳入rowkey獲得最新version數據或指定maxversion獲得指定版本數據。除了查單一RowKey,也能夠在構造 Get 對象的時候傳入一個 rowkey 列表,這樣一次 RPC 請求能夠返回多條數據。

  • 注意點
    1.Bloom Filter(如下簡稱BF)
    • 有助於減少讀取時間。
    • HBase中實現了一個輕量級的內存BF結構,能夠使得Get操做時從磁盤只讀取可能包含目標Row的StoreFile。
    • BF自己存儲在每一個HFile的元數據中,永遠不須要更新。當由於Region部署到RegionServer而打開HFile時,BF將加載到內存中。
    • 默認開啓行級BF,可根據數據特徵修改如 行+列級
    • 衡量BF開啓後影響是否爲證實,能夠看RS的blockCacheHitRatio(BlockCache命中率)指標是否增大,增大表明正面影響。
    • 須要在刪除時重建,所以不適合具備大量刪除的場景。
    • BF分爲行模式和行-列模式,在大量列級PUT時就用行列模式,其餘時候用行模式便可。

12.1.3 put

放入一行數據.

注意點:

  • 該過程會先把put放入本地put緩存writeBuffer,達到閾值後再提交到服務器。
  • 批量導入
    批量導入是最有效率的HBase數據導入方式。
  • 海量數據寫入前預拆分Region,避免後序自動Split過程阻塞數據導入
  • 當對安全性要求沒那麼高時能夠使用WAL異步刷新寫入方式。甚至在某些場景下能夠禁用WAL

12.1.4 delete

刪除指定數據

12.1.5 append

在指定RowKey數據後追加數據

12.1.6 increment

可參考HBase Increment(計數器)簡介及性能測試

在RegionServer端原子性的對某個Value數值加或減,而不是加鎖的Read-Modify-Write。可做爲計數器使用。

12.2 HBase Shell

進入到HBase RS機器的$HBASE_HOME/bin目錄後,使用hbase shell命令啓動 shell客戶端便可。

12.3 二級索引

12.3.1 基本思路

二級索引
如上圖,單獨創建一個HBase表,存F:C1列到RowKey的索引。

那麼,當要查找知足F:C1=C11F:C2列數據,就能夠去索引表找到F:C1=C11對應的RowKey,再回原表查找該行的F:C2數據。

12.3.2 協處理器的實現方案

RegionObserverprePut在每次寫入主表數據時,寫一條到索引表,便可創建二級索引。

12.4 Schema設計

更多例子能夠看http://hbase.apache.org/book.html#schema.casestudies

12.4.1 列族

  • 列族數量越少越好
    HBase程序目前不能很好的支持超過2-3個列族。並且當前版本HBase的flush和合並操做都是以Region爲最小單位,也就是說列族之間會互相影響(好比大負載列族須要flush時,小負載列族必須進行沒必要要的flush操做致使IO)。
  • 列族多的壞處
    當一個表存在多個列族,且基數差距很大時,如A_CF100萬行,B_CF10億行。此時由於HBase按Region水平拆分,會致使A因列族B的數據量龐大而隨之被拆分到不少的region,致使訪問A列族就須要大量scan操做,效率變低。
  • 總的來講最好是設計一個列族就夠了,由於通常查詢請求也只訪問某個列族。
  • 列族名不宜過長
    列族名儘可能簡短甚至不需自描述,由於每一個KeyValue都會包含列族名,總空間會由於列族名更長而更大,是全局影響。
  • BlockSize
    每一個列族可配BlockSize(默認64KB)。當cell較大時需加大此配置。且該值和StoreFile的索引文件大小成反比。
  • 內存列族
    可在內存中定義列族,數據仍是會被持久化到磁盤,但這類列族在BlockCache中擁有最高優先級。

12.4.2 Region

  • 一個Region的大小通常不超過50GB。
  • 一個有1或2個列族的表最佳Region總數爲50-100個

12.4.3 RowKey設計

  • 避免設計連續RowKey致使數據熱點,致使過載而請求響應過慢或沒法響應,甚至影響熱點Region所在RS的其餘Region。若是Rowkey是按時間戳的方式遞增,不要將時間放在二進制碼的前面,建議將Rowkey的高位做爲散列字段,由程序循環生成,低位放時間字段,這樣將提升數據均衡分佈在每一個Regionserver實現負載均衡的概率。經常使用措施以下:
    • Salting-加鹽
      定期望放置的RS數量設計若干隨機前綴,在每一個RowKey前隨機添加,以將新數據均勻分散到集羣中,負載均衡。

      foo0001
      foo0002
      foo0003
      foo0004
      a-foo0003 b-foo0001 c-foo0003 c-foo0004 d-foo0002 

      優缺點:Salting可增長寫的吞吐量,但會下降讀效率,由於有隨機前綴,Scan和Get操做都受影響。

    • Hash算法
      用固定的hash算法,對每一個key求前綴,而後取hash後的部分字符串和原來的rowkey進行拼接。查詢時,也用這個算法先把原始RowKey進行轉換後再輸入到HBase進行查詢。
      hash算法
      優缺點:能夠必定程度上打散整個數據集,可是不利於scan操做,因爲不一樣數據的hash值有可能相同,因此在實際應用中,通常會使用md5計算,而後截取前幾位的字符串.

      examples: substring(MD5(設備ID),0,x) + 設備的ID,其中(x通常會取5到6位.)

    • 逆置Key
      將固定長度或範圍的前N個字符逆序。打亂了RowKey,但會犧牲排序性。
      逆置Key

    • 業務必須用時間序列或連續遞增數字時,能夠在開頭加如type這類的前綴使得分佈均勻。

  • 定位cell時,須要表名、RowKey、列族、列名和時間戳。並且StoreFile(HFile)有索引,cell過大會致使索引過大(使用時會放入內存)。因此須要設計schema時:
    • 列族名儘可能簡短,甚至只用一個字符;
    • 列名儘可能簡短:
    • RowKey保證惟一性,長度可讀、簡短,儘可能使用數字。
  • 版本號採用倒序的時間戳,這樣能夠快速返回最新版本數據
  • 同一行不一樣列族能夠擁有一樣的RowKey
  • RowKey具備不可變性
  • RowKey長度
    Rowkey是一個二進制碼流,Rowkey的長度被不少開發者建議說設計在10~100個字節,不過建議是越短越好,不要超過16個字節,緣由以下:
    1. 數據的持久化文件HFile中是按照KeyValue存儲的,若是Rowkey過長如100個字節,1000萬列數據光Rowkey就要佔用100*1000萬=10億個字節,近1G數據,這會極大影響HFile的存儲效率;
    2. MemStore將緩存部分數據到內存,若是Rowkey字段過長內存的有效利用率會下降,系統將沒法緩存更多的數據,這會下降檢索效率。所以Rowkey的字節長度越短越好。
    3. 操做系統大多64位,內存8字節對齊,控制在16個字節即8字節整數倍利用可利用OS最佳特性。

12.4.4 Versions

指定一個RowKey數據的最大保存的版本個數,默認爲3。越少越好,減少開銷。
若是版本過多可能致使compact時OOM。

若是非要使用不少版本,那最好考慮使用不一樣的行進行數據分離。

12.4.5 壓縮

詳見http://hbase.apache.org/book.html#compression
注意,壓縮技術雖然能減少在數據存到磁盤的大小,但在內存中或網絡傳輸時會膨脹。也就是說,不要妄圖經過壓縮來掩蓋過大的RowKey/列族名/列名的負面影響。

12.4.6 Cell

一個cell不該超過10MB,不然就應該把數據存入HDFS,而只在HBase存指針指向該數據。

12.5 join

  • HBase自己不支持join。
  • 能夠本身寫程序好比MR實現。
  • Phoenix裏面有join函數,可是性能不好,稍不注意會把集羣打掛。最好不要用hbase系來作join,這種仍是用hive來搞比較好。

13 HBase對比其餘技術

13.1 對比HDFS/MR

13.2 對比Cassandra

13.3 對比Kudu

  • Kudu不少地方的設計借鑑了HBase思想
  • Kudu限制較多
  • Kudu順序寫較HBase速度更快,但慢於HDFS;Kudu隨機讀較HBase慢,但比HDFS快得多。總的來講Kudu是一個折中設計。
  • Kudu是真正的列存儲,而HBase是列族存儲。指定查詢某幾列時,通常來講Kudu會更快

14 調優

14.1 參考

調優

14.2 操做系統

可參考:

調優勢:

  • 內存
    儘可能搞大些,HBase很耗內存,好比MemStore/BlockCache等。好比每一個RegionServer 擁有TB級磁盤,32GB內存,其中20GB分配給Region,MemStore128MB,其餘採用默認配置。
  • 使用64位OS和JVM
  • 關閉SWAP
    vm.swappiness = 0

14.3 GC

可參考:

  • HBase最佳實踐-CMS GC調優
    調優勢:

  • 普通內存時用-XX:+UseConcMarkSweepGC,即老年代CMS併發蒐集,年輕帶使用ParNew並行蒐集;

  • 大內存時啓用G1 GC,針對大內存(>=32GB)優化。

  • Concurrent Mode Failure
    調低-XX:CMSInitiatingOccupancyFraction(好比60-70%),不然可能致使老年代佔滿併發蒐集失敗觸發STW的FullGC。但不能太低,不然會致使過多的GC次數和CPU使用。

  • 老年代內存碎片
    默認開啓了MSLAB來解決此問題。還能夠在hbase-env.sh中設置-XX:PretenureSizeThreshold小於hbase.hregion.memstore.mslab.chunksize以使得MSLAB直接在老年代分配內存,這樣能夠省去在年輕帶Survivor區複製成本已經發生MinorGC時複製到老年代成本。

    相關配置以下:

    • hbase.hregion.memstore.mslab.enabled
      默認值true。開啓MSLAB來阻止堆內存碎片,減小GC STW頻率。
    • hbase.hregion.memstore.mslab.chunksize
      默認值2097152(2MB)。MSLAB的chunk最大值,單位字節。
    • hbase.hregion.memstore.mslab.max.allocation
      默認值262144(256KB)。MSLAB中的一次內存蒐集最大值,若是超過就直接從JVM 堆中搜集。
    • hbase.hregion.memstore.chunkpool.maxsize
      默認值爲0.0,HBase2.0後爲1.0。在整個MemStore 能夠佔用的堆內存中,chunkPool可佔用的最大比例,範圍爲0.0~1.0。

14.4 HBase參數

可參考Recommended Configurations

14.4.1 hedged對衝讀

14.4.2 Region Compact

  • 在某些場景中,put會被阻塞在MemStore上,由於太多的小StoreFile文件被反覆合併。
  • 可在某些重要場景的關閉hbase表的major compact,在非高峯期的時候再手動調用major compact,能夠減小split的同時顯著提供集羣的性能和吞吐量。

14.4.3 MemStoreFlush

Memstore配置適合與否對性能影響很大,頻繁的flush會帶來額外負載影響讀寫性能。
可參考Configuring HBase Memstore: What You Should Know

還須要看這裏

14.4.4 綜合

14.5 Zookeeper

14.6 Schema設計

14.7 代碼優化

14.7.1 Bytes.toBytes

儘可能少用Bytes.toBytes,由於在循環或MR任務中,這種重複的轉換代價昂貴,應該以下定義:

public static final byte[] CF = "cf".getBytes(); public static final byte[] ATTR = "attr".getBytes(); ... Get get = new Get(rowkey); Result r = table.get(get); byte[] b = r.getValue(CF, ATTR); // returns current version of value 

14.8 寫優化

14.8.1 批量數據加載

使用基於MR的BulkLoad相較於HBaseAPI,使用更少的CPU和網絡資源。

14.8.2 預分區

14.8.3 WAL優化

在容許的場景,可將WALflush設爲異步甚至禁用,壞處是丟數據風險。

可在批量加載數據時禁用WALflush。

14.8.4 防止Region寫入熱點

14.9 讀優化

14.9.1 GC

14.9.2 磁盤

對實時性要求高的使用SSD

14.9.3 HFile Compact

  • 關閉自動Marjor Compact,轉爲業務低峯期手動
  • 根據具體場景調整Compact觸發閾值/每次Compact文件數量等,減小每次scan時的HFile數,但必須綜合考慮

14.9.4 BlockCache緩存

  • CombinedBlockCache
    即啓用BucketCache,全部DataBlock保存在BucketCache,而MetaBlock即Index和BloomFilter塊保存在Heap中的 LruBlockCache。
  • hfile.block.cache.size
    即緩存StoreFile所使用的BlockCache所佔Heap的百分比,默認是0.4即40%。但同時要注意BlockCache和MemStore大小的總和所佔堆內存比例不能超過80%,由於至少要留20%的內存空間給HBase進行必要的操做。

14.9.5 本地性

RS與DN混合部署,提高數據讀寫本地性。

14.9.6 對衝讀

Hedged Reads(對衝讀),是Hadoop 2.4.0 引入的一項HDFS特性。

  • 普通的每一個HDFS讀請求都對應一個線程
  • 對衝讀開啓後,若是讀取未返回,則客戶端會針對相同數據的不一樣HDFS Block副本生成第二個讀請求。
  • 使用先返回的任何一個讀請求,並丟棄另外一個。
  • 可經過hedgedReadOps(已觸發對衝讀取線程的次數。 這可能代表讀取請求一般很慢,或者對衝讀取的觸發過快) 和 hedgeReadOpsWin(對衝讀取線程比原始線程快的次數。 這可能表示給定的RegionServer在處理請求時遇到問題) 指標評估開啓對衝讀的效果
  • 在追求最大化吞吐量時,開啓對衝讀可能致使性能降低
  • 須要在hbase-site.xml配置以下內容:
<property> <!--專用於對衝讀的線程數,設爲0表明禁用對衝讀. 線程池大小能夠與讀handler的數目相同--> <name>dfs.client.hedged.read.threadpool.size</name> <value>50</value> </property> <property> <!--超時閾值,超事後開啓第二個讀線程.不要調的過小以避免頻繁觸發對衝讀--> <name>dfs.client.hedged.read.threshold.millis</name> <value>100</value> </property> 

14.9.7 短路讀

  • 若是不開啓,則讀取本地DN上的數據時也須要RPC請求使用Socket,層層處理後返回
  • 開啓短路讀後,能夠直接由DN打開目標文件和對應校驗文件並把文件描述符直接返回,Client收到後可直接打開、讀取數據便可
  • 配置方式以下:
    修改hbase-site.xml文件:
    <property> <name>dfs.client.read.shortcircuit</name> <value>true</value> <description> This configuration parameter turns on short-circuit local reads. </description> </property> <property> <name>dfs.domain.socket.path</name> <value>/home/stack/sockets/short_circuit_read_socket_PORT</value> <description> Optional. This is a path to a UNIX domain socket that will be used for communication between the DataNode and local HDFS clients. If the string "_PORT" is present in this path, it will be replaced by the TCP port of the DataNode. </description> </property> 

14.9.8 高可用讀

爲了防止RS掛掉時帶來的其上Region不可用及恢復的時間空檔,可以使用HBase Replication:
在這裏插入圖片描述
注意,該方式由於須要數據同步因此備集羣確定會有必定延遲。
在這裏插入圖片描述

14.9.9 scan只選擇要讀的列

Scan時建議指定須要的Column Family,減小通訊量,不然scan操做默認會返回整個row的全部CF/Column的數據.

Scan s = new Scan(); // 選擇列族進行Scan s.addFamily(Bytes.toBytes("cf1")); // 選擇列族/列進行scan s.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("c1")); // 僅檢索列族cf下的c1列和c2列 Map<byte [], NavigableSet<byte []>> familyMap = new HashMap<>(); NavigableSet cs = new TreeSet(); cs.add("c1"); cs.add("c2"); familyMap.put(Bytes.toBytes("cf1"), cs); s.setFamilyMap(familyMap); 

14.9.10 記得關閉資源

使用完後,關閉Connection/Table/ResultScanner,特別是不關閉ResultScanner可能致使RegionServer出現性能問題,因此最好是把他放在try中:

Scan scan = new Scan(); // set attrs... ResultScanner rs = table.getScanner(scan); try { for (Result r = rs.next(); r != null; r = rs.next()) { // process result... } finally { rs.close(); // always close the ResultScanner! } table.close(); 

14.9.11 合理使用Filter

14.9.12 合理使用BloomFilter

按需調整BloomFilter,默認是Row模式

14.10 HBase刪除優化

  • 切記刪除的原理是寫入一個墓碑,會有寫入開銷和讀數據時開銷
  • 批量刪除
    每一個Table.delete(Delete)就會有一個RPC請求發送到RS,而不能利用writerBuffer,因此大量刪除時請使用Table.delete(List)

14.11 HDFS

  • 數據本地性
  • 短路讀

15 Phoenix on HBase

關於Phoenix on HBase,即Sql化的HBase服務,能夠參考Phoenix學習

常見問題

1 爲何HBase查詢速度快

  1. 首先是能夠從hbase:meta快速的定位到Region,並且優先MemStore(SkipList跳錶)查詢,由於HBase的寫入特性因此MemStore若是找到符合要求的確定就是最新的直接返回便可。

若是找不到還能經過BloomFilter/DataBlock索引等高效的從BlockCache查找,

仍是沒有的話就相對快速的從已按RowKey升序排序的HFile中查找。
2. 列式存儲,若是查找的列在某個列族,只需查找定位Region的某一個Store便可
3. 可以使用豐富的過濾器來加快Scan速度。
4. 後臺會按期拆分Region,將大的Region分佈到多個RS;按期合併,將大量小StoreFile合併爲一個,同時刪除無效信息,減小掃描讀取數據量。

2 爲何HBase寫入速度快

雖然HBase不少時候是隨機寫入,但由於引入了內存中的MemStore(由SkipList實現,是多層有序數據結構),批量順序輸入HDFS,因此可先寫入將隨機寫轉爲了順序寫

3 頻繁出現數據沒法寫入

  • 檢查RSMemStore內存設置

  • 檢查hbase.hstore.blockingStoreFiles參數

    Flush後若是該Store的StoreFile數量若是超過了hbase.hstore.blockingStoreFiles,則會阻塞該Region的更新寫入操做,直到有Compact發生減小了StoreFile數量或等待until hbase.hstore.blockingWaitTime超時,而後繼續正常Flush。

  • 檢查hbase.hregion.memstore.flush.size hbase.hregion.memstore.block.multiplier
    當MemStore的數據達到hbase.hregion.memstore.block.multiplier 乘以 hbase.hregion.memstore.flush.size字節時,會阻塞寫入,主要是爲了防止在update高峯期間MemStore大小失控,形成其flush的文件須要很長時間來compact或split,甚至形成OOM服務直接down掉。內存足夠大時,可調大該值。

  • 檢查hbase.regionserver.global.memstore.size
    hbase.regionserver.global.memstore.size設定了一個RS內所有Memstore的總大小閾值,默認大小爲Heap的40%,達到閾值之後就會阻塞更新請求,並開始RS級別的MemStore flush。

4 RegionServer掛掉或失聯

4.1 問題描述

RS由於長時間FullGC 致使STW,沒法及時發送心跳到ZK,因此被ZK標爲宕機。此時會Master會注意到該RS節點掛掉,將其上的Region遷移到其餘RS節點。待故障RS恢復後,發現本身被標爲宕機,因此只能自殺,不然會出現錯亂。

4.2 解決方案

部分轉自網易範欣欣-HBase原理-數據讀取流程解析

5.1 HBase做爲列式存儲,爲何它的scan性能這麼低呢,列式存儲不是更有利於scan操做麼?Parquet格式也是列式,但它的scan這麼優秀,他們的性能差別並非由於數據組織方式形成的麼?謝謝啦

  1. HBase不徹底是列式存儲,確切的說是列族式存儲,HBase中能夠定義一個列族,列族下能夠有都個列,這些列的數據是存在一塊兒的。並且一般狀況下咱們建議列族個數不大於2個,這樣的話每一個列族下面必然會有不少列。所以HBase並非列式存儲,更有點像行式存儲。
  2. HBase掃描本質上是一個一個的隨機讀,不能作到像HDFS(Parquet)這樣的順序掃描。試想,1000w數據一條一條get出來,性能必然不會很好。問題就來了,HBase爲何不支持順序掃描?

由於HBase支持更新操做以及多版本的概念,這個很重要。能夠說若是支持更新操做以及多版本的話,掃描性能就不會太好。原理是HBase是一個類LSM數據結構,數據寫入以後先寫入內存,內存達到必定程度就會造成一個文件,所以HBase的一個列族會有不少文件存在。由於更新以及多版本的緣由,一個數據就可能存在於多個文件,因此須要一個文件一個文件查找才能定位出具體數據。

因此HBase架構自己我的認爲並不適合作大規模scan,很大規模的scan建議仍是用Parquet,能夠把HBase按期導出到Parquet來scan

5.2 Kudu也是採用的類LSM數據結構,可是卻能達到parquet的掃描速度(kudu是純列式的),kudu的一個列也會造成不少文件,可是好像並沒影響它的性能?

  1. kudu性能並無達到parquet的掃描速度,能夠說介於HBase和HDFS(Parquet)之間

  2. kudu比HBase掃描性能好,是由於kudu是純列存,掃描不會出現跳躍讀的狀況,而HBase可能會跳躍seek,這是本質的區別。

  3. 但kudu掃描性能又沒有Parquet好,就是由於kudu是LSM結構,它掃描的時候仍是會同時順序掃描多個文件,並比較key值大小。

    而Parquet只須要順序對一個Block塊中的數據進行掃描便可,這個是二者的重要區別。

因此說hbase相比parquet,這兩個方面都是scan的劣勢。

好文推薦

綜合

原理

索引

調優

0xFF 參考文檔

2013-12-29 02:34:18 yueyedeai 閱讀數 2992
    HBase  Canary 用於檢測HBase 系統的狀態。它對指定表的每個region 抓取一行,來探測失敗或者延遲。
    hbase org.apache.hadoop.hbase.tool.Canary -help
   
Usage: bin/hbase org.apache.hadoop.hbase.tool.Canary [opts] [table1 [table2]...] | [regionserver1 [regionserver2]..]
 where [opts] are:
   -help          Show this help and exit.
   -regionserver  replace the table argument to regionserver,
      which means to enable regionserver mode
   -daemon        Continuous check at defined intervals.
   -interval <N>  Interval between checks (sec)
   -e             Use region/regionserver as regular expression
      which means the region/regionserver is regular expression pattern
   -f <B>         stop whole program if first error occurs, default is true
   -t <N>         timeout for a check, default is 600000 (milisecs)

(1)檢測每個表的每個region的每個列簇。

    hbase  org.apache.hadoop.hbase.tool.Canary

(2)檢測指定表的每個region的每個列簇,表之間用空格分開

    hbase orghapache.hadoop.hbase.tool.Canary test-01 test-02

(3)檢測regionserver 

     hbase orghapache.hadoop.hbase.tool.Canary  -regionserver

(4)正則表達式檢測

      hbase orghapache.hadoop.hbase.tool.Canary  -e test-0[1-2]

 (5)以daemon模式運行

      hbase orghapache.hadoop.hbase.tool.Canary -daemon 

      時間間隔爲50000毫秒,出現錯誤不會中止

      hbase orghapache.hadoop.hbase.tool.Canary -daemon -interval 50000 -f false
(6)指定超時

 

     hbase orghapache.hadoop.hbase.tool.Canary -daemon  -t 6000000

2016-12-10 09:42:04 qq_806913882 閱讀數 9060

Hbase的訪問方式

一、Native Java API:最常規和高效的訪問方式; 
二、HBase Shell:HBase的命令行工具,最簡單的接口,適合HBase管理使用; 
三、Thrift Gateway:利用Thrift序列化技術,支持C++,PHP,Python等多種語言,適合其餘異構系統在線訪問HBase表數據; 
四、REST Gateway:支持REST 風格的Http API訪問HBase, 解除了語言限制; 
五、MapReduce:直接使用MapReduce做業處理Hbase數據; 
六、使用Pig/hive處理Hbase數據。

Hbase shell基本用法

hbase shell 的help對語法的介紹很全,搞明白help上的實例,對hbase shell的操做就很熟練了。 
hbase shell 的操做分爲 10類,本文只介紹前4類,分別是:

Group name commands
general status, table_help, version, whoami
ddl alter, alter_async, alter_status, create, describe, disable, disable_all, drop, drop_all, enable, enable_all, exists, get_table, is_disabled, is_enabled, list, show_filters
namespace alter_namespace, create_namespace, describe_namespace, drop_namespace, list_namespace, list_namespace_tables
dml append, count, delete, deleteall, get, get_counter, incr, put, scan, truncate, truncate_preserve
tools assign, balance_switch, balancer, catalogjanitor_enabled, catalogjanitor_run, catalogjanitor_switch, close_region, compact, compact_rs, flush, major_compact, merge_region, move, split, trace, unassign, wal_roll, zk_dump
replication 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
snapshots clone_snapshot, delete_all_snapshot, delete_snapshot, list_snapshots, restore_snapshot, snapshot
configuration update_all_config, update_config
security grant, revoke, user_permission
visibility labels add_labels, clear_auths, get_auths, list_labels, set_auths, set_visibility

Hbase shell 命令具體介紹

general

status

做用:查詢當前服務器狀態。 
實例:

hbase(main):006:0> status 1 servers, 0 dead, 5.0000 average load

更多用法:

hbase(main):002:0> help 'status' hbase> status hbase> status 'simple' hbase> status 'summary' hbase> status 'detailed' hbase> status 'replication' hbase> status 'replication', 'source' hbase> status 'replication', 'sink'

version

做用:查看hbase版本 
實例:

hbase(main):010:0> version 1.0.3, rf1e1312f9790a7c40f6a4b5a1bab2ea1dd559890, Tue Jan 19 19:26:53 PST 2016

whoami

做用:查詢當前hbase用戶 
實例:

hbase(main):011:0> whoami datanode1 (auth:SIMPLE) groups: datanode1

ddl

create

做用:建立一個表 
實例:

#在命名空間ns1下,建立表t1,其中有一個列族f1,f1的版本數爲5 hbase> create 'ns1:t1', {NAME => 'f1', VERSIONS => 5} #在默認命名空間下,建立表t1,有三個列族f1,f2,f3 hbase> create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'} #等價於 hbase> create 't1', 'f1', 'f2', 'f3' #建立表t1,列族f1,並設置f1的版本數爲1,屬性TTL爲2592000,屬性BLOCKCACHE爲true。屬性的含義在這就不解釋了。 hbase> create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, BLOCKCACHE => true} # 建立表t1,列族f1,並設置f1的配置hbase.hstore.blockingStoreFiles 爲 10 hbase> create 't1', {NAME => 'f1', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}} #建立表時,配置信息能夠放在最後,例如: hbase> create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40'] hbase> create 't1', 'f1', SPLITS => ['10', '20', '30', '40'] hbase> create 't1', 'f1', SPLITS_FILE => 'splits.txt', OWNER => 'johndoe' hbase> create 't1', {NAME => 'f1', VERSIONS => 5}, METADATA => { 'mykey' => 'myvalue' } hbase> # Optionally pre-split the table into NUMREGIONS, using hbase> # SPLITALGO ("HexStringSplit", "UniformSplit" or classname) #指定Pre-splitting的region的塊數,和分割函數。 hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'} hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit', REGION_REPLICATION => 2, CONFIGURATION => {'hbase.hregion.scan.loadColumnFamiliesOnDemand' => 'true'}} #也能夠用另外一個表t2的引用去建立一個新表t1,t1表具備t2的全部列族,而且加上f1列族。 hbase> t1 = create 't2', 'f1'

alter

做用:能夠修改,增長,刪除表的列族信息、屬性、配置等。 
實例:

#對於表t1,若是t1含有f1列族,則將f1列族的版本數設爲5. # 若是t1不含f1列數,則添加f1列族到表t1上。並將f1的版本數設置爲5. hbase> alter 't1', NAME => 'f1', VERSIONS => 5 #添加或修改多個列族 hbase> alter 't1', 'f1', {NAME => 'f2', IN_MEMORY => true}, {NAME => 'f3', VERSIONS => 5} #刪除 命名空間ns1 中的 表t1 的 列族f1 的兩種方法 hbase> alter 'ns1:t1', NAME => 'f1', METHOD => 'delete' hbase> alter 'ns1:t1', 'delete' => 'f1' #修改表t1的MAX_FILESIZE屬性的值。 hbase> alter 't1', MAX_FILESIZE => '134217728' # 修改表t1或者列族f2的配置 hbase> alter 't1', CONFIGURATION => {'hbase.hregion.scan.loadColumnFamiliesOnDemand' => 'true'} hbase> alter 't1', {NAME => 'f2', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}} #刪除屬性 hbase> alter 't1', METHOD => 'table_att_unset', NAME => 'MAX_FILESIZE' hbase> alter 't1', METHOD => 'table_att_unset', NAME => 'coprocessor$1' #一次性修改多個屬性值 hbase> alter 't1', { NAME => 'f1', VERSIONS => 3 }, { MAX_FILESIZE => '134217728' }, { METHOD => 'delete', NAME => 'f2' }, OWNER => 'johndoe', METADATA => { 'mykey' => 'myvalue' } hbase(main):014:0> 

alter_async

做用:異步更新,與alter的做用相同。

describe / desc

做用:顯示錶的屬性,表的列族的屬性。 
實例:

# 命令:顯示錶t1信息 hbase> describe 't3' # 顯示出的信息: Table t3 is ENABLED t3 COLUMN FAMILIES DESCRIPTION {NAME => 'colfa', BLOOMFILTER => 'ROW', VERSIONS => '1', IN_MEMORY => 'false', KEEP _DELETED_CELLS => 'false', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'NONE', TT L => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', RE PLICATION_SCOPE => '0'} 1 row(s) in 0.0200 seconds

disable

做用:disable表,刪除一個表以前,必須把表disable 
實例:

#disable表t1 hbase> disable 't1'

disable_all

做用: disable多個表,接受正則表達好似。 
實例:

# disable 全部以t開頭的表 hbase> disable_all 't.*'

drop

做用:刪除表。可是刪除以前,必須disable該表 
實例:

# 刪除表t2 hbase(main):005:0> disable 't2' 0 row(s) in 1.2270 seconds hbase(main):006:0> drop 't2' 0 row(s) in 0.1750 seconds

drop_all

做用:刪除多個表,接受正則表達式。 
實例:

# 刪除全部表名以t開頭的表 hbase> drop_all 't.*'

enable

做用:與disble相反,enable表

enable_all

做用:enable多個表,接受正則表達式

exists

做用:查詢表是否存在 
實例:

# 查詢表名爲t1的表是否存在 hbase(main):003:0> exists 't1' Table t1 does exist 0 row(s) in 0.3170 seconds

get_table

做用:返回一個表引用對象 
實例:

# 將表t1的應用對象賦給t1d hbase> t1d = get_table 't1' #t1d操做 t1d.scan t1d.describe ...

is_disabled

做用:查詢表是否disable

is_enabled

做用:查詢表是否enable

list

做用:顯示出hbase中的表,接受正則表達式 
實例:

#顯示全部命名空間的全部表 hbase> list #顯示錶名以abc開頭的表 hbase> list 'abc.*' #顯示命名空間ns下的表名以abc開頭的表 hbase> list 'ns:abc.*' #顯示命名空間ns下的全部表 hbase> list 'ns:.*'

show_filters

做用:顯示出全部過濾器 
實例:

#顯示出全部過濾器 hbase> show_filters

namespace

create_namespace

做用:建立命名空間 
實例:

# 建立命名空間ns1 hbase> create_namespace 'ns1' # 建立命名空間ns1,而且配置ns1 hbase> create_namespace 'ns1', {'PROPERTY_NAME'=>'PROPERTY_VALUE'}

alter_namespace

做用:修改,添加,刪除命名空間的屬性 
實例:

# 設置命名空間ns1的屬性 hbase> alter_namespace 'ns1', {METHOD => 'set', 'PROPERTY_NAME' => 'PROPERTY_VALUE'} # 刪除命名空間ns1的屬性 hbase> alter_namespace 'ns1', {METHOD => 'unset', NAME=>'PROPERTY_NAME'}

describe_namespace

做用:描述命名空間 
實例:

# 描述命名空間ns1 hbase(main):008:0> describe_namespace 'ns1' DESCRIPTION {NAME => 'ns1', PROPERTY_NAME => 'PROPERTY_VALUE'} 1 row(s) in 0.0040 seconds

drop_namespace

做用:刪除命名空間,命名空間必須爲空, 不包含表

list_namespace

做用:列出全部命名空間 
實例:

# 列出全部命名空間 hbase(main):008:0> describe_namespace 'ns1' DESCRIPTION {NAME => 'ns1', PROPERTY_NAME => 'PROPERTY_VALUE'} 1 row(s) in 0.0040 seconds

list_namespace_tables

做用:顯示出某一個命名空間下的全部表 
實例:

# 顯示出默認命名空間下的全部表 hbase(main):004:0> list_namespace_tables 'default' TABLE peoples t1 t3 3 row(s) in 0.0210 seconds

dml

scan

做用:掃描某一個表 
實例:

# 掃描命名空間hbase下的meta表,顯示出meta表的全部數據 hbase> scan 'hbase:meta' # 掃描命名空間hbase下的meta表的列族info的列regioninfo,顯示出meta表的列族info下的regioninfo列的全部數據 hbase> scan 'hbase:meta', {COLUMNS => 'info:regioninfo'} # 掃描命名空間ns1下表t1的列族'c1'和'c2'。顯示出命名空間ns1下表t1的列族'c1'和'c2'的全部數據 hbase> scan 'ns1:t1', {COLUMNS => ['c1', 'c2']} # 掃描命名空間ns1下表t1的列族'c1'和'c2'。顯示出命名空間ns1下表t1的列族'c1'和'c2',且只顯示前10個rowkey的數據。 hbase> scan 'ns1:t1', {COLUMNS => ['c1', 'c2'], LIMIT => 10} # 掃描命名空間ns1下表t1的列族'c1'和'c2'。顯示出命名空間ns1下表t1的列族'c1'和'c2',且只顯示從rowkey=「xyz」開始的前10個rowkey的數據。 hbase> scan 'ns1:t1', {COLUMNS => ['c1', 'c2'], LIMIT => 10, STARTROW => 'xyz'} # 掃描默認命名空間下表t1的列族c1時間戳從'1303668804'到'1303668904'的數據 hbase> scan 't1', {COLUMNS => 'c1', TIMERANGE => [1303668804, 1303668904]} # 反向顯示錶t1的數據 hbase> scan 't1', {REVERSED => true} # 過濾顯示錶t1的數據 hbase> scan 't1', {FILTER => "(PrefixFilter ('row2') AND (QualifierFilter (>=, 'binary:xyz'))) AND (TimestampsFilter ( 123, 456))"} # RAW爲true,顯示出表t1的全部數據,包括已經刪除的 hbase> scan 't1', {RAW => true, VERSIONS => 10} # 表t1的引用的掃描 hbase> t11 = get_table 't1' hbase> t11.scan

append

做用: 
實例:

# 向表t1的rowkey爲r1的列c1的值後面添加字符串value hbase> append 't1', 'r1', 'c1', 'value' #表t1的引用對象t11使用append。 hbase> t11.append 'r1', 'c1', 'value'

count

做用:統計表的行數 
實例:

#統計表t1的行數 count 't1' #統計表t1的行數,其中參數的含義以下 # INTERVAL設置多少行顯示一次及對應的rowkey,默認1000;CACHE每次去取的緩存區大小,默認是10,調整該參數可提升查詢速度 # 例如,查詢表t1中的行數,每10條顯示一次,緩存區爲1000 count 't1', INTERVAL => 10, CACHE => 1000 #對應的表應用對象的用法 hbase> t.count hbase> t.count INTERVAL => 100000 hbase> t.count CACHE => 1000 hbase> t.count INTERVAL => 10, CACHE => 1000

delete

做用:刪除表中cell數據 
實例:

# 刪除命名空間ns1下的表t1的rowkey的r1的列c1,時間戳爲ts1 hbase> delete 'ns1:t1', 'r1', 'c1', ts1 # 刪除默認命名空間下的表t1的rowkey的r1的列c1,時間戳爲ts1 hbase> delete 't1', 'r1', 'c1', ts1 #應用對象的用法 hbase> t.delete 'r1', 'c1', ts1

deleteall

做用:一次性刪除多個cell數據 
實例:

#刪除命名空間ns1下表t1的rowkey爲r1的全部數據 hbase> deleteall 'ns1:t1', 'r1' #刪除默認命名空間下表t1的rowkey爲r1的全部數據 hbase> deleteall 't1', 'r1' #刪除命名空間ns1下表t1的rowkey爲r1的列c1的全部數據 hbase> deleteall 't1', 'r1', 'c1' # 刪除默認命名空間下的表t1的rowkey的r1的列c1,時間戳爲ts1 hbase> deleteall 't1', 'r1', 'c1', ts1 #應用對象的用法 hbase> t.deleteall 'r1' hbase> t.deleteall 'r1', 'c1' hbase> t.deleteall 'r1', 'c1', ts1

get

做用:獲得某一列或cell的數據。 
實例:

#獲得命名空間ns1下表t1的rowkey爲r1的數據 hbase> get 'ns1:t1', 'r1' #獲得默認命名空間下表t1的rowkey爲r1的數據 hbase> get 't1', 'r1' #獲得默認命名空間下表t1的rowkey爲r1,時間戳範圍在ts1和ts2之間的數據 hbase> get 't1', 'r1', {TIMERANGE => [ts1, ts2]} #獲得默認命名空間下表t1的rowkey爲r1的c1列的數據 hbase> get 't1', 'r1', {COLUMN => 'c1'} #獲得默認命名空間下表t1的rowkey爲r1的c1,c2,c3列的數據 hbase> get 't1', 'r1', {COLUMN => ['c1', 'c2', 'c3']} #獲得默認命名空間下表t1的rowkey爲r1的c1列,時間戳爲ts1的數據 hbase> get 't1', 'r1', {COLUMN => 'c1', TIMESTAMP => ts1} #獲得默認命名空間下表t1的rowkey爲r1的c1列,時間戳範圍爲ts1到ts2,版本數爲4的數據 hbase> get 't1', 'r1', {COLUMN => 'c1', TIMERANGE => [ts1, ts2], VERSIONS => 4} #應用對象的用法 hbase> t.get 'r1' hbase> t.get 'r1', {TIMERANGE => [ts1, ts2]} hbase> t.get 'r1', {COLUMN => 'c1'} hbase> t.get 'r1', {COLUMN => ['c1', 'c2', 'c3']} hbase> t.get 'r1', {COLUMN => 'c1', TIMESTAMP => ts1} hbase> t.get 'r1', {COLUMN => 'c1', TIMERANGE => [ts1, ts2], VERSIONS => 4} hbase> t.get 'r1', {COLUMN => 'c1', TIMESTAMP => ts1, VERSIONS => 4}

put

做用:添加cell 
實例:

# 向命名空間ns1下表t1的rowkey爲r1的列c1添加數據 hbase> put 'ns1:t1', 'r1', 'c1', 'value' # 向默認命名空間下表t1的rowkey爲r1的列c1添加數據 hbase> put 't1', 'r1', 'c1', 'value' # 向默認命名空間下表t1的rowkey爲r1的列c1添加數據,並設置時間戳爲ts1 hbase> put 't1', 'r1', 'c1', 'value', ts1 # 向默認命名空間下表t1的rowkey爲r1的列c1添加數據,並設置時間戳爲ts1,並設置屬性 hbase> put 't1', 'r1', 'c1', 'value', ts1, {ATTRIBUTES=>{'mykey'=>'myvalue'}} #引用對象的用法 t.put 'r1', 'c1', 'value', ts1, {ATTRIBUTES=>{'mykey'=>'myvalue'}}

truncate

做用:刪除表,不用disable 
實例:

#刪除表t3,不用disable truncate 't3'

引用文檔:

http://blog.csdn.net/woshiwanxin102213/article/details/17611457 
split的三種方式

2018-03-15 11:45:40 LEoe_ 閱讀數 6470

在安裝以前先介紹下Hbase,Hadoop生態系統中HBase所處位置,實現的功能,解決的問題。

HBase – Hadoop Database,是一個高可靠性、高性能、面向列、可伸縮的分佈式存儲系統,利用HBase技術可在廉價PC Server上搭建起大規模結構化存儲集羣。

HBase是Google Bigtable的開源實現,相似Google Bigtable利用GFS做爲其文件存儲系統,HBase利用Hadoop HDFS做爲其文件存儲系統;Google運行MapReduce來處理Bigtable中的海量數據,HBase一樣利用Hadoop MapReduce來處理HBase中的海量數據;Google Bigtable利用 Chubby做爲協同服務,HBase利用Zookeeper做爲對應。HBase是Google Bigtable的開源實現,它利用Hadoop HDFS做爲其文件存儲系統,利用Hadoop MapReduce來處理HBase中的海量數據,利用Zookeeper做爲協同服務。

圖2-1 EcoSystem生態系統構建

上圖描述了Hadoop EcoSystem中的各層系統,其中HBase位於結構化存儲層,Hadoop HDFS爲HBase提供了高可靠性的底層存儲支持,Hadoop MapReduce爲HBase提供了高性能的計算能力,Zookeeper爲HBase提供了穩定服務和failover機制。 
此外,Pig和Hive還爲HBase提供了高層語言支持,使得在HBase上進行數據統計處理變的很是簡單。 Sqoop則爲HBase提供了方便的RDBMS數據導入功能,使得傳統數據庫數據向HBase中遷移變的很是方便。 
HBase具備如下特性:

  • 線性及模塊可擴展性。
  • 嚴格一致讀寫性。
  • 可配置的表自動分割策略。
  • RegionServer自動故障恢復。
  • 便利地備份MapReduce做業的基類。
  • 便於客戶端訪問的Java API
  • 爲實時查詢提供了塊緩存和Bloom Filter。
  • 可經過服務器端的過濾器進行查詢下推預測。
  • 提供XML、Protobuf及二進制編碼的Thrift網管和REST-ful網絡服務。
  • 可擴展的JIRB(jruby-based)shell
  • 支持經過Hadoop或JMX將度量標準倒出到文件或Ganglia中。

1、實驗環境

虛擬機數量:3臺 
操做系統:Ubuntu 14.04 
實驗環境及版本: 
Hadoop:Hadoop 2.2.0 
Java:java version 「1.7.0_51」 
HBase:hbase-0.96.0.tar.gz 
Zookeeper:zookeeper-3.4.5.tar.gz

Hbase安裝包:https://download.csdn.net/download/leoe_/10292264


2、實驗內容及步驟

一、準備工做

(1)SSH無密驗證雙向互通: 
要達到雙向無密碼互通咱們只須要將每臺機器的公鑰均寫入到同一個受權文件,而後將受權文件複製到每臺機器上便可(具體公鑰寫入受權文件的方法參考Hadoop徹底分佈式的安裝試驗)。這樣就能夠達到雙向無密碼互通。這樣Hadoop的腳本才能夠遠程操控其餘的Hadoop和HBase進程。 
(2)Hadoop徹底分佈式的安裝: 
必須能夠正常啓動 HDFS 系統,確保HDFS可以上傳和讀寫文件。

(3) Zookeeper分佈式的安裝: 
分佈式HBase安裝依賴於正在運行的ZooKeeper集羣。Apache HBase默認狀況下爲您管理ZooKeeper「集羣」。它將啓動和中止ZooKeeper集合做爲HBase啓動/中止過程的一部分。你還能夠獨立於HBase管理ZooKeeper集羣,只須要在Hbase的配置文件hbase-env.sh中作一些設置便可。

二、ntp時間同步服務器搭建與使用

查找jdk版本Ntp時間同步服務器安裝配置: 
集羣的時鐘要保證基本的一致。稍有不一致是能夠容忍的,可是很大的不一致會 形成奇怪的行爲。運行 NTP 或者其餘什麼東西來同步你的時間。下面對Ntp時間同步服務器NTP安裝配置步驟以下:


2.1 服務端(master) 
安裝ntp服務,代碼以下:

# apt-get install ntp

安裝後默認啓動服務,若是沒有啓動,啓動之。

# /etc/init.d/ntp start

圖2-1 啓動服務

# vim /etc/ntp.conf 

修改成以下:

圖2-2 修改配置文件

重啓ntp服務

# /etc/init.d/ntp restart

2.2 客戶端(slaver一、slaver2) 
1)使用ntpdate命令,若是不存在這個命令,則先安裝apt-get install ntp 
2)同步時間

# /usr/sbin/ntpdate 192.168.60.190 //即便用ip爲192.168.60.190的ntp服務器同步時間

3)設置定時同步

vim /etc/crontab

圖2-3 修改配置文件

系統便會在天天早上1點30分自動將系統時間同步到ntp服務器的時間,固然這裏crontab的時間是指客戶端的時間,同步後等同於ntp服務器的時間。


2.3 ulimit 和 nproc設置(集羣均配置)

HBase是數據庫,會在同一時間使用不少的文件句柄。大多數Ubuntu系統使用的默認值1024是不能知足的,因此你須要修改你的最大文件句柄限制。能夠設置到10k. 你還須要修改 hbase 用戶的 nproc,若是太低會形成 OutOfMemoryError異常。 
須要澄清的,這兩個設置是針對操做系統的,不是Hbase自己的。有一個常見的錯誤是Hbase運行的用戶,和設置最大值的用戶不是一個用戶。在Hbase啓動的時候,第一行日誌會如今ulimit信息,因此你最好檢查一下。 
(1)limits.conf文件

# vim /etc/security/limits.conf 

添加以下內容: 
圖2-4 修改配置文件

(2)common-session文件

# vim /etc/pam.d/common-session 

加上這一行:

session required  pam_limits.so

以下:

圖2-5 修改配置文件

不然在/etc/security/limits.conf上的配置不會生效. 
還有註銷再登陸,這些配置才能生效。

(3)文件中修改內容解釋: 
在/etc/security/limits.conf

root  -       nofile  65536   root最大能打開的文件數不超過65536
root   hard    nproc   16384  root用戶最大能開啓的進程數不超過16384

在/etc/pam.d/common-session 
pam_limits.so模塊能夠使用在對通常應用程序使用的資源限制方面。若是須要在SSH服務器上對來自不一樣用戶的ssh訪問進行限制,就能夠調用該模塊來實現相關功能。當須要限制用戶admin登陸到SSH服務器時的最大鏈接數(防止同一個用戶開啓過多的登陸進程),就能夠在/etc/pam.d/sshd文件中增長一行對pam_limits.so模塊的調用。


三、HBase的安裝和配置

解壓縮:

# tar -zxvf hbase-0.96.0.tar.gz

更更名字爲hbase:mv hbase-0.96.0 hbase

3.1 配置conf/hbase-env.sh

export JAVA_HOME==/usr/lib/jvm/jdk1.7.0_51 export HBASE_CLASSPATH=~/u/etc/hadoop export HBASE_PID_DIR=/soft/hbase/pids 修改: export HBASE_MANAGES_ZK=false

圖4-1 配置HBase配置文件 
圖4-2 配置HBase配置文件

解釋:一個分佈式運行的Hbase依賴一個zookeeper集羣。全部的節點和客戶端都必須可以訪問zookeeper。默認的狀況下Hbase會管理一個zookeep集羣,即Hbase默認自帶一個zookeep集羣。這個集羣會隨着Hbase的啓動而啓動。而在實際的商業項目中一般本身管理一個zookeeper集羣更便於優化配置提升集羣工做效率,但須要配置Hbase。須要修改conf/hbase-env.sh裏面的HBASE_MANAGES_ZK 來切換。這個值默認是true的,做用是讓Hbase啓動的時候同時也啓動zookeeper.在本實驗中,咱們採用獨立運行zookeeper集羣的方式,故將其屬性值改成false。


3.2 配置conf/hbase-site.xml

配置效果圖以下:

圖4-3 hbase-site.xml配置文件

解釋:要想運行徹底分佈式模式,加一個屬性 hbase.cluster.distributed 設置爲 true 而後把 hbase.rootdir 設置爲HDFS的NameNode的位置

hbase.rootdir:這個目錄是region server的共享目錄,用來持久化Hbase。URL須要是’徹底正確’的,還要包含文件系統的scheme

hbase.cluster.distributed :Hbase的運行模式。false是單機模式,true是分佈式模式。若爲false,Hbase和Zookeeper會運行在同一個JVM裏面。在hbase-site.xml配置zookeeper:當Hbase管理zookeeper的時候,你能夠經過修改zoo.cfg來配置zookeeper,對於zookeepr的配置,你至少要在 hbase-site.xml中列出zookeepr的ensemble servers,具體的字段是 hbase.zookeeper.quorum.在這裏列出Zookeeper集羣的地址列表,用逗號分割。

hbase.zookeeper.property.clientPort:ZooKeeper的zoo.conf中的配置,客戶端鏈接的端口。

hbase.zookeeper.property.dataDir:ZooKeeper的zoo.conf中的配置。對於獨立的Zookeeper,要指明Zookeeper的host和端口。須要在 hbase-site.xml中設置。


3.3 配置conf/regionservers

寫入:slaver一、slaver2

圖4-4 配置集羣信息

在這裏列出了你但願運行的所有 HRegionServer,一行寫一個host (就像Hadoop裏面的 slaver 同樣). 列在這裏的server會隨着集羣的啓動而啓動,集羣的中止而中止。


3.4 hadoop配置文件拷入

# cp ~/u/etc/hadoop/hdfs-site.xml /soft/hbase/conf # cp ~/u/etc/hadoop/core-site.xml /soft/hbase/conf

3.5 分發hbase

# scp -r /soft/hbase slaver1:/soft/hbase # scp -r /soft/hbase slaver2:/soft/hbase

3.6 運行和測試

在master上執行: 
(1)# start-dfs.sh 
(2)# start-yarn.sh 
(3)# zkServer.sh start(各個節點均執行) 
(4)# start-hbase.sh (直接運行這個命令須要將HBASE的bin目錄也加入到/etc/environment中)

使用jps查看進程:

master進程列表 
這裏寫圖片描述

從節點1進程列表 
這裏寫圖片描述

從節點2進程列表 
這裏寫圖片描述

經過瀏覽器查看60010,60030端口查看 
http://192.168.60.190:60010/

圖4-8 瀏覽器主節點信息

http://192.168.60.199:60030/rs-status

圖4-9 瀏覽器從節點信息

4.7 多節點啓動HMaster

root@master:/soft/hbase/bin# hbase-daemon.sh start master

在其餘子節點同時啓動HMaster,能夠作等待備份做用;


五、HBase簡單操做

Hbase腳本 
啓動:hbase shell

圖5-1 啓動HBase

(1) status命令

hbase(main):008:0> status 1 servers, 0 dead, 3.0000 average load

(2) version命令

hbase(main):007:0> version 0.94.12, r1524863, Fri Sep 20 00:25:45 UTC 2013

(3) create 命令 
建立一個名爲 test 的表,這個表只有一個列爲 cf。其中表名、列都要用單引號括起來,並以逗號隔開。

hbase(main):001:0> create 'test', 'cf' 0 row(s) in 10.3830 seconds

(4) list 命令 
查看當前 HBase 中具備哪些表。

hbase(main):009:0> list TABLE test 1 row(s) in 0.3590 seconds

六、備註

問題解決: 
一、在安裝完HBase後,啓動hbase時會報slf4j-log4j的錯誤,這是由於hadoop的slf4j-log4j與HBase中的slf4j-log4j啓衝突。 
二、在web上查看HBase時,以前用的時:

http://master:60010/ http://slaver1:60030/

可是存在問題,不能打開頁面進行查看HBase裏邊的信息。是由於在HBase1.X以後的版本中端口號改爲了16010和16030

相關文章
相關標籤/搜索