第 1 章 HBase 简介

1.1HBase定义

Apache HBase 是以hdfs为数据存储的,一种分布式,可扩展性的NoSQL数据库。非关系型数据库

1.2HBase数据模型

HBase的设计理念依据Coogle 的BigTable论文,论文中对数据模型的首句介绍。

Bigtable 是一个稀疏的,分布式的,持久的多维排序map

之后对于映射的解释如下:

该映射由行键,列键和时间戳索引;映射中的每一个值都是一个未解释的字节数组

最终HBase关于数据模型和BigTable的对应关系如下:

HBase 使用于 BigTable非常相似的数据模型,用户将数据行存储在带标签的表中,数据行具有可排序的键和任意数量的列。该表存储稀疏,因此如果用户喜欢,同一表中的行可以具有疯狂变化的列

最终理解 HBase 数据模型的关键在于稀疏、分布式、多维、排序的映射。其中映射 map 指代非关系型数据库的 key-Value 结构。

1.2.1 HBase 逻辑结构

HBase 可以用于存储多种结构的数据,以 JSON 为例,存储的数据原貌为:

{
	"row_key1":{
		"personal_info":{
			"name":"zhangsan",
			"city":"北京",
			"phone":"131********"
		},
		"office_info":{
			"tel":"010-1111111",
			"address":"atguigu"
		}
	},
    
	"row_key11":{
		"personal_info":{
			"city":"上海",
			"phone":"132********"
		},
		"office_info":{
			"tel":"010-1111111"
		}
	},
    
	"row_key2":{
	......
}

逻辑结构

存储数据稀疏,数据存储多维,不同的行具有不同的列。

数据存储整体有序,按照RowKey的字典序排列,RowKey为Byte数组

hbase phoneix 特殊字符 hbase 2.x 特性_大数据

1.2.2HBase物理存储结构

物理存储结构即为数据映射关系,而在概念视图的空单元格,底层实际根本不储存。

HBase物理存储结构

hbase phoneix 特殊字符 hbase 2.x 特性_数据库_02

1.2.3 数据模型

1)Name Space

命名空间,类似于关系型数据库的 database 概念,每个命名空间下有多个表。HBase 两 个自带的命名空间,分别是 hbase 和default,hbase 中存放的是 HBase 内置的表,default表是用户默认使用的命名空间。

2)Table

类似于关系型数据库的表概念。不同的是,HBase 定义表时只需要声明列族即可,不需要声明具体的列。因为数据存储时稀疏的,所有往 HBase 写入数据时,字段可以动态按需指定。因此,和关系型数据库相比,HBase 能够轻松应对字段变更的场景。

3)Row

HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成,数据是按照 RowKey 的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。

4)Column

HBase 中的每个列都由 Column Family(列族)和 Column Qualifier(列限定符)进行限 定,例如 info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。

5)Time Stamp

用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段, 其值为写入 HBase 的时间。

6)Cell

由{rowkey, column Family:column Qualifier, timestamp} 唯一确定的单元。cell 中的数 据全部是字节码形式存贮

1.3HBase 基本结构

hbase phoneix 特殊字符 hbase 2.x 特性_hadoop_03

架构角色:

1)Master

实现类为 HMaster,负责监控集群中所有的 RegionServer 实例。主要作用如下:

1)管理元数据表格 hbase:meta,接收用户对表格创建修改删除的命令并执行

2)监控 region 是否需要进行负载均衡,故障转移和 region 的拆分。

通过启动多个后台线程监控实现上述功能:

  1. LoadBalancer 负载均衡器

周期性监控 region 分布在 regionServer 上面是否均衡,由参数 hbase.balancer.period 控 制周期时间,默认 5 分钟

  1. CatalogJanitor 元数据管理器

定期检查和清理 hbase:meta 中的数据。meta 表内容在进阶介绍。

  1. MasterProcWAL master 预写日志处理器

把 master 需要执行的任务记录到预写日志 WAL 中,如果 master 宕机,让 backupMaster 读取日志继续干。

2)Region Server

Region Server 实现类为 HRegionServer,主要作用如下:

