第一章.HBase进阶

1.RegionServer架构

$02[HBase进阶] - 图1

  1. # storeFile
  2. - 保存实际数据的物理文件,StoreFile以Hfile的形式存储在HDFS 上,每个Store会有一个或多个StoreFile(Hfile),数据在每个StoreFile都是有序的
  3. # MemStore
  4. - 写缓存,由于Hfile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile
  5. # WAL
  6. - 由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中,所以在系统出现故障的时候,数据可以通过这个日志文件重建
  7. # BlockCache
  8. - 读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询

2.写流程

$02[HBase进阶] - 图2

写流程:

  1. Client先访问zookeeper,获取hbase:meta表位于哪个Region Server
  2. 访问对应的Region Server,获取hbase:meta表根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中,并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问
  3. 与目标Region Server进行通讯
  4. 将数据顺序写入(追加)到WAL
  5. 将数据写入对应的MemStore,数据会在MemStore进行排序
  6. 向客户端发送ack
  7. 等到达MemStore的刷写时机后,将数据刷写到Hfile

3.MemStore Flush(刷写时机)

$02[HBase进阶] - 图3

  1. 1. 当某个MemStore的大小达到了hbase.hregion.memstore.flush.size(默认值128M),其所在region的所有memstore都会刷写:
  2. 当memstore的大小达到了 hbase.hregion.memstore.flush.size(默认值128M) *hbase.hregion.memstore.block.multiplier(默认值4)时,会阻止继续往memstore写数据
  3. 2. 当region server中的memstore的总大小达到
  4. java_heapsize
  5. *hbase.regionserver.global.memstore.size(默认值0.4)
  6. *hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)
  7. region会按照其所有的memstore大小顺序(由大到小)依次进行刷写,直到region server中所有的memstore的总大小减小到上述值以下
  8. 当region server中memstore的总大小达到
  9. java_heapsize
  10. *hbase.regionserver.global.memstore.size(默认值0.4)
  11. 时,会阻止继续往所有的memstore写数据
  12. 3. 到达自动刷写的时间,也会触发memstore flush,自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时)
  13. 4. 当WAL文件的数量超过hbase.regionserver.max.logs,region会按照时间顺序依次进行刷写,直到WAL文件数量减少到hbase.regionserver.max.logs以下(该属性名已经废弃,现无需手动设置,最大值为32)

4.读流程

  1. 整体流程

$02[HBase进阶] - 图4

  1. Merge细节

$02[HBase进阶] - 图5

读流程:

  1. Client先访问zookeeper,获取hbase:meta表位于哪个Region Server
  2. 访问对应的Region Server, 获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region中,并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问
  3. 与目标Region Server进行通讯
  4. 分别在MemStore和Store File(Hfile)中查询目标数据,并将查到的所有数据进行合并,此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)
  5. 将查询到的新的数据块(Block,Hfile数据存储单元,默认大小为64kb)缓存到Block Cache
  6. 将合并后的最终结果返回给客户端

5.StoreFile Compaction

$02[HBase进阶] - 图6

  1. - 由于memstore每次刷写都会生成一个新的Hfile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的hfile中,因此查询时需要遍历所有的Hfile,为了减少Hfile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction
  2. - Compaction分为两种,分别是Minor Compaction 和 Major Compaction,Minor Compaction会将临近的若干个较小的Hfile合并成一个较大的Hfile,并清理掉部分过期和删除的数据,Major Compaction 会将一个Store下的所有Hfile合并成一个大的Hfile,并且会清理掉所有过期和删除的数据

