大数据开发基础下 学习笔记

来源:互联网 发布:海岛奇研究所升级数据 编辑:程序博客网 时间:2024/04/27 02:33

hbase原理介绍

Hbase是基于Hadoop的项目,所以一般情况下我们使用的直接就是HDFS文件系统,这里我们不深谈HDFS如何构造其分布式的文件系统,只需要知道虽然Hbase中有多个RegionServer的概念,并不意味着数据是持久化在RegionServer上的,事实上,RegionServer是调度者,管理Regions,但是数据是持久化在HDFS上的。明确这一点,在后面的讨论中,我们直接把文件系统抽象为HDFS,不再深究。

 

    Hbase是一个分布式的数据库,使用Zookeeper来管理集群。在架构层面上分为Master(Zookeeper中的leader)和多个RegionServer,基本架构如图:


    在Hbase的概念中,RegionServer对应于集群中的一个节点,而一个RegionServer负责管理多个Region。一个Region代表一张表的一部分数据,所以在Hbase中的一张表可能会需要很多个Region来存储其数据,但是每个Region中的数据并不是杂乱无章的,Hbase在管理Region的时候会给每个Region定义一个Rowkey的范围,落在特定范围内的数据将交给特定的Region,从而将负载分摊到多个节点上,充分利用分布式的优点。另外,Hbase会自动的调节Region处在的位置,如果一个RegionServer变得Hot(大量的请求落在这个Server管理的Region上),Hbase就会把Region移动到相对空闲的节点,依次保证集群环境被充分利用。

 

二、存储模型

    有了架构层面的保证,接下来的事情就只是关注于数据的具体存储了。这里就是每个Region所承担的工作了。我们知道一个Region代表的是一张Hbase表中特定Rowkey范围内的数据,而Hbase是面向列存储的数据库,所以在一个Region中,有多个文件来存储这些列。Hbase中数据列是由列簇来组织的,所以每一个列簇都会有对应的一个数据结构,Hbase将列簇的存储数据结构抽象为Store,一个Store代表一个列簇。



     所以在这里也可以看出为什么在我们查询的时候要尽量减少不需要的列,而经常一起查询的列要组织到一个列簇里:因为要需要查询的列簇越多,意味着要扫描越多的Store文件,这就需要越多的时间。

 

    我们来深入Store中存储数据的方式。Hbase的实现是用了一种LSM 树的结构,LSM树是由B+树改进而来,所以我们首先来简单的看看B+树。


 

    这是一颗简单的B+树,含义不言而喻,这里不多分析,但是这种数据结构并不适合Hbase中的应用场景。这样的数据结构在内存中效率是很高的,但是Hbase中数据是存储在文件中的,如果按照这样的结构来存储,意味着我们每一次插入数据都要由一级索引找到文件再在文件中间作操作来保证数据的有序性,这无疑是效率低下的。所以Hbase采用的是LSM树的结构,这种结构的关键是,每一次的插入操作都会先进入MemStore(内存缓冲区),当MemStore达到上限的时候,Hbase会将内存中的数据输出为有序的StoreFile文件数据(根据Rowkey、版本、列名排序,这里已经和列簇无关了因为Store里都属于同一个列簇)。这样会在Store中形成很多个小的StoreFile,当这些小的File数量达到一个阀值的时候,Hbase会用一个线程来把这些小File合并成一个大的File。这样,Hbase就把效率低下的文件中的插入、移动操作转变成了单纯的文件输出、合并操作。

 

    由上可知,在Hbase底层的Store数据结构中,每个StoreFile内的数据是有序的,但是StoreFile之间不一定是有序的,Store只需要管理StoreFile的索引就可以了。这里也可以看出为什么指定版本和Rowkey可以加强查询的效率,因为指定版本和Rowkey的查询可以利用StoreFile的索引跳过一些肯定不包含目标数据的数据。

 

 

hbase开发实例

1、开发环境

在进行Hbase开发前,需要安装JDK、Hadoop和HBase

根据自己的安装环境修改版本信息,使用Maven构建项目,在pom.xml中添加hbase的依赖

2、初始化配置

设置HBase的配置,如ZooKeeper的地址、端口号等等。可以通过org.apache.hadoop.conf.Configuration.set方法手工设置HBase的配置信息,也可以直接将HBase的hbase-site.xml配置文件引入项目即可。

3、常见API的使用

HBase的常用操作包括建表、插入表数据、删除表数据、获取一行数据、表扫描、删除列族、删除表等等,下面给出具体代码。