(1)负责数据 cell 的处理,例如写入数据 put,查询数据 get 等

(2)拆分合并 region 的实际执行者,有 master 监控,有 regionServer 执行。

3)Zookeeper

HBase 通过 Zookeeper 来做 master 的高可用、记录 RegionServer 的部署信息、并且存储 有 meta 表的位置信息。

HBase 对于数据的读写操作时直接访问 Zookeeper 的,在 2.3 版本推出 Master Registry 模式,客户端可以直接访问 master。使用此功能,会加大对 master 的压力,减轻对 Zookeeper 的压力。

4)HDFS

HDFS 为 Hbase 提供最终的底层数据存储服务,同时为 HBase 提供高容错的支持。

第 2 章 HBase 快速入门

2.1 HBase 安装部署

2.1.1 ZooLeeper 正常部署

首先保证了ZooKeeper集群的正常部署,并启动。。。

[ma@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh start
[ma@hadoop103 zookeeper-3.5.7]$ bin/zkServer.sh start
[ma@hadoop104 zookeeper-3.5.7]$ bin/zkServer.sh start

2.1.2 Hadoop 正常部署

Hadoop 集群的正常部署并启动。

[ma@hadoop102 hadoop-3.1.3]$ sbin/start-dfs.sh [ma@hadoop103 hadoop-3.1.3]$ sbin/start-yarn.sh

2.1.3 HBase 的解压

HBase -2.4.11🎅

1)解压 Hbase 到指定目录

[ma@hadoop102 software]$ tar -zxvf hbase-2.4.11-bin.tar.gz -C  /opt/module/ 

[ma@hadoop102 software]$ mv /opt/module/hbase-2.4.11 /opt/module/hbase

2)配置环境变量

[ma@hadoop102 ~]$ sudo vim /etc/profile

😆添加:

#HBASE_HOME export HBASE_HOME=/opt/module/hbase export PATH=$PATH:$HBASE_HOME/bin

3)使用 source 让配置的环境变量生效

[ma@hadoop102 module]$ source /etc/profile.d/my_env.sh

2.1.4HBase 的配置文件

1)hbase-env.sh 修改内容,可以添加到最后:

false 取消 不需要自己管理实例 用zookeeper

export HBASE_MANAGES_ZK=false

2)hbase-site.xml 修改内容:

添加java路径
# The java implementation to use.  Java 1.8+ required.
export JAVA_HOME=/opt/module/jdk1.8.0_144
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
 <property>
 <name>hbase.zookeeper.quorum</name>
 <value>hadoop102,hadoop103,hadoop104</value>
 <description>The directory shared by RegionServers.
 </description>
 </property>
<!-- <property>-->
<!-- <name>hbase.zookeeper.property.dataDir</name>-->
<!-- <value>/export/zookeeper</value>-->
<!-- <description> 记得修改 ZK 的配置文件 -->
<!-- ZK 的信息不能保存到临时文件夹-->
<!-- </description>-->
<!-- </property>-->
 <property>
 <name>hbase.rootdir</name>
 <value>hdfs://hadoop102:8020/hbase</value>
     <!--8020这个端口号,要跟hadoop的NameNode一样-->
 <description>The directory shared by RegionServers.
 </description>
 </property>
 <property>
 <name>hbase.cluster.distributed</name>
 <value>true</value>
 </property>
</configuration>

3)regionservers

hadoop102 
hadoop103 
hadoop104

4)解决 HBase 和 Hadoop 的 log4j 兼容性问题,修改 HBase 的 jar 包,使用 Hadoop 的 jar 包

[ma@hadoop102 hbase]$ mv /opt/module/hbase/lib/client-facingthirdparty/slf4j-reload4j-1.7.33.jar /opt/module/hbase/lib/clientfacing-thirdparty/slf4j-reload4j-1.7.33.jar.bak

2.1.5 HBase 远程发送到其他集群

[ma@hadoop102 module]$ xsync hbase/

2.1.6 HBase 服务的启动

1)单点启动

[ma@hadoop102 hbase]$ bin/hbase-daemon.sh start master 

[ma@hadoop102 hbase]$ bin/hbase-daemon.sh start regionserver