6.Region Split

  1. - 默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分,刚拆分时,两个子Region都位于当前的Region Server,但随着负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server
  2. - Region Split时机:
  3. 1. 当一个Region中的某个Store下所有的StoreFile的总大小超过hbase.hregion.max.filesize,该Region就会进行拆分(0.94版本之前)
  4. 2. 当一个region中的某个Store下所有StoreFile的总大小超过Min(initialSize*R^3,hbase.hregion.max.filesize),该region就会进行拆分,其中initialSize的默认值为2*
  5. hbase.hregion.memstore.flush.size,R为当前Region Server中属于该Table的Region个数(0.94版本之后)
  6. 具体的切分策略为:
  7. 第一次 split :1^3*256 =256MB
  8. 第二次split:2^3 * 256 = 2048MB
  9. 第三次split:3^3 * 256 = 6912MB
  10. 第四次split:4^3 * 256 = 16384MB > 10GB,因此取较小的值10GB
  11. 后面每次split的size都是10GB了
  12. 3. HBase2.0引入了新的split策略,如果当前RegionServer上该表只有一个Region,按照2* hbase.hregion.memstore.flush.size分裂,否则按照hbase.hregion.max.filesize分裂

$02[HBase进阶] - 图7

第二章.HBase API

1.环境准备

新建一个maven项目,并添加依赖

  1. <dependency>
  2. <groupId>org.apache.hbase</groupId>
  3. <artifactId>hbase-server</artifactId>
  4. <version>2.0.5</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.hbase</groupId>
  8. <artifactId>hbase-client</artifactId>
  9. <version>2.0.5</version>
  10. </dependency>

