一、shell操作hbase

1、进入bhase shell环境

  • 在集群上输入hbase shell 即可

    2、hbase常用操作

  • 操作分类

    • 系统操作
    • DDL操作(数据定义语言,如建表删除表)
    • DML操作(数据操作语言,如增删改查)
    • Namespace操作
      1. 系统操作语言
  1. status 查看集群状态
  2. version 查看版本信息
  3. whoami 当前操作的用户是哪个
    1. DDL操作(系统定义语言)
  4. create 创建表 create”student”,”base_info”,”private_info”
  5. list 列出所有表 list
  6. disable 禁用表 disable disable”student”
  7. is_disabled 表是否被禁用 is_disabled”student”
  8. drop 删除表(必须先禁用再删除) drop”student”
  9. enable 启用一张表 enable”student”
  10. describe 查看表结构 describe”student”
  11. alter 修改表的结构,如给表student加入一个列族123 alter”student”,”123”
  12. exists 验证表是否存在 exists”student”
  13. disable_all 禁用多个表
  14. drop_all 删除多个表
    1. DML操作(数据操作语言)
  15. 插入数据向当前空间表Student的rowkey为1的行添加数据
    1. put”Student”,”r1”,”base_info:name”,”张三”
  16. hbase读取数据只有三种方式
    1. scan遍历全表 scan”student”
    2. scan范围查询 scan”student”,{LINIT=>2,STARTROW=>”r1”,ENDROW=>”r2”}
    3. get按照rowkey查询 get”student”,”rowid” get”student”,”rowid”,”baseinfo:username”
  17. 删除指定条件的列数据
    1. delete”student”,”r1”,”baseinfo:username”
  18. 清空表
    1. Truncate(只是清空数据) truncate”student”
      1. 不需要手动disable表,系统会自动disable,再执行清空操作。
    2. 查看当前空间下的所有表 list_namespace_tables”namespace”
  19. 查看habse版本号
    1. decsribe”tablename”
      • 修改列族的版本号个数
        • 模板:alter ‘tablename’,NAME=>’columnFamilyName’,VERSIONS=>versionNumbers
        • 样例:alter ‘Student4Job008’,NAME=>’baseInfo’,VERSIONS=>2
        • 再去查看表的描述信息,可以查到变化情况。
      • 查看表数据的指定版本个数(有效的版本号,若已删除版本但处于标记状态,并未真正删除数据的不属于有效的版本数据)
        • 模板1-查看全表:scan ‘tablename’,{VERSIONS => versionNumber}
        • 模板1-查看某列族:scan ‘tablename’,{NAME=>’baseInfo’,VERSIONS => versionNumber}
        • 样例:scan ‘Student4Job008’,{VERSIONS => 4}
        • 注:hbase删除数据时,先加入删除标记,即标记删除法,待到minor 或是 major compaction再进行物理删除。
      • 查看表数据的指定所有版本对应的数据(包括全部版本的实际还存在的数据, 即包括加了删除标记但未正式删除的)
        • 模板1-查看全表:scan ‘tablename’,{RAW=>true,VERSIONS => versionNumber}
        • 模板1-查看某列族:scan ‘tablename’,{RAW=>true,NAME=>’baseInfo’,VERSIONS => versionNumber}
        • 样例:scan ‘Student4Job008’,{RAW=>true,VERSIONS => 4}
  • Namespace操作
    • 创建命名空间
      • create_namespace “testnamespace”
    • 更新命名空间
      • alter_namespace”testnamespcae”{METHOD=>”set”,”property_name”=>”property_value”}
    • 查看命名空间描述信息
      • describe_namespcae”testnamespace”
    • 查看所有命名空间
      • list_namespace
    • 在指定命名空间下创建表
      • create”testnamespace:student”,”base_info”

        二、Java操作HBase

        1、maven项目构建与hbase包引入

  1. <dependency>
  2. <groupId>org.apache.hbase</groupId>
  3. <artifactId>hbase-client</artifactId>
  4. <version>1.1.2</version>
  5. <!-- <scope>provided</scope> -->
  6. </dependency>