2)群启

[ma@hadoop102 hbase]$ bin/start-hbase.sh

3)对应的停止服务

[ma@hadoop102 hbase]$ bin/stop-hbase.sh

4)查看进程

进程结构

hadoop102

hadoop103

hadoop104

HMaster

配置高可用

HRegionServer

HRegionServer

HRegionServer

2.1.7 查看 HBase 页面

启动成功后,可以通过“host:port”的方式来访问 HBase 管理页面,

例如: http://hadoop102:16010

2.1.8 高可用(可选)

在 HBase 中 HMaster 负责监控 HRegionServer 的生命周期,均衡 RegionServer 的负载, 如果 HMaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不 会维持太久。所以 HBase 支持对 HMaster 的高可用配置。

1)关闭 HBase 集群(如果没有开启则跳过此步)

[ma@hadoop102 hbase]$ bin/stop-hbase.sh

2)在 conf 目录下创建 backup-masters 文件

[ma@hadoop102 hbase]$ touch conf/backup-masters

3)在 backup-masters 文件中配置高可用 HMaster 节点

[ma@hadoop102 hbase]$ echo hadoop103 > conf/backup-masters

4)将整个 conf 目录 scp 到其他节点

[ma@hadoop102 hbase]$ xsync conf

5)重启 hbase,打开页面测试查看

http://hadooo102:16010

2.2 HBase Shell 操作

2.2.1 基本操作

1)进入 HBase 客户端命令行

bin/hbase shell

2)查看帮助命令

能够展示 HBase 中所有能使用的命令,主要使用的命令有 namespace 命令空间相关, DDL 创建修改表格,DML 写入读取数据。

hbase:001:0> help

2.2.2namespace

1)创建命名空间

使用特定的 help 语法能够查看命令如何使用。

hbase:002:0> help 'create_namespace'

2)创建命名空间 bigdata

hbase:003:0> create_namespace 'bigdata'

3)查看所有的命名空间

hbase:004:0> list_namespace

2.2.3 DDL

1)创建表

在 bigdata 命名空间中创建表格 student,两个列族。info 列族数据维护的版本数为 5 个, 如果不写默认版本数为 1。

hbase:005:0> create 'bigdata:student', {NAME => 'info', VERSIONS =>  5}, {NAME => 'msg'}

如果创建表格只有一个列族,没有列族属性,可以简写。

如果不写命名空间,使用默认的命名空间 default。

hbase:009:0> create 'student1','info'

2)查看表

查看表有两个命令:list 和 describe

list:查看所有的表名

hbase:013:0> list

describe:查看一个表的详情

hbase:014:0> describe 'student1'

3)修改表

表名创建时写的所有和列族相关的信息,都可以后续通过 alter 修改,包括增加删除列族。

(1)增加列族和修改信息都使用覆盖的方法

hbase:015:0> alter 'student1', {NAME => 'f1', VERSIONS => 3}

(2)删除信息使用特殊的语法

hbase:015:0> alter 'student1', NAME => 'f1', METHOD => 'delete' hbase:016:0> alter 'student1', 'delete' => 'f1'

4)删除表

shell 中删除表格,需要先将表格状态设置为不可用。

hbase:017:0> disable 'student1' 
hbase:018:0> drop 'student1'

2.2.4 DML

1)写入数据

在 HBase 中如果想要写入数据,只能添加结构中最底层的 cell。可以手动写入时间戳指 定 cell 的版本,推荐不写默认使用当前的系统时间。

hbase:019:0> put 'bigdata:student','1001','info:name','zhangsan' 
hbase:020:0> put 'bigdata:student','1001','info:name','lisi' 
hbase:021:0> put 'bigdata:student','1001','info:age','18'

如果重复写入相同 rowKey,相同列的数据,会写入多个版本进行覆盖。

2)读取数据

读取数据的方法有两个:get 和 scan。

get最大范围是一行数据,也可以进行列的过滤,读取数据的结果为多行 cell。

hbase:022:0> get 'bigdata:student','1001' 
hbase:023:0> get 'bigdata:student','1001' , {COLUMN => 'info:name'}

