大数据HBase_04_01

来源:互联网 发布:json date 格式化注解 编辑:程序博客网 时间:2024/06/07 05:04

Hadoop生态系统 


HBase 简介

--HBase – Hadoop Database,是一个高可靠性、高性能(秒级别读取)、面向列、可伸缩、实时读写的分布式数据库

--利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。

--HBase利用Hadoop HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为分布式协调服务。

--HBase主要存储非结构化和半结构化的松散数据。

注意:HBASE是免费的,存储能力强;在国内使用比较活跃。如新浪微博数据的存储是由redis存储,现在正往HBASE转移。

具体细节参考文档:http://abloz.com/hbase/book.html

HBase 数据模型


概念的理解

Row Key:HBASE的主键,用来检索记录。

--决定一行数据

--按照字典顺序排序

--Row key只能存储64k的字节数据

Column Family列族 &qualifier

--列族在创建表的时候声明,一个列族可以包含多个列,列中的数据都是以二进制形式存在,没有数据类型。因此列中可以存储多种格式的数据如图片视频等信息。

--HBase表中的每个列都归属于某个列族,列族必须作为表模式(schema)定义的一部分预先给出。如 create ‘test’, ‘course’;

--列名以列族作为前缀,每个“列族”都可以有多个列成员(column);如course:math, course:english, 新的列族成员(列)可以随后按需、动态加入;
--权限控制、存储以及调优都是在列族层面进行的;
--HBase把同一列族里面的数据存储在同一目录下,由几个文件保存。

Cell单元格

由行和列的坐标交叉决定;
单元格是有版本的;
单元格的内容是未解析的字节数组;
由{row key, column( =<family> +<qualifier>), version} 唯一确定的单元。
cell中的数据是没有类型的,全部是字节码形式存贮。

timestamp:时间戳

每个 cell都保存着同一份数据的多个版本。版本通过时间戳来索引

物理存储。

 1、Table 在行的方向上分割为多个HRegion,一个region[startkey,endkey)表示,每个HRegion分散在不同的RegionServer中。




2、region按大小分割的,每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值的时候,Hregion就会等分会两个新的Hregion。当table中的行不断增多,就会有越来越多的Hregion。


3、Hregion是Hbase中分布式存储和负载均衡的最小单元。最小单元就表示不同的Hregion可以分布在不同的HRegion server上。但一个Hregion是不会拆分到多个server上的。

4、由上图看出HBase表的中的数据不可能全部存储在一台机器中,它是分布式存储在多台机器上,这样在读写的时候的都是操作不同的机器,提高了读写的效率。

总之:一个表被分割为了多个region,每个region保存着一个rowkey范围的数据,这些region被保存在了多台机器中。

注意:region是分布式存储以及负载均衡的最小的单元。

      一个region保存着一个rowkey范围的数据。

      表中的数据是根据行键按照字典顺序进行排序的。


5、HRegion虽然是分布式存储的最小单元,但并不是存储的最小单元。

事实上,HRegion由一个或者多个Store组成,每个store保存一个columns family。

每个Strore又由一个memStore和0至多个StoreFile组成。如图:

StoreFile以HFile格式保存在HDFS上。



HBase 架构


Client
--包含访问HBase的接口、并维护cache来加快对HBase的访问。
Zookeeper
--保证任何时候,集群中只有一个master
--存贮所有Region的寻址入口。
--实时监控Region server的上线和下线信息。并实时通知Master
--存储HBase的schema和table元数据

Master(集群的主节点)
--为Region server分配region
--负责Region server的负载均衡
--发现失效的Region server并重新分配其上的region
--管理用户对table的增删改操作

RegionServer(集群的从节点)

--Region server维护Master分配给它的region,处理对这些region的IO请求

--Region server负责切分在运行过程中变得过大的region。

Region 

--HBase自动把表水平划分成多个区域(region),每个region会保存一个表里面某段连续的数据
--每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region(裂变)
--当table中的行不断增多,就会有越来越多的region。这样一张完整的表被保存在多个Regionserver 上。


Memstore 与 storefile

一个region由多个store组成,一个store对应一个CF(列族)

store包括位于内存中的memstore和位于磁盘的storefile;写操作先写入memstore,当memstore中的数据达到某个阈值,hregionserver会启动flashcache进程写入storefile,每次写入形成单独的一个storefile

当storefile文件的数量增长到一定阈值后,系统会进行合并(minor、major compaction),在合并过程中会进行版本合并和删除工作(majar),形成更大的storefile。
当一个region所有storefile的大小和数量超过一定阈值后,会把当前的region分割为两个,并由hmaster分配到相应的regionserver服务器,实现负载均衡
客户端检索数据,先在memstore找,找不到再找storefile