2.DDL

  1. package com.atguigu.hbaseapi;
  2. import org.apache.hadoop.conf.Configuration;
  3. import org.apache.hadoop.hbase.HBaseConfiguration;
  4. import org.apache.hadoop.hbase.NamespaceDescriptor;
  5. import org.apache.hadoop.hbase.TableName;
  6. import org.apache.hadoop.hbase.client.*;
  7. import org.apache.hadoop.hbase.util.Bytes;
  8. import java.io.IOException;
  9. import java.util.List;
  10. public class HBaseDDL {
  11. public static Connection connection;
  12. static{
  13. try{
  14. //创建配置对象
  15. Configuration configuration = HBaseConfiguration.create();
  16. configuration.set("hbase.zookeeper.quorum","hadoop102:2181");
  17. //获取连接对象
  18. connection = ConnectionFactory.createConnection(configuration);
  19. }catch(IOException e){
  20. e.printStackTrace();
  21. }
  22. }
  23. /**
  24. * @Description: 创建命名空间
  25. * @Param: [nameSpace]
  26. * @return: void
  27. * @Author: jcsune
  28. * @Date: 2021/8/2
  29. */
  30. public static void createNameSpace(String nameSpace) throws IOException{
  31. //获取admin
  32. Admin admin = connection.getAdmin();
  33. //构建NameSpaceDescriptor
  34. NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(nameSpace);
  35. NamespaceDescriptor namespaceDescriptor = builder.build();
  36. //利用admin创建命名空间
  37. admin.createNamespace(namespaceDescriptor);
  38. System.out.println("创建命名空间" + nameSpace + "成功");
  39. //关闭资源
  40. admin.close();
  41. }
  42. /**
  43. * @Description: 列出当前所有的命名空间
  44. * @Param: []
  45. * @return: void
  46. * @Author: jcsune
  47. * @Date: 2021/8/2
  48. */
  49. public static void listNameSpace()throws IOException{
  50. //获取admin
  51. Admin admin = connection.getAdmin();
  52. //获取所有命名空间的信息
  53. NamespaceDescriptor[] namespaceDescriptors = admin.listNamespaceDescriptors();
  54. System.out.println("所有的命名空间:");
  55. for (NamespaceDescriptor namespaceDescriptor : namespaceDescriptors) {
  56. System.out.println(namespaceDescriptor.getName());
  57. }
  58. //关闭命名空间
  59. admin.close();
  60. }
  61. /**
  62. * @Description: 删除命名空间
  63. * @Param: [nameSpace]
  64. * @return: void
  65. * @Author: jcsune
  66. * @Date: 2021/8/2
  67. */
  68. public static void dropNameSpace(String nameSpace) throws IOException{
  69. //获取admin
  70. Admin admin = connection.getAdmin();
  71. //删除命名空间
  72. admin.deleteNamespace(nameSpace);
  73. System.out.println("删除命名空间" + nameSpace + "成功");
  74. //关闭资源
  75. admin.close();
  76. }
  77. /**
  78. * @Description: 创建表
  79. * @Param: [tableName, cf]
  80. * @return: void
  81. * @Author: jcsune
  82. * @Date: 2021/8/2
  83. */
  84. public static void createTable(String tableName,String cf) throws IOException{
  85. //获取admin
  86. Admin admin = connection.getAdmin();
  87. //判断
  88. if(existsTable(tableName)){
  89. System.out.println(tableName+ "表已将存在了");
  90. return;
  91. }
  92. //构建表描述者构造器
  93. TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName));
  94. //创建一个列族描述者构造器
  95. ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf));
  96. ColumnFamilyDescriptor columnFamilyDescriptor = columnFamilyDescriptorBuilder.build();
  97. //向表描述者构造器设置列族信息
  98. tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
  99. TableDescriptor tableDescriptor = tableDescriptorBuilder.build();
  100. //创建表
  101. admin.createTable(tableDescriptor);
  102. System.out.println(tableName+"表创建成功");
  103. //关闭资源
  104. admin.close();
  105. }
  106. /**
  107. * @Description: 删除表
  108. * @Param: [tableName]
  109. * @return: void
  110. * @Author: jcsune
  111. * @Date: 2021/8/2
  112. */
  113. public static void dropTable(String tableName) throws IOException{
  114. //获取admin
  115. Admin admin = connection.getAdmin();
  116. //判断
  117. if (!existsTable(tableName)){
  118. System.out.println(tableName + "表不存在");
  119. admin.close();
  120. return;
  121. }
  122. //删除表
  123. admin.deleteTable(TableName.valueOf(tableName));
  124. System.out.println(tableName+"表删除成功");
  125. //关闭资源
  126. admin.close();
  127. }
  128. /**
  129. * @Description: 列出所有表
  130. * @Param: []
  131. * @return: void
  132. * @Author: jcsune
  133. * @Date: 2021/8/2
  134. */
  135. public static void listTable() throws IOException{
  136. //获取admin
  137. Admin admin = connection.getAdmin();
  138. //列出表
  139. List<TableDescriptor> tableDescriptors = admin.listTableDescriptors();
  140. //遍历
  141. System.out.println("所有的表:");
  142. for (TableDescriptor tableDescriptor : tableDescriptors) {
  143. System.out.println(tableDescriptor.getTableName());
  144. }
  145. //关闭资源
  146. admin.close();
  147. }
  148. /**
  149. * @Description: 判断表是否存在
  150. * @Param: [tableName]
  151. * @return: boolean
  152. * @Author: jcsune
  153. * @Date: 2021/8/2
  154. */
  155. private static boolean existsTable(String tableName) throws IOException{
  156. //获取admin
  157. Admin admin = connection.getAdmin();
  158. //判断
  159. boolean exists = admin.tableExists(TableName.valueOf(tableName));
  160. return exists;
  161. }
  162. public static void main(String[] args) {
  163. try {
  164. //HBaseDDL.createNameSpace("xiyouji");
  165. //listNameSpace();
  166. //dropNameSpace("xiyouji");
  167. listTable();
  168. createTable("xiyouji","info");
  169. dropTable("xiyouji");
  170. } catch (IOException e) {
  171. e.printStackTrace();
  172. }
  173. }
  174. }