也可以修改读取 cell 的版本数,默认读取一个。最多能够读取当前列族设置的维护版本数。

hbase:024:0>get 'bigdata:student','1001' , {COLUMN => 'info:name',  VERSIONS => 6}

scan 是扫描数据,能够读取多行数据,不建议扫描过多的数据,推荐使用 startRow 和 stopRow 来控制读取的数据,默认范围左闭右开。

hbase:025:0> scan 'bigdata:student',{STARTROW => '1001',STOPROW =>  '1002'}

实际开发中使用 shell 的机会不多,所有丰富的使用方法到 API 中介绍。 🥘

3)删除数据

删除数据的方法有两个:delete 和 deleteall。

delete 表示删除一个版本的数据,即为 1 个 cell,不填写版本默认删除最新的一个版本。

hbase:026:0> delete 'bigdata:student','1001','info:name' deleteall

表示删除所有版本的数据,即为当前行当前列的多个 cell。(执行命令会标记 数据为要删除,不会直接将数据彻底删除,删除数据只在特定时期清理磁盘时进行)

hbase:027:0> deleteall 'bigdata:student','1001','info:name'

第 3 章 HBase API

3.1 环境准备

新建项目后在 pom.xml 中添加依赖:

注意:会报错 javax.el 包不存在,是一个测试用的依赖,不影响使用

<dependencies>
 	<dependency>
 		<groupId>org.apache.hbase</groupId>
 		<artifactId>hbase-server</artifactId>
		<version>2.4.11</version>
 		<exclusions>
 		<exclusion>
 		<groupId>org.glassfish</groupId>
 		<artifactId>javax.el</artifactId>
 		</exclusion>
 		</exclusions>
 	</dependency>
 	<dependency>
 		<groupId>org.glassfish</groupId>
 		<artifactId>javax.el</artifactId>
 		<version>3.0.1-b06</version>
 	</dependency>
</dependencies>

3.2 创建连接

根据官方 API 介绍,HBase 的客户端连接由 ConnectionFactory 类来创建,用户使用完成 之后需要手动关闭连接。同时连接是一个重量级的,推荐一个进程使用一个连接,对 HBase 的命令通过连接中的两个属性 Admin 和 Table 来实现。

3.2.1 单线程创建连接

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.AsyncConnection;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;

public class HBaseConnect {
	public static void main(String[] args) throws IOException {
 		// 1. 创建配置对象
 		Configuration conf = new Configuration();
 		// 2. 添加配置参数
		conf.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");
 		// 3. 创建 hbase 的连接
 		// 默认使用同步连接
	 	Connection connection = ConnectionFactory.createConnection(conf);
 		// 可以使用异步连接
 		// 主要影响后续的 DML 操作
 		CompletableFuture<AsyncConnection> asyncConnection = 
		ConnectionFactory.createAsyncConnection(conf);
 		// 4. 使用连接
 		System.out.println(connection);
 		// 5. 关闭连接
 		connection.close();
 	}
}

3.2.2 多线程创建连接

使用类单例模式,确保使用一个连接,可以同时用于多个线程。

package com.ma;

import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import java.io.IOException;

public class HBaseConnection {
    //声明一个静态属性
    public static Connection connection = null;
    static{
        //1.创建连接配置对象
//        Configuration conf = new Configuration();
//        //2.添加配置参数
//        conf.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");
        //因为已经把配置参数写到hbase-site.xml中,所以直接连接
        //3.创建连接
        //默认使用同步连接
        try {
            connection = ConnectionFactory.createConnection();
        } catch (IOException e) {
        	System.out.println("连接失败")
            e.printStackTrace();
        }
    }

    /**
     * 关闭连接方法
     * @throws IOException
     */
    public static void closeConnection() throws IOException {
        if (connection != null){
            // 关闭
            connection.close();
        }
    }

    public static void main(String[] args) throws IOException {
        //可以使用异步连接
//        CompletableFuture<AsyncConnection> asyncConnection = ConnectionFactory.createAsyncConnection();
        //4.使用连接
        System.out.println(connection);
        
        //在main最后关闭连接
        HBaseConnection.closeConnection();
    }
}