2、Java操作代码案例

  1. package com.tl.job007.hbase.test;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.Random;
  5. import org.apache.hadoop.conf.Configuration;
  6. import org.apache.hadoop.hbase.Cell;
  7. import org.apache.hadoop.hbase.CellScanner;
  8. import org.apache.hadoop.hbase.CellUtil;
  9. import org.apache.hadoop.hbase.HBaseConfiguration;
  10. import org.apache.hadoop.hbase.HColumnDescriptor;
  11. import org.apache.hadoop.hbase.HTableDescriptor;
  12. import org.apache.hadoop.hbase.TableName;
  13. import org.apache.hadoop.hbase.client.Admin;
  14. import org.apache.hadoop.hbase.client.Connection;
  15. import org.apache.hadoop.hbase.client.ConnectionFactory;
  16. import org.apache.hadoop.hbase.client.Get;
  17. import org.apache.hadoop.hbase.client.Put;
  18. import org.apache.hadoop.hbase.client.Result;
  19. import org.apache.hadoop.hbase.client.Table;
  20. import org.apache.hadoop.hbase.util.Bytes;
  21. /**
  22. * @author 天亮教育
  23. */
  24. public class HBaseOperator {
  25. // 用于链接hbase的连接器对象,类似于mysql jdbc的Connection
  26. public Connection connection;
  27. // 用hbase configuration初始化配置信息时会自动加载当前应用classpath下的hbase-site.xml
  28. public static Configuration configuration = HBaseConfiguration.create();
  29. // 初始化hbase操作对象
  30. public HBaseOperator() throws Exception {
  31. // ad = new HBaseAdmin(configuration); //过期了,推荐使用Admin
  32. configuration.set("hbase.zookeeper.quorum",
  33. "192.168.1.34,192.168.1.31,192.168.1.32,192.168.1.41");
  34. configuration.set("hbase.zookeeper.property.clientPort", "2181");
  35. configuration.set("zookeeper.znode.parent", "/hbase-unsecure");
  36. // 对connection初始化
  37. connection = ConnectionFactory.createConnection(configuration);
  38. }
  39. // 创建表
  40. public void createTable(String tablename, String... cf1) throws Exception {
  41. // 获取admin对象
  42. Admin admin = connection.getAdmin();
  43. // 创建tablename对象描述表的名称信息
  44. TableName tname = TableName.valueOf(tablename);// mytable
  45. // 创建HTableDescriptor对象,描述表信息
  46. HTableDescriptor tDescriptor = new HTableDescriptor(tname);
  47. // 判断是否表已存在
  48. if (admin.tableExists(tname)) {
  49. System.out.println("表" + tablename + "已存在");
  50. return;
  51. }
  52. // 添加表列簇信息
  53. for (String cf : cf1) {
  54. HColumnDescriptor famliy = new HColumnDescriptor(cf);
  55. tDescriptor.addFamily(famliy);
  56. }
  57. // 调用admin的createtable方法创建表
  58. admin.createTable(tDescriptor);
  59. System.out.println("表" + tablename + "创建成功");
  60. }
  61. // 删除表
  62. public void deleteTable(String tablename) throws Exception {
  63. Admin admin = connection.getAdmin();
  64. TableName tName = TableName.valueOf(tablename);
  65. if (admin.tableExists(tName)) {
  66. admin.disableTable(tName);
  67. admin.deleteTable(tName);
  68. System.out.println("删除表" + tablename + "成功!");
  69. } else {
  70. System.out.println("表" + tablename + "不存在。");
  71. }
  72. }
  73. // 新增数据到表里面Put
  74. public void putData(String table_name) throws Exception {
  75. TableName tableName = TableName.valueOf(table_name);
  76. Table table = connection.getTable(tableName);
  77. Random random = new Random();
  78. List<Put> batPut = new ArrayList<Put>();
  79. for (int i = 0; i < 10; i++) {
  80. // 构建put的参数是rowkey rowkey_i (Bytes工具类,各种java基础数据类型和字节数组之间的相互转换)
  81. Put put = new Put(Bytes.toBytes("rowkey_" + i));
  82. put.addColumn(Bytes.toBytes("user"), Bytes.toBytes("username"),
  83. Bytes.toBytes("user_" + i));
  84. put.addColumn(Bytes.toBytes("user"), Bytes.toBytes("age"),
  85. Bytes.toBytes(random.nextInt(50) + 1));
  86. put.addColumn(Bytes.toBytes("user"), Bytes.toBytes("birthday"),
  87. Bytes.toBytes("20170" + i + "01"));
  88. put.addColumn(Bytes.toBytes("content"), Bytes.toBytes("phone"),
  89. Bytes.toBytes("电话_" + i));
  90. put.addColumn(Bytes.toBytes("content"), Bytes.toBytes("email"),
  91. Bytes.toBytes("email_" + i));
  92. // 单条记录put
  93. // table.put(put);
  94. batPut.add(put);
  95. }
  96. table.put(batPut);
  97. System.out.println("表插入数据成功!");
  98. }
  99. // 查询数据
  100. public void getData(String table_Name) throws Exception {
  101. TableName tableName = TableName.valueOf(table_Name);
  102. Table table = connection.getTable(tableName);
  103. // 构建get对象
  104. List<Get> gets = new ArrayList<Get>();
  105. for (int i = 0; i < 5; i++) {
  106. Get get = new Get(Bytes.toBytes("rowkey_" + i));
  107. gets.add(get);
  108. }
  109. Result[] results = table.get(gets);
  110. for (Result result : results) {
  111. // 使用cell获取result里面的数据
  112. CellScanner cellScanner = result.cellScanner();
  113. while (cellScanner.advance()) {
  114. Cell cell = cellScanner.current();
  115. // 从单元格cell中把数据获取并输出
  116. // 使用 CellUtil工具类,从cell中把数据获取出来
  117. String famliy = Bytes.toString(CellUtil.cloneFamily(cell));
  118. String qualify = Bytes.toString(CellUtil.cloneQualifier(cell));
  119. String rowkey = Bytes.toString(CellUtil.cloneRow(cell));
  120. String value = Bytes.toString(CellUtil.cloneValue(cell));
  121. System.out.println("rowkey:" + rowkey + ",columnfamily:"
  122. + famliy + ",qualify:" + qualify + ",value:" + value);
  123. }
  124. }
  125. }
  126. // 关闭连接
  127. public void cleanUp() throws Exception {
  128. connection.close();
  129. }
  130. // 测试相关功能方法
  131. public static void main(String[] args) throws Exception {
  132. HBaseOperator hbaseOperator = new HBaseOperator();
  133. hbaseOperator.createTable("zel", "user", "content");
  134. hbaseOperator.putData("zel");
  135. hbaseOperator.getData("zel");
  136. hbaseOperator.cleanUp();
  137. }
  138. }