3.DML

  1. package com.atguigu.hbase.API.demo.DML;
  2. import com.atguigu.hbase.API.demo.DDL.HBaseDDL;
  3. import org.apache.hadoop.conf.Configuration;
  4. import org.apache.hadoop.hbase.Cell;
  5. import org.apache.hadoop.hbase.CellUtil;
  6. import org.apache.hadoop.hbase.HBaseConfiguration;
  7. import org.apache.hadoop.hbase.TableName;
  8. import org.apache.hadoop.hbase.client.*;
  9. import org.apache.hadoop.hbase.util.Bytes;
  10. import java.awt.event.WindowFocusListener;
  11. import java.io.IOException;
  12. /**
  13. * @author leon
  14. * @ClassName HBaseDML.java
  15. * @createTime 2021年07月31日 11:11:00
  16. */
  17. public class HBaseDML {
  18. public static Connection connection;
  19. static {
  20. try {
  21. // 1.创建配置对象
  22. Configuration configuration = HBaseConfiguration.create();
  23. configuration.set("hbase.zookeeper.quorum", "hadoop102:2181");
  24. // 2. 获取连接对象
  25. connection = ConnectionFactory.createConnection(configuration);
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. /**
  31. * 插入/修改
  32. * @param tableName
  33. * @param rowkey
  34. * @param cf
  35. * @param cn
  36. * @param value
  37. */
  38. public static void putData(String tableName, String rowkey, String cf, String cn, String value) throws IOException {
  39. // 1. 获取Table对象
  40. Table table = connection.getTable(TableName.valueOf(tableName));
  41. // 2. 构建put对象
  42. Put put = new Put(Bytes.toBytes(rowkey));
  43. // 3. 设置列族、列名属性
  44. put.addColumn(Bytes.toBytes(cf),Bytes.toBytes(cn),Bytes.toBytes(value));
  45. // 4. 执行插入
  46. table.put(put);
  47. System.out.println("数据插入成功");
  48. // 5. 关闭资源
  49. table.close();
  50. }
  51. /**
  52. * get
  53. * @param tableName
  54. * @param rowkey
  55. * @param cf
  56. * @param cn
  57. */
  58. public static void getData(String tableName, String rowkey, String cf, String cn) throws IOException {
  59. // 1. 获取Table对象
  60. Table table = connection.getTable(TableName.valueOf(tableName));
  61. // 2. 构建get对象
  62. Get get = new Get(Bytes.toBytes(rowkey));
  63. // 3. 设置列族、列名属性
  64. if(cf == null && cn != null){
  65. System.out.println("输入有误");
  66. table.close();
  67. return;
  68. }
  69. if(cf != null && cn == null){
  70. // 查询某列族
  71. get.addFamily(Bytes.toBytes(cf));
  72. }
  73. if(cf != null && cn != null){
  74. // 查询cell
  75. get.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cn));
  76. }
  77. // 4. 执行插入
  78. Result result = table.get(get);
  79. // 5. 解析result
  80. Cell[] cells = result.rawCells();
  81. // 6. 遍历
  82. System.out.println("数据是:");
  83. for (Cell cell : cells) {
  84. String rk = Bytes.toString(cell.getRowArray());
  85. String rowkey1 = Bytes.toString(CellUtil.cloneRow(cell));
  86. String cFamily = Bytes.toString(CellUtil.cloneFamily(cell));
  87. String cname = Bytes.toString(CellUtil.cloneQualifier(cell));
  88. String value = Bytes.toString(CellUtil.cloneValue(cell));
  89. System.out.println("rk:"+rk);
  90. System.out.println("rowkey1:"+rowkey1+", cf:cn"+cFamily+":"+cname+", value:"+value);
  91. }
  92. // 5. 关闭资源
  93. table.close();
  94. }
  95. /**
  96. * 扫描表
  97. * @param tableName
  98. */
  99. public static void scanData(String tableName) throws IOException {
  100. // 1. 获取Table对象
  101. Table table = connection.getTable(TableName.valueOf(tableName));
  102. // 2. 创建scan
  103. Scan scan = new Scan();
  104. // 3. 扫描
  105. ResultScanner scanner = table.getScanner(scan);
  106. System.out.println("数据是:");
  107. for (Result result : scanner) {
  108. Cell[] cells = result.rawCells();
  109. for (Cell cell : cells) {
  110. String rowkey1 = Bytes.toString(CellUtil.cloneRow(cell));
  111. String cFamily = Bytes.toString(CellUtil.cloneFamily(cell));
  112. String cname = Bytes.toString(CellUtil.cloneQualifier(cell));
  113. String value = Bytes.toString(CellUtil.cloneValue(cell));
  114. System.out.println("rowkey1:"+rowkey1+", cf:cn"+cFamily+":"+cname+", value:"+value);
  115. }
  116. }
  117. // 5. 关闭资源
  118. table.close();
  119. }
  120. /**
  121. * 删除数据
  122. */
  123. public static void dropData(String tableName, String rowKey) throws IOException {
  124. // 1. 获取table
  125. Table table = connection.getTable(TableName.valueOf(tableName));
  126. // 2. 创建delete对象
  127. Delete delete = new Delete(Bytes.toBytes(rowKey));
  128. // 3. 删除一行数据
  129. table.delete(delete);
  130. System.out.println("删除数据成功");
  131. // 4. 关闭资源
  132. table.close();
  133. }
  134. public static void main(String[] args) {
  135. try {
  136. // HBaseDDL.createTable("xixi", "info");
  137. // putData("xixi", "1001", "info","name", "张三");
  138. // putData("xixi", "1001", "info","age", "23");
  139. // putData("xixi", "1001", "info","sex", "难");
  140. // System.out.println("=====查询一行======");
  141. // getData("xixi", "1001", null, null);
  142. // System.out.println("=====查询一列族======");
  143. // getData("xixi", "1001", "info", null);
  144. // System.out.println("=====查cell======");
  145. // getData("xixi", "1001", "info", "sex");
  146. // scanData("xixi");
  147. // dropData("xixi", "1001");
  148. // scanData("xixi");
  149. getData("STUDENT", "1001", null, "name");
  150. } catch (IOException e) {
  151. e.printStackTrace();
  152. }
  153. }
  154. }