3.1 创建数据库表

  1.  // 创建数据库表  
  2. public static void createTable(String tableName, String[] columnFamilys) throws IOException {  
  3.     // 建立一个数据库的连接  
  4.     Connection conn = ConnectionFactory.createConnection(conf);  
  5.     // 创建一个数据库管理员  
  6.     HBaseAdmin hAdmin = (HBaseAdmin) conn.getAdmin();  
  7.     if (hAdmin.tableExists(tableName)) {  
  8.         System.out.println(tableName + "表已存在");  
  9.         conn.close();  
  10.         System.exit(0);  
  11.     } else {  
  12.         // 新建一个表描述  
  13.         HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName));  
  14.         // 在表描述里添加列族  
  15.         for (String columnFamily : columnFamilys) {  
  16.             tableDesc.addFamily(new HColumnDescriptor(columnFamily));  
  17.         }  
  18.         // 根据配置好的表描述建表  
  19.         hAdmin.createTable(tableDesc);  
  20.         System.out.println("创建" + tableName + "表成功");  
  21.     }  
  22.     conn.close();  
  23. }  

3.2 添加一条数据

  1.  // 添加一条数据  
  2. public static void addRow(String tableName, String rowKey, String columnFamily, String column, String value)   
  3.         throws IOException {  
  4.     // 建立一个数据库的连接  
  5.     Connection conn = ConnectionFactory.createConnection(conf);  
  6.     // 获取表  
  7.     HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));  
  8.     // 通过rowkey创建一个put对象  
  9.     Put put = new Put(Bytes.toBytes(rowKey));  
  10.     // 在put对象中设置列族、列、值  
  11.     put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column), Bytes.toBytes(value));  
  12.     // 插入数据,可通过put(List<Put>)批量插入  
  13.     table.put(put);  
  14.     // 关闭资源  
  15.     table.close();  
  16.     conn.close();  
  17. }  

3.3 获取一条数

  1. // 通过rowkey获取一条数据  
  2. public static void getRow(String tableName, String rowKey) throws IOException {  
  3.     // 建立一个数据库的连接  
  4.     Connection conn = ConnectionFactory.createConnection(conf);  
  5.     // 获取表  
  6.     HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));  
  7.     // 通过rowkey创建一个get对象  
  8.     Get get = new Get(Bytes.toBytes(rowKey));  
  9.     // 输出结果  
  10.     Result result = table.get(get);  
  11.     for (Cell cell : result.rawCells()) {  
  12.         System.out.println(  
  13.                 "行键:" + new String(CellUtil.cloneRow(cell)) + "\t" +  
  14.                 "列族:" + new String(CellUtil.cloneFamily(cell)) + "\t" +   
  15.                 "列名:" + new String(CellUtil.cloneQualifier(cell)) + "\t" +   
  16.                 "值:" + new String(CellUtil.cloneValue(cell)) + "\t" +  
  17.                 "时间戳:" + cell.getTimestamp());  
  18.     }  
  19.     // 关闭资源  
  20.     table.close();  
  21.     conn.close();  
  22. }  

3.4 全表扫描

  1. // 全表扫描  
  2.     public static void scanTable(String tableName) throws IOException {  
  3.         // 建立一个数据库的连接  
  4.         Connection conn = ConnectionFactory.createConnection(conf);  
  5.         // 获取表  
  6.         HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));  
  7.         // 创建一个扫描对象  
  8.         Scan scan = new Scan();  
  9.         // 扫描全表输出结果  
  10.         ResultScanner results = table.getScanner(scan);  
  11.         for (Result result : results) {  
  12.             for (Cell cell : result.rawCells()) {  
  13.                 System.out.println(  
  14.                         "行键:" + new String(CellUtil.cloneRow(cell)) + "\t" +  
  15.                         "列族:" + new String(CellUtil.cloneFamily(cell)) + "\t" +   
  16.                         "列名:" + new String(CellUtil.cloneQualifier(cell)) + "\t" +   
  17.                         "值:" + new String(CellUtil.cloneValue(cell)) + "\t" +  
  18.                         "时间戳:" + cell.getTimestamp());  
  19.             }  
  20.         }  
  21.         // 关闭资源  
  22.         results.close();  
  23.         table.close();  
  24.         conn.close();  
  25. }  

3.5 删除一条数据

  1. // 删除一条数据  
  2. public static void delRow(String tableName, String rowKey) throws IOException {  
  3.     // 建立一个数据库的连接  
  4.     Connection conn = ConnectionFactory.createConnection(conf);  
  5.     // 获取表  
  6.     HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));  
  7.     // 删除数据  
  8.     Delete delete = new Delete(Bytes.toBytes(rowKey));  
  9.     table.delete(delete);  
  10.     // 关闭资源  
  11.     table.close();  
  12.     conn.close();  
  13. }  

3.6 删除多条数据

  1. // 删除多条数据  
  2. public static void delRows(String tableName, String[] rows) throws IOException {  
  3.     // 建立一个数据库的连接  
  4.     Connection conn = ConnectionFactory.createConnection(conf);  
  5.     // 获取表  
  6.     HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));  
  7.     // 删除多条数据  
  8.     List<Delete> list = new ArrayList<Delete>();  
  9.     for (String row : rows) {  
  10.         Delete delete = new Delete(Bytes.toBytes(row));  
  11.         list.add(delete);  
  12.     }  
  13.     table.delete(list);  
  14.     // 关闭资源  
  15.     table.close();  
  16.     conn.close();  
  17. }  