添加配置文件

在 resources 文件夹中创建配置文件 hbase-site.xml,添加以下内容

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration>
    <property>
        <name>hbase.zookeeper.quorum</name>
        <value>hadoop102,hadoop103,hadoop104</value>
        <description>The directory shared by RegionServers.</description>
    </property>
</configuration>

3.3 DDL

创建 HBaseDDL 类,添加静态方法即可作为工具类

public class HBaseDDL {
    // 添加静态属性 connection 指向单例连接
    public static Connection connection= HBaseConnection.connection;
}

3.3.1 创建命名空间

/**
     * 创建命名空间
     * @param namespace 命名空间名称
     */
    public static void  createNamespace(String namespace) throws IOException {
        //1.获取admin
        //admin 连接是轻量级的,不是线程安全的  不推荐池化,或者缓存这个连接
        Admin admin = connection.getAdmin();

        //2.调用方法,创建命名空间
       /* 代码相对shel1更加底层 所以shel1能够实现的功能代码一定能实现
        11所以需要填写完整的命名空间描述*/

        //2.1 创建命名空间描述建造者  => 设计师
        NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);
        //2.2 给命名空间添加需求
        builder.addConfiguration("user","mazankang");

        //2.3 使用builder构造出对应的NamespaceDescriptor添加完参数的对象
        //完成创建
        // 创建命名空间出现的问题  都属于本方法自身的问题  不应该抛出异常

        try {
            admin.createNamespace(builder.build());
        } catch (IOException e) {
            System.out.println("命名空间已经存在");
            e.printStackTrace();
        }

        //3 关闭admin
        admin.close();
    }

3.3.2 判断表格是否存在

/**
     *判断表格是否存在
     * @param namespace 命名空间名称
     * @param tableName 表格名称
     * @return true表示存在
     */
    public static boolean isTableExists(String namespace,String tableName) throws IOException {
        //1.获取admin
        Admin admin = connection.getAdmin();

        //2.使用对象的方法

        boolean b = false;
        try {
            b = admin.tableExists(TableName.valueOf(namespace, tableName));
        } catch (IOException e) {
            e.printStackTrace();
        }

        admin.close();

        return b;

    }

3.3.3 创建表

/**
     * 创建表格
     * @param namespace 命名空间名称
     * @param tableName  表格名称
     * @param columnFamilys 列族名称     可以有多个
     */
    public static void createTable(String namespace,String tableName,String... columnFamilys) throws IOException {
        //判断至少有一个列族
        if (columnFamilys.length ==0 ){
            System.out.println("创建表格需要至少一个列族");
            return;
        }
        //判断表格是否存在
        if (isTableExists(namespace,tableName)){
            System.out.println("表格已经存在");
            return;
        }

        //获取admin
        Admin admin = connection.getAdmin();

        //2.调用方法创建表格
        //2.1创建表格描述的建造者
        TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(namespace, tableName));

         //2.2添加参数
        for (String columnFamily : columnFamilys) {
            //2.3创建列族描述的建造者
            ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));
            //2.4对应当前列族添加版本
            //添加版本参数
            columnFamilyDescriptorBuilder.setMaxVersions(5);//版本
            // ------>>>>>>> 在这里可以加创造表属性所需要的所以方法
            //2.5创建添加完参数的列族描述
            tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptorBuilder.build());
        }

        //2.6创建对应的表格描述
        try {
            admin.createTable(tableDescriptorBuilder.build());
        } catch (IOException e) {
            e.printStackTrace();
        }

        admin.close();
    }

3.3.4 修改表

