Hadoop

Hadoop 核心概念

什么是 Hadoop

HADOOP是apache旗下的一套开源软件平台HADOOP提供利用服务器集群,根据用户的自定义业务逻辑,对海量数据进行分布式处理,HADOOP的核心组件有:HDFS(分布式文件系统)、YARN(运算资源调度系统)、MAPREDUCE(分布式运算编程框架),广义上来说,HADOOP通常是指一个更广泛的概念——HADOOP生态圈

Hadoop 产生背景

HADOOP最早起源于Nutch。Nutch的设计目标是构建一个大型的全网搜索引擎,包括网页抓取、索引、查询等功能,但随着抓取网页数量的增加,遇到了严重的可扩展性问题——如何解决数十亿网页的存储和索引问题。2003年、2004年谷歌发表的两篇论文为该问题提供了可行的解决方案。——分布式文件系统(GFS),可用于处理海量网页的存储——分布式计算框架MAPREDUCE,可用于处理海量网页的索引计算问题。Nutch的开发人员完成了相应的开源实现HDFS和MAPREDUCE,并从Nutch中剥离成为独立项目HADOOP,到2008年1月,HADOOP成为Apache顶级项目,迎来了它的快速发展期。

HADOOP生态圈以及各组成部分的简介

  • HDFS:分布式文件系统
  • MAPREDUCE:分布式运算程序开发框架
  • HIVE:基于大数据技术(文件系统+运算框架)的SQL数据仓库工具
  • HBASE:基于HADOOP的分布式海量数据库
  • ZOOKEEPER:分布式协调服务基础组件
  • Mahout:基于mapreduce/spark/flink等分布式运算框架的机器学习算法库
  • Oozie:工作流调度框架
  • Sqoop:数据导入导出工具
  • Flume:日志数据采集框架

分布式系统概述

注:由于大数据技术领域的各类技术框架基本上都是分布式系统,因此,理解hadoop、storm、spark等技术框架,都需要具备基本的分布式系统概念

什么是分布式

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据 。分布式系统的特点是:硬件独立,各设备之间独立,互不依赖、 软件统一,对用户来说,就像是跟单个系统打交道。

为什么需要分布式

为了性能扩展:系统负载高,单台机器无法承受,希望通过多台机器来提高系统负载能力 为了增强可靠性:软件不是完美的,网络不是完美的,甚至机器也不是完美的,随时可能出错,为了避免故障,需要将业务分散开保留一定的冗余度

分布式软件系统(Distributed Software Systems)

  1. 该软件系统会划分成多个子系统或模块,各自运行在不同的机器上,子系统或模块之间通过网络通信进行协作,实现最终的整体功能
  2. 比如分布式操作系统、分布式程序设计语言及其编译(解释)系统、分布式文件系统和分布式数据库系统等。

为什么使用分布式

  • 单机处理能力存在瓶颈;
  • 升级单机处理能力的性价比越来越低;
  • 分布式系统稳定性、可用性好

web日志数据挖掘

“Web点击流日志”包含着网站运营很重要的信息,通过日志分析,我们可以知道网站的访问量,哪个网页访问人数最多,哪个网页最有价值,广告转化率、访客的来源信息,访客的终端信息等。

hadoop集群搭建

集群简介

HADOOP集群具体来说包含两个集群:HDFS集群和YARN集群,两者逻辑上分离,但物理上常在一起HDFS集群:负责海量数据的存储,集群中的角色主要有 NameNode / DataNode YARN集群:负责海量数据运算时的资源调度,集群中的角色主要有 ResourceManager / NodeManager (那mapreduce是什么呢?它其实是一个应用程序开发包)

服务器分配
本集群搭建案例,以5节点为例进行搭建,角色分配如下:

节点

进程

node-1

NameNode ResourceManager

node-2

SecondaryNameNode

node-3

DataNode NodeManager

node-4

DataNode NodeManager

node-5

DataNode NodeManager

服务器系统配置

  1. 配置系统网络,安装常用依赖包如文件上传、编辑器、网络工具
  2. 同步时间、配置各主机名称映射、配置ssh免密登陆
  3. 安装java环境

hadoop集群安装部署

# 下载
$ wget https://mirrors.shu.edu.cn/apache/hadoop/common/hadoop-2.7.7/hadoop-2.7.7.tar.gz