3.7 删除列族

  1. // 删除列族  
  2. public static void delColumnFamily(String tableName, String columnFamily) throws IOException {  
  3.     // 建立一个数据库的连接  
  4.     Connection conn = ConnectionFactory.createConnection(conf);  
  5.     // 创建一个数据库管理员  
  6.     HBaseAdmin hAdmin = (HBaseAdmin) conn.getAdmin();  
  7.     // 删除一个表的指定列族  
  8.     hAdmin.deleteColumn(tableName, columnFamily);  
  9.     // 关闭资源  
  10.     conn.close();  
  11. }  

3.8 删除数据库表

  1. // 删除数据库表  
  2. public static void deleteTable(String tableName) throws IOException {  
  3.     // 建立一个数据库的连接  
  4.     Connection conn = ConnectionFactory.createConnection(conf);  
  5.     // 创建一个数据库管理员  
  6.     HBaseAdmin hAdmin = (HBaseAdmin) conn.getAdmin();  
  7.     if (hAdmin.tableExists(tableName)) {  
  8.         // 失效表  
  9.         hAdmin.disableTable(tableName);  
  10.         // 删除表  
  11.         hAdmin.deleteTable(tableName);  
  12.         System.out.println("删除" + tableName + "表成功");  
  13.         conn.close();  
  14.     } else {  
  15.         System.out.println("需要删除的" + tableName + "表不存在");  
  16.         conn.close();  
  17.         System.exit(0);  
  18.     }  
  19. } 

mapreduce开发实例

分析MapReduce执行过程

    MapReduce运行的时候,会通过Mapper运行的任务读取HDFS中的数据文件,然后调用自己的方法,处理数据,最后输出。Reducer任务会接收Mapper任务输出的数据,作为自己的输入数据,调用自己的方法,最后输出到HDFS的文件中。整个流程如图:

image

Mapper任务的执行过程详解

每个Mapper任务是一个Java进程,它会读取HDFS中的文件,解析成很多的键值对,经过我们覆盖的map方法处理后,转换为很多的键值对再输出。整个Mapper任务的处理过程又可以分为以下几个阶段,如图所示。

image

在上图中,把Mapper任务的运行过程分为六个阶段。

  1. 第一阶段是把输入文件按照一定的标准分片(InputSplit),每个输入片的大小是固定的。默认情况下,输入片(InputSplit)的大小与数据块(Block)的大小是相同的。如果数据块(Block)的大小是默认值64MB,输入文件有两个,一个是32MB,一个是72MB。那么小的文件是一个输入片,大文件会分为两个数据块,那么是两个输入片。一共产生三个输入片。每一个输入片由一个Mapper进程处理。这里的三个输入片,会有三个Mapper进程处理。

  2. 第二阶段是对输入片中的记录按照一定的规则解析成键值对。有个默认规则是把每一行文本内容解析成键值对。“键”是每一行的起始位置(单位是字节),“值”是本行的文本内容。

  3. 第三阶段是调用Mapper类中的map方法。第二阶段中解析出来的每一个键值对,调用一次map方法。如果有1000个键值对,就会调用1000次map方法。每一次调用map方法会输出零个或者多个键值对。

  4. 第四阶段是按照一定的规则对第三阶段输出的键值对进行分区。比较是基于键进行的。比如我们的键表示省份(如北京、上海、山东等),那么就可以按照不同省份进行分区,同一个省份的键值对划分到一个区中。默认是只有一个区分区的数量就是Reducer任务运行的数量。默认只有一个Reducer任务。

  5. 第五阶段是对每个分区中的键值对进行排序。首先,按照键进行排序,对于键相同的键值对,按照值进行排序。比如三个键值对<2,2>、<1,3>、<2,1>,键和值分别是整数。那么排序后的结果是<1,3>、<2,1>、<2,2>。如果有第六阶段,那么进入第六阶段;如果没有,直接输出到本地的linux文件中。

  6. 第六阶段是对数据进行归约处理,也就是reduce处理。键相等的键值对会调用一次reduce方法。经过这一阶段,数据量会减少。归约后的数据输出到本地的linxu文件中。本阶段默认是没有的,需要用户自己增加这一阶段的代码

Reducer任务的执行过程详解

每个Reducer任务是一个java进程。Reducer任务接收Mapper任务的输出,归约处理后写入到HDFS中,可以分为几个阶段。

  1. 第一阶段是Reducer任务会主动从Mapper任务复制其输出的键值对。Mapper任务可能会有很多,因此Reducer会复制多个Mapper的输出。

  2. 第二阶段是把复制到Reducer本地数据,全部进行合并,即把分散的数据合并成一个大的数据。再对合并后的数据排序。

  3. 第三阶段是对排序后的键值对调用reduce方法。键相等的键值对调用一次reduce方法,每次调用会产生零个或者多个键值对。最后把这些输出的键值对写入到HDFS文件中。

在整个MapReduce程序的开发过程中,我们最大的工作量是覆盖map函数和覆盖reduce函数。


(笔记为所学知识结合网上材料的整理)

0 0
原创粉丝点击