/**
     * 修改表格中一个列族的版本
     * @param namespace 命名空间名称
     * @param tableName 表格名称
     * @param columnFamily 列族名称
     * @param version 版本号
     */
    public static void modifyTable(String namespace,String tableName,String columnFamily,int version) throws IOException {
        //判断表格是否存在
        if (!isTableExists(namespace,tableName)){
            System.out.println("表格不存在");
            return;
        }

        //1.获取admin
        Admin admin = connection.getAdmin();

        //2.调用方法修改表格
        //2.0 获取之前的表格描述
        TableDescriptor descriptor = admin.getDescriptor(TableName.valueOf(namespace, tableName));
        // 需要填写旧的列族描述
        ColumnFamilyDescriptor columnFamily1 = descriptor.getColumnFamily(Bytes.toBytes(columnFamily));


        //如果使用填写tableName的方法相当于创建了一个新的表格描述建造者没有之前的信息
        //1如果想要修改之前的信息必须调用方法填写一个旧的表格描述
        //2.1 创建一个表格描述建造者
        //--------------------------
        TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(descriptor);
        //2.2 对应建造者进行表格数据的修改
        //创建列族描述建造者
        ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder =
                ColumnFamilyDescriptorBuilder.newBuilder(columnFamily1);

        //修改对应的版本
        columnFamilyDescriptorBuilder.setMaxVersions(version);

        //此处修改的时候 如果是新创建的 别的参数会初始化
        tableDescriptorBuilder.modifyColumnFamily(columnFamilyDescriptorBuilder.build());


        try {
            admin.modifyTable(tableDescriptorBuilder.build());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        //关闭
        admin.close();
    }

3.3.5 删除表

/**
     * 删除表格
     * @param namespace 命名空间名称
     * @param tableName 表格名称
     * @return true 表示删除成功
     */
    public static boolean deleteTable(String namespace,String tableName) throws IOException {
        //1判断表格是否存在
        if (!isTableExists(namespace,tableName)) {
            System.out.println("表格不存在");
            return false;
        }
        //2.获取admin
        Admin admin = connection.getAdmin();
        // 3.调用 相关的方法删除表格

        try {
            //HBase删除表格之前 一定要标记表格为不可用disable
            TableName tableName1 = TableName.valueOf(namespace, tableName);
            admin.disableTable(tableName1);
            admin.deleteTable(tableName1);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //关闭admin
        admin.close();

        return true;
    }

3.4 DML

创建类 HBaseDML

public class HBaseDML {
    //添加静态属性connection指向单例属性
    public static Connection connection = HBaseConnection.connection;
}

3.4.1 插入数据

/**
     * 插入数据
     * @param namespace 命名空间名称
     * @param tableName 表名称
     * @param rowKye 主键,
     * @param columnFamily 列族
     * @param columnName 列名
     * @param value 值
     */
    public static void putCell(String namespace,String tableName,String rowKye,String columnFamily,String columnName,String value) throws IOException {
        //获取table
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));

        //2,调用相关的方法往里面插入数据
        //创建put对象
        Put put = new Put(Bytes.toBytes(rowKye));
        //3,添加属性
        put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnName),Bytes.toBytes(value));
        //4.添加对象将对象写入相关的方法
        try {
            table.put(put);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        table.close();
    }

3.4.2 读取数据

/**
     * 读取数据 读取对应的一行中的某一列
     * @param namespace 命名空间名称
     * @param tableName 表名称
     * @param rowKye 主键
     * @param columnFamily 列族
     * @param columnName 列名
     */
    public static void getCells(String namespace,String tableName,String rowKye,String columnFamily,String columnName) throws IOException {
        //获取table
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));

        //2.创建get的对象
        Get get = new Get(Bytes.toBytes(rowKye));
        //如果现在调用get方法读取数据,此时读一整行数据
        get.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnName));

        //设置读取数据的版本
        get.readAllVersions();

        //读取数据得到result对象
        Result result = null;
        try {
            result = table.get(get);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //处理数据
        ///1测试方法:直接把读取的数据打印到空制台
        //||如果是实际开发需要再额外写方法对应处理数据
        Cell[] cells = result.rawCells();
        for (Cell cell : cells) {
            //ce11存储数据比较底层
            String value = new String(CellUtil.cloneValue(cell));
            System.out.println(value);
        }

        //关闭
        table.close();
    }

3.4.3 扫描数据