# 解压缩
$  tar -zxvf hadoop-2.7.7/hadoop-2.7.7.tar.gz

# 修改系统环境变量
$ vi /etc/profile

export HADOOP_HOME=/export/servers/hadoop2.7.7
export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin


# 配置环境变量
$ vi  hadoop-env.sh

export JAVA_HOME=/usr/local/jdk1.8.0_191

# core-site.xml配置如下
$ vi core-site.xml

<configuration>   
    <property>   
        <name>fs.defaultFS</name>   
        <value>hdfs://node-1:9000</value>   
    </property>   
    <property>   
        <name>hadoop.tmp.dir</name>   
        <value>/export/data/hadoop/tmp</value>
    </property>
</configuration>


# hdfs-site.xml配置如下
$ vi  hdfs-site.xml

<configuration>
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>/export/data/hadoop/name</value>
    </property>
    <property>
        <name>dfs.datanode.data.dir</name>
        <value>/export/data/hadoop/data</value>
    </property>

    <property>
        <name>dfs.replication</name>
        <value>3</value>
    </property>

    <property>
        <name>dfs.secondary.http.address</name>
        <value>node-2:50090</value>
    </property>

</configuration>

# mapred-site.xml配置如下
$ vi mapred-site.xml

<configuration>
    <property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
    </property>
</configuration>


# yarn-site.xml配置如下
$ vi yarn-site.xml

<configuration>

  <property>
        <name>yarn.resourcemanager.hostname</name>
        <value>node-1</value>
    </property>

    <property>
        <name>yarn.nodemanager.aux-services</name>
        <value>mapreduce_shuffle</value>
    </property>

</configuration>

# 修改 slaves 配置
$ vim salves

node-3
node-4
node-5

启动hadoop集群

# 初始化HDFS
$ hadoop namenode -format

# 启动HDFS
$ $HADOOP_HOME/sbin/start-dfs.sh

# 启动yarn
$ $HADOOP_HOME/sbin/start-yarn.sh

# 启动hdfs和yarn
$ $HADOOP_HOME/sbin/start-all.sh

# hadoop常用命令

# 查看hadoop版本
$ hadoop verion

# 运行jar文件
$ hadoop jar

# 创建文件夹
$ hadoop fs -mkdir -p /data/input

# 递归查看文件夹
$ hadoop fs -ls -R /data/input

# 上传本地文件到HDFS
$ hadoop fs -put /root/words.txt  /data/input

# 查看HDFS上的文件
$ hadoop fs -cat /data/input/words.txt

# 删除HDFS上的文件
$ hadoop fs -rm -f /data/input/words.txt

运行一个mapreduce程序

使用hadoop自带的例子程序,运行一个示例mr程序

$ hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.7.jar wordcount /data/input /data/output

集群使用初步

可打开web控制台查看 HDFS 和 yarn 集群信息 hdfs 的 web 页面是 http://node-1:50070/ yarn 集群信息的 web 页面是http://node-1:8088/

mapreduce是hadoop中的分布式运算编程框架,只要按照其编程规范,只需要编写少量的业务逻辑代码即可实现一个强大的海量数据并发处理程序

HDFS详解

HDFS前言

设计思想

  • 分而治之:将大文件、大批量文件,分布式存放在大量服务器上,以便于采取分而治之的方式对海量数据进行运算分析;

在大数据系统中作用:

  • 为各类分布式运算框架(如:mapreduce,spark,tez,……)提供数据存储服务

重点概念

  • 文件切块,副本存放,元数据

HDFS的概念和特性

首先,它是一个文件系统,用于存储文件,通过统一的命名空间——目录树来定位文件

其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色