第三章.HBase优化

1.预分区

每一个region维护着startRow与endRow,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护,那么依照这个原则,我们可以将数据将要投放的分区提前大致的规划好,以提高HBase性能

  1. 手动设定预分区
  1. create 'xixi','info',SPILTS =>['1000','2000','3000','4000']
  1. 生成16进制序列预分区
  1. create 'xixi','info',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
  1. 按照文件设置的规则预分区

创建splits.txt,文件内容如下

  1. aaaa
  2. bbbb
  3. cccc
  4. dddd

然后执行

  1. create 'xixi','info',SPLITS FILE => 'splits.txt'
  1. 使用JavaAPI创建预分区
  1. //自定义算法,产生一系列Hash散列值存储在二维数组中
  2. byte[][] splitKeys = 某个散列值函数
  3. //创建HbaseAdmin实例
  4. HBaseAdmin hAdmin = new HBaseAdmin(HbaseConfiguration.create());
  5. //创建HTableDescriptor实例
  6. HTableDescriptor tableDesc = new HTableDescriptor(tableName);
  7. //通过HTableDescriptor实例和散列值二维数组创建带有预分区的Hbase表
  8. hAdmin.createTable(tableDesc, splitKeys);

2.RowKey设计

一条数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪一个预分区的区间内,设计rowkey的主要目的,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜

3.内存优化

HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,一般16G~36G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死

4.基础优化

  1. zookeeper会话超时时间(hbase-site.xml)
  1. # 属性:zookeeper.session.timeout
  2. # 解释:默认值为90000毫秒(90s)。当某个RegionServer挂掉,90s之后Master才能察觉到。可适当减小此值,以加快Master响应,可调整至60000毫秒。
  1. 设置RPC监听数量(hbase-site.xml)
  1. # 属性:hbase.regionserver.handler.count
  2. # 解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值
  1. 手动控制Major Compaction(hbase-site.xml)
  1. # 属性:hbase.hregion.majorcompaction
  2. # 解释:默认值:604800000秒(7天), Major Compaction的周期,若关闭自动Major Compaction,可将其设为0
  1. 优化HStore文件大小(hbase-site.xml)
  1. # 属性:hbase.hregion.max.filesize
  2. # 解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。
  1. 优化Hbase客户端缓存(hbase-site.xml)
  1. # 属性:hbase.client.write.buffer
  2. # 解释:默认值2097152bytes(2M)用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。
  1. 指定scan.next扫描Hbase所获取的行数(hbase-site.xml)
  1. # 属性:hbase.client.scanner.caching
  2. # 解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。
  1. BlockCache占用RegionServer堆内存的比例(hbase-site.xml)
  1. # 属性:hfile.block.cache.size
  2. # 解释:默认0.4,读请求比较多的情况下,可适当调大
  1. MemStore占用RegionServer堆内存的比例
  1. # 属性:hbase.regionserver.global.memstore.size
  2. # 解释:默认0.4,写请求较多的情况下,可适当调大