/**
     * 扫描数据
     * @param namespace 命名空间名称
     * @param tableName 表格名称
     * @param startRow 开始的row
     * @param stopRow 结束的Row 左闭右开
     */
    public static void scanRows(String namespace,String tableName,String startRow,String stopRow) throws IOException {
        //获取table
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));

        //2.创建scan 对象
        Scan scan = new Scan();
        //如果此时直接调用,会直接扫描整张表

        //添加参数 来控制扫描的数据
        scan.withStartRow(Bytes.toBytes(startRow));
        scan.withStopRow(Bytes.toBytes(stopRow));

        //读取多行数据 获得scanner
        ResultScanner scanner = null;
        try {
            scanner = table.getScanner(scan);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        //ResultScanner来记录多行 result的数组
        for (Result result : scanner) {
            Cell[] cells = result.rawCells();
            for (Cell cell : cells) {
                System.out.print(new String(CellUtil.cloneRow(cell))+"---"+
                                 new String(CellUtil.cloneFamily(cell))+"---"+
                                 new String(CellUtil.cloneQualifier(cell))+"---"+
                                 new String(CellUtil.cloneValue(cell))+"\t");
            }
            System.out.println();
        }

        table.close();
    }

3.4.4 带过滤扫描

/**
     * 带过滤的扫描
     * @param namespace 命名空间名称
     * @param tableName 表名称
     * @param startRow 开始row
     * @param stopRow 结束row
     * @param columnFamily 列族
     * @param columnName 列名
     * @param value 值
     * @throws IOException 异常
     */
    public static void filterScan(String namespace,String tableName,String startRow,String stopRow
    ,String columnFamily,String columnName,String value) throws IOException {
        //获取table
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));

        //2.创建scan 对象
        Scan scan = new Scan();
        //如果此时直接调用,会直接扫描整张表

        //添加参数 来控制扫描的数据
        scan.withStartRow(Bytes.toBytes(startRow));
        scan.withStopRow(Bytes.toBytes(stopRow));

        //------>>>>>>
        //可以添加多个过滤
        FilterList filterList = new FilterList();
        //创建过滤器
        //(1)结果值保留当前列的数据
        ColumnValueFilter columnValueFilter = new ColumnValueFilter(
                //列族名称
                Bytes.toBytes(columnFamily),
                //列名
                Bytes.toBytes(columnName),
                // 比较关系
                CompareOperator.EQUAL,
                // 值
                Bytes.toBytes(value)
        );
        //(2)结果保留整行数据
        SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
                //列族名称
                Bytes.toBytes(columnFamily),
                //列名
                Bytes.toBytes(columnName),
                // 比较关系
                CompareOperator.EQUAL,
                // 值
                Bytes.toBytes(value)
        );


//        filterList.addFilter(columnValueFilter);
        filterList.addFilter(singleColumnValueFilter);
        //添加过滤
        scan.setFilter(filterList);
        //读取多行数据 获得scanner
        ResultScanner scanner = null;
        try {
            scanner = table.getScanner(scan);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        //ResultScanner来记录多行 result的数组
        for (Result result : scanner) {
            Cell[] cells = result.rawCells();
            for (Cell cell : cells) {
                System.out.print(new String(CellUtil.cloneRow(cell))+"---"+
                        new String(CellUtil.cloneFamily(cell))+"---"+
                        new String(CellUtil.cloneQualifier(cell))+"---"+
                        new String(CellUtil.cloneValue(cell))+"\t");
            }
            System.out.println();
        }

        table.close();
    }

3.4.5 删除数据

/**
     * 删除一行中的一列数据
     * @param namespace 命名空间名称
     * @param tableName 表格名字
     * @param rowKey 主键
     * @param columnFamily 列族
     * @param columnName 列名
     */
    public static void deleteColumn(String namespace,String tableName,String rowKey,String columnFamily,String columnName) throws IOException {
        //获取table
        Table table = connection.getTable(TableName.valueOf(namespace, tableName));

        //创建delete对象
        Delete delete = new Delete(Bytes.toBytes(rowKey));

        //添加列信息
        //addColumn删除一个版本的数据
        //addColumns删除多个版本的数据
        delete.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnName));
        delete.addColumns(Bytes.toBytes(columnFamily),Bytes.toBytes(columnName));

        try {
            table.delete(delete);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        //关闭
        table.close();
    }