<<HBase presentation>>

#instruction

HBase是建立在HDFS之上,提供高可靠性、高性能、列存储、可伸缩、实时读写的分布式列存储的开源数据库系统。
  HBase是Apache的Hadoop 项目的子项目。 
HBase中每张表的记录数(行数)可多达几十亿条,甚至更多,每条记录可以拥有多达上百万的字段。而这样的存储能力却不需要特别的硬件,普通的服务器集群就可以胜任。

 

HBase表是一个分布式多维表,表中的数据通过一个行关键字(row key)、一个列族和列名(column family,column name)和一个时间戳(time stamp)进行索引和查询定位。

 

1.Row Key
HBase一张表中可以有上亿行记录,每一行都由一个行关键字(row key)来标识。
特点:
(1)HBase保证对所有行按照row key进行字典序(byte order)排序。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)
(2)Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。
(3)HBase中的row key 只能是一个字段而不能是多个字段的组合。(这与关系型数据库的主键(primary不同))。

 

2.列族和列
hbase表中的每个列,都归属于某个列族。

列族是表的schema(模式)的一部分(而列不是),必须在使用表之前定义。
列名都以列族作为前缀。例如courses:history,courses:math都属于courses 这个列族。
特点:在每个列族中,可以存放很多的列,而每行每列族中的列数量可以不同,数量可以很大。列是不需要静态定义的,每行都可以动态的增加和减少列。

 

3.时间戳

HBase中通过行关键字、列(列族名和列名)和时间戳的三元组确定一个存储单元(cell)。

每个 cell都保存着同一份数据的多个版本。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。

版本通过时间戳来索引。时间戳的类型是 64位整型。
时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。

Base的数据模型介绍
HBase的数据模型也是由一张张的表组成,每一张表里也有数据行和列,但是在HBase数据库中的行和列又和关系型数据库的稍有不同。下面统一介绍HBase数据模型中一些名词的概念:
      
         表(Table): HBase会将数据组织进一张张的表里面,但是需要注意的是表名必须是能用在文件路径里的合法名字,因为HBase的表是映射成hdfs上面的文件。

         行(Row): 在表里面,每一行代表着一个数据对象,每一行都是以一个行键(Row Key)来进行唯一标识的,行键并没有什么特定的数据类型,以二进制的字节来存储。

         列族(Column Family): 在定义HBase表的时候需要提前设置好列族, 表中所有的列都需要组织在列族里面,列族一旦确定后,就不能轻易修改,因为它会影响到HBase真实的物理存储结构,但是列族中的列标识(Column Qualifier)以及其对应的值可以动态增删。表中的每一行都有相同的列族,但是不需要每一行的列族里都有一致的列标识(Column Qualifier)和值,所以说是一种稀疏的表结构,这样可以一定程度上避免数据的冗余。例如:{row1, userInfo: telephone —> 137XXXXX869 }{row2, userInfo: fax phone —> 0898-66XXXX } 行1和行2都有同一个列族userinfo,但是行1中的列族只有列标识(Column Qualifier):移动电话号码,而行2中的列族中只有列标识(Column Qualifier):传真号码。

         列标识(Column Qualifier): 列族中的数据通过列标识来进行映射,其实这里大家可以不用拘泥于“列”这个概念,也可以理解为一个键值对,Column Qualifier就是Key。列标识也没有特定的数据类型,以二进制字节来存储。

         单元(Cell): 每一个 行键,列族和列标识共同组成一个单元,存储在单元里的数据称为单元数据,单元和单元数据也没有特定的数据类型,以二进制字节来存储。
 

         时间戳(Timestamp): 默认下每一个单元中的数据插入时都会用时间戳来进行版本标识。读取单元数据时,如果时间戳没有被指定,则默认返回最新的数据,写入新的单元数据时,如果没有设置时间戳,默认使用当前时间。每一个列族的单元数据的版本数量都被HBase单独维护,默认情况下HBase保留3个版本数据。

hbase 自带zookeeper 改端口_hadoop

图片来自:http://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf

 

        有时候,你也可以把HBase看成一个多维度的Map模型去理解它的数据模型。正如下图,一个行键映射一个列族数组,列族数组中的每个列族又映射一个列标识数组,列标识数组中的每一个列标识(Column Qualifier)又映射到一个时间戳数组,里面是不同时间戳映射下不同版本的值,但是默认取最近时间的值,所以可以看成是列标识(Column Qualifier)和它所对应的值的映射。用户也可以通过HBase的API去同时获取到多个版本的单元数据的值。Row Key在HBase中也就相当于关系型数据库的主键,并且Row Key在创建表的时候就已经设置好,用户无法指定某个列作为Row Key。

 