HRegion是HBase中分布式存储和负载均衡的最小单元。最小单元就表示不同的HRegion可以分布在不同的 HRegion server上。
HRegion由一个或者多个Store组成,每个store保存一个columns family。
每个Strore又由一个memStore和0至多个StoreFile组成。如图:StoreFile以HFile格式保存在HDFS上。

HBase优化



HBASE的伪分布式搭建

在itcast02上搭建HBase的伪分布式
前提环境:Java环境变量的配置。
                  HBase的安装以及环境变量的配置

1.hbase-env.sh中配置JAVA_HOME
2.配置hbase-site.xml如下
<property>
<name>hbase.rootdir</name>
<value>file:///home/testuser/hbase</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/testuser/zookeeper</value>
</property>

启动HBase

start-hbase.sh


Hbase的命令操作

执行hbase的命令行

迁移到 hbase的bin目录下 执行 hbase shell命令

使用命令行的方式创建表和插入数据。

创建表:

create 'people',{NAME=>'info',VERSIONS=>3},{NAME=>'data',VERSIONS=>1}


查看所有的表list命令。

查看指定表的描述describe

插入数据put命令。

如:往列族info中插入数据

    put 'people', 'rk0001', 'info:name', 'cls'

put 'people', 'rk0001', 'info:gender', 'femal'

put 'people','rk0001', 'info:size', '36'

    往列族data中插入数据

put 'people', 'rk0001', 'data:torrent', '种子'

 

其中rk0001hbase的主键;info为列族;nameinfo列族下的列;cls为列name的值。

查看数据scan 'people'


这个时候返回的数据为一行4列,其中行数由主键决定。

往另一个主键中插入数据

put 'people', 'rk0002', 'info:name', 'bdyjy'

put 'people', 'rk0002', 'info:gender', 'female'


这个时候返回了两行数据。

同一列插入多次数据

put 'people','rk0001', 'info:size', '37'

put 'people','rk0001', 'info:size', '37.5'


在这里不要认为是把原来的数据覆盖掉了。而是它有自己的历史记录。

scan 'people', {COLUMNS => 'info', VERSIONS=>3}


注意:当再次插入put 'people','rk0001', 'info:size', '38'

也就是说同一列中插入的数据超过版本号后会将某条原来的数据删除。

HBase的完全分布式安装


首先保证 Hadoop集群正常运行, 启动!!
1、环境准备
   hostname、hosts、网络、防火墙
   时间同步!!!
yum install ntp -y
ntpdate 202.120.2.101(时间服务器)
2、免密钥
   主 -> 从
   主 -> 主
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
ssh-copy-id nodeX
3、上传压缩包  解压
      配置环境变量
4、修改配置文件
   hbase-env.sh   JAVA_HOME;  HBASE_MANAGES_ZK=false
   hbase-site.xml
   regionservers
   backup-masters
   hdfs-site.xml
  1)hbase-site.xml

 <property>
<name>hbase.rootdir</name>
<value>hdfs://ns1/hbase</value>  #####ns1为nameservice的值
</property>
<property>
<name>hbase.cluster.distributed</name>               ####是否是分布式   
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>                      ##Zookeeper的集群节点
<value>weeked11,weekend12,weekend13</value>

</property>
 2)regionservers
    weekend11
    weekend12
    weekend13
3)backup-masters
    vi backup-masters
    weekend12
 4)hdfs-site.xml 复制到hbase的conf目录下

5、同步配置文件 !!! 
6、启动集群
   在Master 执行start-hbase.sh(在哪台节点上启动,哪台节点就是Master、这里我们weekend10上启动)
搭建过程出错:
看日志 分析问题
删除hdfs  /hbase目录
删除ZooKeeper当中 /hbase结点  zkCli.sh   rmr /hbase
修改错误  配置文件
重新启动集群


整个集群启动后查看Zookeeper某个节点


Java API的方式操作Hbase