重要特性如下

  • HDFS中的文件在物理上是分块存储(block),块的大小可以通过配置参数( dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,老版本中是64M
  • HDFS文件系统会给客户端提供一个统一的抽象目录树,客户端通过路径来访问文件,形如:hdfs://host:port/xxx/xxx/file.data
  • 目录结构及文件分块信息(元数据)的管理由namenode节点承担namenode是HDFS集群主节点,负责维护整个hdfs文件系统的目录树,以及每一个路径(文件)所对应的block块信息(block的id,及所在的datanode服务器)
  • 文件的各个 block 的存储管理由datanode节点承担 datanode 是HDFS集群从节点,每一 个block 都可以在多个 datanode 上存储多个副本(副本数量也可以通过参数设置dfs.replication)
  • HDFS是设计成适应一次写入,多次读出的场景,且不支持文件的修改

(注:适合用来做数据分析,并不适合用来做网盘应用,因为,不便修改,延迟大,网络开销大,成本太高)

HDFS常用命令

dfs == hadoop fs

# 在hdfs上创建目录
$ dfs -mkdir /xxx

# 显示目录信息
$ dfs -ls -r /xxx

# 递归查看HDFS目录文件
$ dfs -lsr  /xxx

# 等同于copyFromLocal 
$ dfs -put a.html /xxx

# 等同于copyToLocal,就是从hdfs下载文件到本地
$ dfs -get /xxx/a.html

# 删除文件夹下所有文件
$ dfs -rm -r -f /data

# 显示文件内容
$ dfs -cat  /xxx/a.txt

# 从本地剪切粘贴到hdfs
$ dfs -moveFromLocal

# 从hdfs剪切粘贴到本地
$ dfs -moveToLocal

# 追加一个文件到已经存在的文件末尾
$ dfs -appendToFile

# 显示一个文件的末尾
$ dfs -tail

# 从hdfs的一个路径拷贝hdfs的另一个路径
$ dfs -cp

# 从hdfs的一个路径拷贝hdfs的另一个路径
$ dfs -mv

# 删除文件或文件夹
$ dfs -rm

HSFS原理

  • 概述
  • HDFS集群分为两大角色:NameNode、DataNode (Secondary Namenode)
  • NameNode负责管理整个文件系统的元数据
  • DataNode 负责管理用户的文件数据块
  • 文件会按照固定的大小(blocksize)切成若干块后分布式存储在若干台datanode上
  • 每一个文件块可以有多个副本,并存放在不同的datanode上
  • Datanode会定期向Namenode汇报自身所保存的文件block信息,而namenode则会负责保持文件的副本数量
  • HDFS的内部工作机制对客户端保持透明,客户端请求访问HDFS都是通过向namenode申请来进行
  • HDFS上传文件的步骤
    客户端要向HDFS写数据,首先要跟namenode通信以确认可以写文件并获得接收文件block的datanode,然后,客户端按顺序将文件逐个block传递给相应datanode,并由接收到block的datanode负责向其他datanode复制block的副本
  • 根namenode通信请求上传文件,namenode检查目标文件是否已存在,父目录是否存在
  • namenode返回是否可以上传
  • client请求第一个 block该传输到哪些datanode服务器上
  • namenode返回3个datanode服务器ABC
  • client请求3台dn中的一台A上传数据(本质上是一个RPC调用,建立pipeline),A收到请求会继续调
  • 然后B调用C,将真个pipeline建立完成,逐级返回客户端
  • client开始往A上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位,A收到一个packet就会传给B,B传给C;A每传一个packet会放入一个应答队列等待应答
  • 当一个block传输完成之后,client再次请求namenode上传第二个block的服务器。
  • HDFS 读数据流程
    客户端将要读取的文件路径发送给namenode,namenode获取文件的元信息(主要是block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应datanode逐个获取文件的block并在客户端本地进行数据追加合并从而获得整个文件
  • 跟namenode通信查询元数据,找到文件块所在的datanode服务器
  • 挑选一台datanode(就近原则,然后随机)服务器,请求建立socket流
  • datanode开始发送数据(从磁盘里面读取数据放入流,以packet为单位来做校验)
  • 客户端以packet为单位接收,现在本地缓存,然后写入目标文件

namenode职责

负责客户端请求的响应,元数据的管理(查询,修改)

元数据管理

namenode对数据的管理采用了三种存储形式:
1.内存元数据(NameSystem)
2.磁盘元数据镜像文件
3.数据操作日志文件(可通过日志运算出元数据)

元数据存储机制

  • 内存中有一份完整的元数据(内存meta data)
  • 磁盘有一个“准完整”的元数据镜像(fsimage)文件在 namenode 的工作目录中
  • 用于衔接内存metadata和持久化元数据镜像fsimage之间的操作日志 edits 文件

注:当客户端对hdfs中的文件进行新增或者修改操作,操作记录首先被记入edits日志文件中,当客户端操作成功后,相应的元数据会更新到内存meta.data中

元数据手动查看

  • 可以通过hdfs的一个工具来查看edits中的信息
  • bin/hdfs oev -i edits -o edits.xml
  • bin/hdfs oiv -i fsimage_0000000000000000087 -p XML -o fsimage.xml

元数据的checkpoint

每隔一段时间,会由secondary namenode将namenode上积累的所有edits和一个最新的fsimage下载到本地,并加载到内存进行merge(这个过程称为checkpoint)

元数据目录说明

在第一次部署好Hadoop集群的时候,我们需要在NameNode(NN)节点上格式化磁盘:

$ $HADOOP_HOME/bin/hdfs namenode -format

格式化完成之后,将会在$dfs.namenode.name.dir/current目录下如下的文件结构

current/
|-- VERSION
|-- edits_*
|-- fsimage_0000000000008547077
|-- fsimage_0000000000008547077.md5
|-- seen_txid

DATANODE的工作机制

  • Datanode工作职责:

存储管理用户的文件块数据,定期向namenode汇报自身所持有的block信息(通过心跳信息上报,这点很重要,因为,当集群中发生某些block副本失效时,集群如何恢复block初始副本数量的问题)

<property>
    <name>dfs.blockreport.intervalMsec</name>
    <value>3600000</value>
</property>
  • Datanode掉线判断时限参数

datanode进程死亡或者网络故障造成datanode无法与namenode通信,namenode不会立即把该节点判定为死亡,要经过一段时间,这段时间暂称作超时时长。HDFS默认的超时时长为10分钟+30秒。如果定义超时时间为timeout,则超时时长的计算公式为:timeout = 2 * heartbeat.recheck.interval + 10 * dfs.heartbeat.interval。

而默认的heartbeat.recheck.interval 大小为5分钟,dfs.heartbeat.interval默认为3秒。
需要注意的是hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒。所以,举个例子,如果heartbeat.recheck.interval设置为5000(毫秒),dfs.heartbeat.interval设置为3(秒,默认),则总的超时时间为40秒。

<property>
    <name>heartbeat.recheck.interval</name>
    <value>2000</value>
</property>
<property>
    <name>dfs.heartbeat.interval</name>
    <value>1</value>
</property>

验证DATANODE功能

上传一个文件,观察文件的block具体的物理存放情况:

在每一台datanode机器上的这个目录中能找到文件的切块:
/export/data/hadoop/tmp/dfs/data/current/BP-193442119-192.168.2.120-1432457733977/current/finalized

hadoop hdfs java客户端

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.net.URI;

/**
 * <p>
 *
 * @author leone
 * @since 2018-05-06
 **/
public class HdfsClientTest {

    FileSystem fs = null;

    /**
     * 初始化
     */
    @Before
    public void init() throws Exception {
        Configuration conf = new Configuration();
        fs = FileSystem.get(new URI("hdfs://node-1:9000/"), conf, "root");
    }

    /**
     * 上传文件
     *
     * @throws IOException
     */
    @Test
    public void putTest() throws IOException {
        fs.copyFromLocalFile(new Path("file:///D:/tmp/hadoop/input1/words.txt"),
                new Path("hdfs://node-1:9000/hadoop-2.7.7/input1/words.txt"));
    }


    /**
     * 下载文件
     *
     * @throws Exception
     * @throws IOException
     */
    @Test
    public void download() throws Exception, IOException {
        fs.copyToLocalFile(new Path("/hadoop-2.7.7/input1/words.txt"), new Path("file:///E:/hadoop/words.txt"));
    }


    /**
     * 创建目录
     *
     * @throws IllegalArgumentException
     * @throws IOException
     */
    @Test
    public void mkdirTest() throws IllegalArgumentException, IOException {
        fs.mkdirs(new Path("/test/input/"));
    }

    /**
     * 删除文件或目录
     */
    @Test
    public void deleteTest() throws IOException {
        fs.delete(new Path("/test"), true);
    }

    /**
     * 移动文件
     *
     * @throws IllegalArgumentException
     * @throws IOException
     */
    @Test
    public void mvFileTest() throws IllegalArgumentException, IOException {
        fs.rename(new Path("/hadoop-2.7.7/input1/words.txt"), new Path("/hadoop-2.7.7/input3/words.txt"));
    }

}