hbase 自带zookeeper 改端口_HBase_02

图片来自:http://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf    

       

       又有的时候,你也可以把HBase看成是一个类似Redis那样的Key-Value数据库。如下图,当你要查询某一行的所有数据时,Row Key就相当于Key,而Value就是单元中的数据(列族,列族里的列和列中时间戳所对应的不同版本的值);当深入到HBase底层的存储机制时,用户要查询指定行里某一条单元数据时,HBase会去读取一个数据块,里面除了有要查询的单元数据,可能同时也会获取到其它单元数据,因为这个数据块还包含着这个Row Key所对应的其它列族或其它的列信息,这些信息实际也代表着另一个单元数据,这也是HBase的API内部实际的工作原理。

hbase 自带zookeeper 改端口_时间戳_03

 

#zookeeper安装

  1. 主机名称到IP地址映射配置

#ZooKeeper集群中具有两个关键的角色:Leader和Follower。集群中所有的结点作为一个整体对分布式应用提供服务,集群中每个结点之间都互相连接,所以,在配置的ZooKeeper集群的时候,每一个结点的host到IP地址的映射都要配置上集群中其它结点的映射信息。

#例如,我的ZooKeeper集群中每个结点的配置,以master为例,/etc/hosts内容如下所示:

192.168.74.128 master

192.168.74.129 node1

192.168.74.130 node2

ZooKeeper采用一种称为Leader election的选举算法。在整个集群运行过程中,只有一个Leader,其他的都是Follower,如果ZooKeeper集群在运行过程中Leader出了问题,系统会采用该算法重新选出一个Leader。因此,各个结点之间要能够保证互相连接,必须配置上述映射。

ZooKeeper集群启动的时候,会首先选出一个Leader,在Leader election过程中,某一个满足选举算的结点就能成为Leader

  1. 修改ZooKeeper配置文件

#在其中一台机器(master)上,解压缩zookeeper-3.4.3.tar.gz,修改配置文件conf/zoo.cfg,内容如下所示:

vi conf/zoo.cfg

   配置如下:

# the directory where the snapshot is stored.
dataDir=/var/zookeeper/data
 
# the port at which the clients will connect
clientPort=2181
 
server.0=master:2888:3888
server.1=node1:2888:3888
server.2=node2:2888:3888
 
initLimit=5
maxClientCnxns=0
syncLimit=2
tickTime=2000

  

 

  1. 远程复制分发安装文件

#上面已经在一台机器master上配置完成ZooKeeper,现在可以将该配置好的安装文件远程拷贝到集群中的各个结点对应的目录下

#cd /hadoop/modules
# scp -r zookeeper-3.4.3/ node1:/hadoop/modules/
# scp -r zookeeper-3.4.3/ node1:/hadoop/modules/

 

  1. 设置myid

#在我们配置的dataDir指定的目录下面,创建一个myid文件,里面内容为一个数字,用来标识当前主机,conf/zoo.cfg文件中配置的server.X中X为什么数字,则myid文件中就输入这个数字,例如:

 

创建两个目录:
#mkdir -p /var/zookeeper/data
#mkdir -p /var/zookeeper/log
进入data目录
#cd /var/zookeeper/data
创建myid文件
# echo 1 >myid

  #每台机器都要配置好

  1. 配置zookeeper环境变量

#创建一个环境变量ZOOKEEPER_HOME并把该环境变量添加到系统路径

#vi /etc/profile.d/zookeeper_home.sh
输入如下:
export ZOOKEEPER_HOME=/hadoop/modules/zookeeper-3.4.3
export PATH=$ZOOKEEPER_HOME/bin:$PATH
将环境变量导入:
# source /etc/profile

 

#每台机器都要配置好环境变量

 

#完成以上操作zookeeper安装完成

 

  1. 测试zookeeper是否安装成功

#在三台机器上分别执行shell脚本,由于已经把可执行路径添加到环境变量中了,所以可以直接执行:

# zkServer.sh start
JMX enabled by default
Using config: /hadoop/modules/zookeeper-3.4.3/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

masterC :

hbase 自带zookeeper 改端口_hadoop_04

node2C1

hbase 自带zookeeper 改端口_HBase_05

node2C2:

hbase 自带zookeeper 改端口_HBase_06

#以结点master为例,日志如下所示:

# tail -500f zookeeper.out
#启动的顺序是master>node1>node2,由于ZooKeeper集群启动的时候,
每个结点都试图去连接集群中的其它结点,先启动的肯定连不上后面还没启动的,
所以上面日志前面部分的异常是可以忽略的。通过后面部分可以看到,
集群在选出一个Leader后,最后稳定了。
其他结点可能也出现类似问题,属于正常

 

#查看zookeeper集群是否启动

# zkServer.sh status
JMX enabled by default
Using config: /hadoop/modules/zookeeper-3.4.3/bin/../conf/zoo.cfg
Mode: leader

masterC:

hbase 自带zookeeper 改端口_HBase_07

node2C1:

hbase 自带zookeeper 改端口_HBase_08

node2C2:

hbase 自带zookeeper 改端口_hadoop_09

#可以通过客户端脚本,连接到ZooKeeper集群上。对于客户端来说,ZooKeeper是一个整体(ensemble),连接到ZooKeeper集群实际上感觉在独享整个集群的服务,所以,你可以在任何一个结点上建立到服务集群的连接,在其中一台机器上执行客户端脚本,来查看这台服务器是否启动:

# zkCli.sh -server 192.168.74.128:2181
进入去之后输入
[zk: node2:2181(CONNECTED) 0] ls /
显示
[zookeeper]

 

#hbase安装

  1. 安装hbase之前必须把zookeeper安装好,将hbase压缩包解压

#启动HDFS集群实例,并创建目录hdfs://master:9000/hbase,在master上执行

# hadoop fs -mkdir /hbase

验证创建是否成功

# hadoop fs –lsr /hbase

#解压hbase安装包

# tar –zxvf /hadoop/modules/hbase-0.94.6.tar.gz

 

  1. 配置hbase环境变量
# vi /etc/profile.d/hbase.sh

 

#复制粘贴一下内容 到 vi 中。

export HBASE_HOME=/hadoop/modules/hbase-0.94.6
export PATH=$HBASE_HOME/bin:$PATH

 

 

 

#手动立即生效

# source /etc/profile

 

#每台机器都要配置好环境变量

 

  1. 修改hbase配置文件

#修改hbase-env.sh

# vi /hadoop/modules/hbase-0.94.6/conf/hbase-env.sh

加入配置

export JAVA_HOME=/usr/java/jdk1.6.0_15/
export HBASE_CLASSPATH=/hadoop/modules/hadoop/hadoop-1.0.3/conf
export HBASE_OPTS="$HBASE_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode"
export HBASE_HEAPSIZE=128
export HBASE_MANAGES_ZK=false <!—使用独立的zookeeper为false-->

#修改hbase-site.xml

# vi /hadoop/modules/hbase-0.94.6/conf/hbase-site.sh

加入配置

<property>
       <name>hbase.rootdir</name>
       <value>hdfs://master:9000/hbase</value>
      //必须与你的hadoop主机名,端口号一致;Hbase该项并不识别机器IP,只能使用机器hostname才行
    </property>
    <property>
       <name>hbase.cluster.distributed</name>
       <value>true</value>
<!-- Hbase的运行模式。false是单机模式,true是分布式模式。若为false,Hbase和Zookeeper会运行在同一个JVM里面-->
    </property>
    <property>
       <name>hbase.zookeeper.quorum</name>
       <value>master,node1,node2</value>
//hbase.zookeeper.quorum 的个数必须是奇数 
</property>
<property>
        <name>zookeeper.session.timeout</name>
        <value>60000</value>
<!-- ZooKeeper 会话超时.HBase把这个值传递改zk集群,向他推荐一个会话的最大超时时间-->
</property>
<property>
        <name>hbase.zookeeper.property.clientPort</name>
        <value>2181</value>
<!--HBASE_MANAGES_ZK=false时该端口号要与zoo.cfg中配置clientPort属性的端口号一致-->
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<!-- ZooKeeper的zoo.conf中的配置。 快照的存储位置-->
<value>/var/zookeeper/data/</value>
<description>Property from ZooKeeper's config zoo.cfg.
The directory where the snapshot is stored.
</description>
</property>

#配置regionservers

# vi /hadoop/modules/hbase-0.94.6/conf/regionservers

输入:

node1
node2

完全分布式模式的还需要修改conf/regionservers. 在这里列出了你希望运行的全部 HRegionServer,一行写一个host (就像Hadoop里面的 slaves 一样)

  1. 将配置好的hbase文件复制到其他集群机器
# scp -r /hadoop/modules/hbase-0.94.6 node1:/hadoop/modules/
# scp -r /hadoop/modules/hbase-0.94.6 node2:/hadoop/modules/

 

  1. 启动hbase,并测试是否安装成功