public class HBaseDemo {
HBaseAdmin admin;
HTable hTable;
Configuration conf;
String TN = "phone";
@Before
public void begin() throws Exception {
conf = new Configuration(true);
// hbase ZooKeeper集群 伪分布式-那一台服务器
conf.set("hbase.zookeeper.quorum", "weekend11,weekend12,weekend13");
admin = new HBaseAdmin(conf);
hTable = new HTable(conf, TN);
}
@After
public void end() throws Exception {
if (admin != null) {
admin.close();
}
if (hTable != null) {
hTable.close();
}
}
@Test
public void createTable() throws Exception {
// 判断创建的表是否存在
if (admin.tableExists(TN)) {
//禁用创建的表
admin.disableTable(TN);
admin.deleteTable(TN);
}
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(TN));// 设置表名
         HColumnDescriptor cf = new HColumnDescriptor("cf");// 创建列族
cf.setInMemory(true);// 设置缓存
desc.addFamily(cf);// 将创建的列族添加到表中
admin.createTable(desc);
}
@Test
public void insertDB() throws Exception {
// 参数 rowkey
Put put = new Put("123123".getBytes());
put.add("cf".getBytes(), "name".getBytes(), "xiaoming".getBytes());
put.add("cf".getBytes(), "age".getBytes(), "11".getBytes());
put.add("cf".getBytes(), "sex".getBytes(), "man".getBytes());
hTable.put(put);
}
@Test
public void getDB1() throws Exception {
Get get = new Get("123123".getBytes());
get.addColumn("cf".getBytes(), "name".getBytes());
Result rs = hTable.get(get);
Cell cell = rs.getColumnLatestCell("cf".getBytes(), "name".getBytes());
System.out.println(new String(CellUtil.cloneValue(cell)));

}

/**
* 模拟 通话详单 十个用户 产生一百条通话记录 rowkey: 手机号_(大数-时间戳)
*/
@Test
public void insertDB2() throws Exception {
List<Put> puts = new ArrayList<Put>();
for (int i = 0; i < 10; i++) {
// 自己的手机号
String pnum = getPhoneNum("186");
for (int j = 0; j < 100; j++) {
// 通话时间
String dateStr = getDate("2017");
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String rowkey = pnum + "_" + (Long.MAX_VALUE - sdf.parse(dateStr).getTime());
// 对方手机号
String dnum = getPhoneNum("177");
// 主叫0被叫1类型
String type = r.nextInt(2) + "";
// 通话时长
String telLength = r.nextInt(100) + "";
Put put = new Put(rowkey.getBytes());
put.add("cf".getBytes(), "dnum".getBytes(), dnum.getBytes());
put.add("cf".getBytes(), "type".getBytes(), type.getBytes());
put.add("cf".getBytes(), "telLength".getBytes(), telLength.getBytes());
put.add("cf".getBytes(), "date".getBytes(), dateStr.getBytes());
puts.add(put);
} }

hTable.put(puts);
}
/**
* 查询 某个用户 某个月 通话记录 范围查找
*/
@Test
public void scanDB1() throws Exception {

Scan scan = new Scan();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
// 18688166887手机号 一月份 的所有通话记录
String startRowkey = "18688166887_" + (Long.MAX_VALUE - sdf.parse("20170201000000").getTime());
String stopRowkey = "18688166887_" + (Long.MAX_VALUE - sdf.parse("20170101000000").getTime());
scan.setStartRow(startRowkey.getBytes());
scan.setStopRow(stopRowkey.getBytes());
ResultScanner rss = hTable.getScanner(scan);
for (Result rs : rss) {
System.out
.print(new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
System.out.print(
"  " + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
System.out.print("  "
+ new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "telLength".getBytes()))));
System.out.println(
"  " + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
}
}
/**
* 查询某个用户 所有的主叫类型的 通话记录 18688166887 cf:type=0 过滤器

* @throws Exception
*/
@Test
public void scanDB2() throws Exception {
FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL);
PrefixFilter filter1 = new PrefixFilter("18688166887".getBytes());

list.addFilter(filter1);
SingleColumnValueFilter filter2 = new SingleColumnValueFilter("cf".getBytes(), "type".getBytes(),
CompareOp.EQUAL, "0".getBytes());
list.addFilter(filter2);
Scan scan = new Scan();
scan.setFilter(list);
ResultScanner rss = hTable.getScanner(scan);
for (Result rs : rss) {
System.out.print(new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
System.out.print("  " + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
System.out.print("  "+ new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "telLength".getBytes()))));
System.out.println("  " + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
}
}
/**
* 删除 cell ??
*/
Random r = new Random();
/**
* 随机生成测试手机号码 prefix: 手机号码前缀 eq:186
*/
public String getPhoneNum(String prefix) {
return prefix + String.format("%08d", r.nextInt(99999999));
}
/**
* 随机生成时间

* @param year年
* @return 时间 格式:yyyyMMddHHmmss
*/
public String getDate(String year) {
return year + String.format("%02d%02d%02d%02d%02d",new Object[] { r.nextInt(12) + 1, r.nextInt(28) + 1, r.nextInt(24), r.nextInt(60), r.nextInt(60) });
}
}

执行ceateTable()方法时使用 hbase shell 命令查看 执行情况


Web界面的查看

插入数据后的情况