三、HBase经典面试问题剖析

1、hbase架构设计之元数据管理之root表和meta表

  • hbase0.98版本及以前
    • hbase用root表来记录meta的Region信息。类似于meta表记录用户表的Region信息。
    • Root只会有一个Region。
    • client需要先访问root表来获取meta表的Region信息,故需要知道存储root表的Region的RegionServer的地址。
    • 该地址被存在zookeeper中。默认路径/hbase/root-region/server
    • root表结构示意图
    • Hbase之shell和Java操作 - 图1
    • meta表结构示意图
    • Hbase之shell和Java操作 - 图2
    • 工作流程描述
      • Hbase的所有Region元数据都没存储在meta表中,随着Region的增多,Meta数据量也会增大,分裂成多个Region。
      • 为了定位meta表中各个Region的位置,将meta表中所有的Region的元数据保存在Root表中。
      • 最后又zookeeper保存root表的位置信息。
      • 所有客户端访问数据时都需要先访问zookeeper来获取root位置信息,然后通过访问root获得meta表的位置,最后通过meta确定用户存放的数据的位置。
      • 工作流程示意图
      • Hbase之shell和Java操作 - 图3
      • Root永远不会被分割,他只有一个Region。
      • 这样可以保证最多只需要跳跃三次即可定位到任意一个Region。
      • 为了加快访问速度,meta表的所有Region都保存在内存中。
      • 客户端会将查询过的位置信息缓存起来。且缓存不会失效。
      • 如果客户端访问缓存查不到信息,则会访问meta查询信息的位置,如果还是失败,则询问root表相关的meta表在哪里。
      • 最后,如果前面的信息全部失效。则通过zookeeper重新定位region信息。所以如果客户端上的缓存全部失效则需要六次网络来回,才能定位到正确的Region。
  • hbase0.98版本之后元数据管理说明(为优化执行效率而升级)

    • 即为现在的管理方式,去除root表,只保留meta表。
    • meta表有且只有一个Region来存储。其不可再切分成多个Region。
    • 通过调大meta表的大小即可解决海量region数据的问题。

      2、habes之rowkey设计技巧

  • 背景说明

    • hbase是三位有序数据表,通过rowkey、列族、timestamp版本号这三个维度进行快速定位数据。
    • rowkey唯一标识一行记录,有以下几种方式进行hbase数据查询
      • 通过get方式,指定rowkey获取唯一一行数据。
      • 通过scan,设置startrow和stopstart进行条件筛选。
      • 浅表扫面scan,即扫描整张表中的所有行记录。
  • RowKey设计必要性-避免热点问题
    • 热点发生在大量的client同时访问少数的那几个节点。(可能是读、写、或者其他操作)
    • 大量的访问可能会导致region所在的机器超出承受范围而导致不可用,甚至导致同一个regionserver上的其他region不可用。
    • 多数是由于rowkey设计不合理导致的。
  • 设计技巧说明
    • 长度越短越好,不要太长
      • rowkey是二进制码流存储理论上可以是任意字符串,一般最大64kb,实际一般为10-100bytes。设计成定长。
      • HFile中是根据KV存储的,rowkey为K,其他值为V,rowkey过长就会导致存储效率低。
      • memstore会缓存部分数据到内存,如果rowkey过长则占用空间,降低系统性能。
    • rowkey散列原因
      • 原因说明
        • rowkey是hbase数据的排序和划分region的主要依据,如果过于稠密则导致大量数据都被分配在同一个region中,使得对应regionserver负载过高。
        • 很多人用时间戳作为rowkey,这样会导致大量数据集中在一个region中,造成负载过高,降低效率。
      • 散列做法
        • 至少不能将时间放在二进制码的前面。
        • 第一种选择是可以将时间戳倒序后作为rowkey。比较简单,但是失去了rowkey可读性、有序性的意义。
        • 第二种是将rowkey的高位作为散列字段。由程序随机生成,地位放时间段
      • rowkey唯一原则
        • rowkey是一行数据的唯一标识,必须保持唯一性。rowkey是按照字典顺序排序存储的。
        • 设计时要充分利用排序这个特点,将经常读取的数据存储在一起,即将会被访问的数据存储在一起。
        • 这样更方便的利用数据块集中加载,数据缓存加速等提升查询效率。