#先检查hdfs是否启动,并且查看安全模式是否为off状态

# hadoop dfsadmin -safemode get

#当为off状态是可以启动hbase,否则就要手动退出off状态

# hadoop dfsadmin -safemode leave

 

#启动hbase之前先将集群机器上的zookeeper都启动好

#启动hbase

# start-hbase.sh

Master:

hbase 自带zookeeper 改端口_HBase_10

Node:

hbase 自带zookeeper 改端口_HBase_11

#进入hbase操作:

# hbase shell

 

#通过浏览器查看16010端口:

hbase 自带zookeeper 改端口_zookeeper_12

 

* Eclipse Development

首先在C:\Windows\System32\drivers\etc下面的HOSTS文件,加上linux 集群

hbase 自带zookeeper 改端口_hadoop_13

#定义成员变量
publicstatic Configuration configuration;
publicstatic Connection connection;
publicstatic Admin admin;
 
#初始化
publicstaticvoid init() {
        configuration = HBaseConfiguration.create();
        configuration.set("hbase.zookeeper.property.clientPort", "2181");
        configuration.set("hbase.zookeeper.quorum", "192.168.111.160,192.168.111.158,192.168.111.159");
        configuration.set("hbase.master", "192.168.111.159:60000");
        System.setProperty("hadoop.home.dir", "F:/vm_share/hadoop-2.7.5");
        System.setProperty("HADOOP_USER_NAME", "root");
        try {
            connection = ConnectionFactory.createConnection(configuration);
            admin = connection.getAdmin();
        } catch (IOException e) {
e.printStackTrace();
        }
}
 
// 关闭连接
     public static void close() {
         try {
             if (null != admin)
                 admin.close();
             if (null != connection)
                 connection.close();
         } catch (IOException e) {
             e.printStackTrace();
         }    }
// 建表
     public static void createTable(String tableNmae, String[] cols) throws IOException {        init();
         TableName tableName = TableName.valueOf(tableNmae);        if (admin.tableExists(tableName)) {
             System.out.println("talbe is exists!");
         } else {
             HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
             for (String col : cols) {
                 HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(col);
                 hTableDescriptor.addFamily(hColumnDescriptor);
             }
             admin.createTable(hTableDescriptor);
         }
         close();
     }    // 删表
     public static void deleteTable(String tableName) throws IOException {
         init();
         TableName tn = TableName.valueOf(tableName);
         if (admin.tableExists(tn)) {
             admin.disableTable(tn);
             admin.deleteTable(tn);
         }
         close();
     }// 查看已有表
    publicstaticvoid listTables() throws IOException {
        init();
        HTableDescriptor hTableDescriptors[] = admin.listTables();
        for (HTableDescriptor hTableDescriptor : hTableDescriptors) {
            System.out.println(hTableDescriptor.getNameAsString());
        }
// 插入数据
     public static void insterRow(String tableName, String rowkey, String colFamily, String col, String val)
             throws IOException {
         init();
         Table table = connection.getTable(TableName.valueOf(tableName));
         Put put = new Put(Bytes.toBytes(rowkey));
         put.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(col), Bytes.toBytes(val));
         table.put(put);        // 批量插入
         /*
          * List<Put> putList = new ArrayList<Put>(); puts.add(put);
          * table.put(putList);
          */
         table.close();
         close();
     }    // 删除数据
     public static void deleRow(String tableName, String rowkey, String colFamily, String col) throws IOException {
         init();
         Table table = connection.getTable(TableName.valueOf(tableName));
         Delete delete = new Delete(Bytes.toBytes(rowkey));
         // 删除指定列族
         // delete.addFamily(Bytes.toBytes(colFamily));
         // 删除指定列
         // delete.addColumn(Bytes.toBytes(colFamily),Bytes.toBytes(col));
         table.delete(delete);
         // 批量删除
         /*
          * List<Delete> deleteList = new ArrayList<Delete>();
          * deleteList.add(delete); table.delete(deleteList);
          */
         table.close();
         close();
     }    // 根据rowkey查找数据
     public static void getData(String tableName, String rowkey, String colFamily, String col) throws IOException {
         init();
         Table table = connection.getTable(TableName.valueOf(tableName));
         Get get = new Get(Bytes.toBytes(rowkey));
         // 获取指定列族数据
         // get.addFamily(Bytes.toBytes(colFamily));
         // 获取指定列数据
         // get.addColumn(Bytes.toBytes(colFamily),Bytes.toBytes(col));
         Result result = table.get(get);        showCell(result);
         table.close();
         close();
     }        close();
}

-- edit by Hugo