• hadoop之HDFS学习笔记(一)


    主要内容:hdfs的整体运行机制,DATANODE存储文件块的观察,hdfs集群的搭建与配置,hdfs命令行客户端常见命令;业务系统中日志生成机制,HDFS的java客户端api基本使用。

    1、什么是大数据

    基本概念

    《数据处理》

    在互联网技术发展到现今阶段,大量日常、工作等事务产生的数据都已经信息化,人类产生的数据量相比以前有了爆炸式的增长,以前的传统的数据处理技术已经无法胜任,需求催生技术,一套用来处理海量数据的软件工具应运而生,这就是大数据!

    处理海量数据的核心技术:

    海量数据存储:分布式

    海量数据运算:分布式

    大数据的海量数据的存储和运算,核心技术就是分布式。

    这些核心技术的实现是不需要用户从零开始造轮子的

    存储和运算,都已经有大量的成熟的框架来用

    存储框架:

    HDFS——分布式文件存储系统(HADOOP中的存储框架)

    HBASE——分布式数据库系统

    KAFKA——分布式消息缓存系统(实时流式数据处理场景中应用广泛)

    文件系统中的数据以非结构化居多,没有直观的结构,数据库中的信息多以表的形式存在,具有结构化,存在规律;

    查询的时候文本文件只能一行一行扫描,而数据库效率高很多,可以利用sql查询语法,数据库在存和取方便的多。

    数据库和文件系统相比,数据库相当于在特定的文件系统上的软件封装。其实HBASE就是对HDFS的进一层封装,它的底层文件系统就是HDFS。

    分布式消息缓存系统,既然是分布式,那就意味着横跨很多机器,意味着容量可以很大。和前两者相比它的数据存储形式是消息(不是表,也不是文件),消息可以看做有固定格式的一条数据,比如消息头,消息体等,消息体可以是json,数据库的一条记录,一个序列化对象等。消息最终存放在kafaka内部的特定的文件系统里。

    运算框架:(要解决的核心问题就是帮用户将处理逻辑在很多机器上并行

    MAPREDUCE—— 离线批处理/HADOOP中的运算框架

    SPARK —— 离线批处理/实时流式计算

    STORM —— 实时流式计算

    离线批处理:数据是静态的,一次处理一大批数据。

    实时流式:数据在源源不断的生成,边生成,边计算

    这些运算框架的思想都差不多,特别是mapreduce和spark,简单来看spark是对mapreduce的进一步封装;

    运算框架和存储框架之间没有强耦合关系,spark可以读HDFS,HBASE,KAFKA里的数据,当然需要存储框架提供访问接口。

    辅助类的工具(解放大数据工程师的一些繁琐工作):

    HIVE —— 数据仓库工具:可以接收sql,翻译成mapreduce或者spark程序运行

    FLUME——数据采集

    SQOOP——数据迁移

    ELASTIC SEARCH —— 分布式的搜索引擎

    flume用于自动采集数据源机器上的数据到大数据集群中。

    HIVE看起来像一个数据库,但其实不是,Hive中存了一些需要分析的数据,然后在直接写sql进行分析,hive接收sql,翻译成mapreduce或者spark程序运行;

    hive本质是mapreduce或spark,我们只需要写sql逻辑而不是mapreduce逻辑,Hive自动完成对sql的翻译,而且还是在海量数据集上。

    .......

    换个角度说,大数据是:

    1、有海量的数据

    2、有对海量数据进行挖掘的需求

    3、有对海量数据进行挖掘的软件工具(hadoop、spark、storm、flink、tez、impala......)

    大数据在现实生活中的具体应用

    数据处理的最典型应用:公司的产品运营情况分析

    电商推荐系统:基于海量的浏览行为、购物行为数据,进行大量的算法模型的运算,得出各类推荐结论,以供电商网站页面来为用户进行商品推荐

    精准广告推送系统:基于海量的互联网用户的各类数据,统计分析,进行用户画像(得到用户的各种属性标签),然后可以为广告主进行有针对性的精准的广告投放

    2、什么是hadoop

    hadoop中有3个核心组件:

    分布式文件系统:HDFS —— 实现将文件分布式存储在很多的服务器上

    分布式运算编程框架:MAPREDUCE —— 实现在很多机器上分布式并行运算

    分布式资源调度平台:YARN —— 帮用户调度大量的mapreduce程序,并合理分配运算资源

    3、hdfs整体运行机制

    hdfs:分布式文件系统

    hdfs有着文件系统共同的特征:

    1、有目录结构,顶层目录是:  /

    2、系统中存放的就是文件

    3、系统可以提供对文件的:创建、删除、修改、查看、移动等功能

    hdfs跟普通的单机文件系统有区别:

    1、单机文件系统中存放的文件,是在一台机器的操作系统中

    2、hdfs的文件系统会横跨N多的机器

    3、单机文件系统中存放的文件,是在一台机器的磁盘上

    4、hdfs文件系统中存放的文件,是落在n多机器的本地单机文件系统中(hdfs是一个基于linux本地文件系统之上的文件系统)

    hdfs的工作机制:

    1、客户把一个文件存入hdfs,其实hdfs会把这个文件切块后,分散存储在N台linux机器系统中(负责存储文件块的角色:data node)<准确来说:切块的行为是由客户端决定的>

    2、一旦文件被切块存储,那么,hdfs中就必须有一个机制,来记录用户的每一个文件的切块信息,及每一块的具体存储机器(负责记录块信息的角色是:name node)

    3、为了保证数据的安全性,hdfs可以将每一个文件块在集群中存放多个副本(到底存几个副本,是由当时存入该文件的客户端指定的)

    综述:一个hdfs系统,由一台运行了namenode的服务器,和N台运行了datanode的服务器组成!

    4、搭建hdfs分布式集群

    4.1 hdfs集群组成结构:

    4.2 安装hdfs集群的具体步骤:

    4.2.1、首先需要准备N台linux服务器

    学习阶段,用虚拟机即可!

    先准备4台虚拟机:1个namenode节点  + 3 个datanode 节点

    4.2.2、修改各台机器的主机名和ip地址

    主机名:hdp-01  对应的ip地址:192.168.33.11

    主机名:hdp-02  对应的ip地址:192.168.33.12

    主机名:hdp-03  对应的ip地址:192.168.33.13

    主机名:hdp-04  对应的ip地址:192.168.33.14

    4.2.3、从windows中用CRT软件进行远程连接

    在windows中将各台linux机器的主机名配置到的windows的本地域名映射文件中:

    c:/windows/system32/drivers/etc/hosts

    192.168.33.11 hdp-01

    192.168.33.12 hdp-02

    192.168.33.13 hdp-03

    192.168.33.14 hdp-04

    用crt连接上后,修改一下crt的显示配置(字号,编码集改为UTF-8):

    4.2.3、配置linux服务器的基础软件环境

    •  防火墙

    关闭防火墙:service iptables stop 

    关闭防火墙自启: chkconfig iptables off

    •  安装jdk:(hadoop体系中的各软件都是java开发的)

      1)        利用alt+p 打开sftp窗口,然后将jdk压缩包拖入sftp窗口

      2)         然后在linux中将jdk压缩包解压到/root/apps 下

      3)         配置环境变量:JAVA_HOME   PATH

      vi /etc/profile   在文件的最后,加入:

    export JAVA_HOME=/root/apps/jdk1.8.0_60
    
    export PATH=$PATH:$JAVA_HOME/bin

      4)         修改完成后,记得 source /etc/profile使配置生效

      5)         检验:在任意目录下输入命令: java -version 看是否成功执行

      6)         将安装好的jdk目录用scp命令拷贝到其他机器

      7)         将/etc/profile配置文件也用scp命令拷贝到其他机器并分别执行source命令

    •   集群内主机的域名映射配置

    在hdp-01上,vi /etc/hosts

    127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4

    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

    192.168.33.11   hdp-01

    192.168.33.12   hdp-02

    192.168.33.13   hdp-03

    192.168.33.14   hdp-04

    然后,将hosts文件拷贝到集群中的所有其他机器上

    scp /etc/hosts hdp-02:/etc/

    scp /etc/hosts hdp-03:/etc/

    scp /etc/hosts hdp-04:/etc/

    提示:

    如果在执行scp命令的时候,提示没有scp命令,则可以配置一个本地yum源来安装

    1、先在虚拟机中配置cdrom为一个centos的安装镜像iso文件

    2、在linux系统中将光驱挂在到文件系统中(某个目录)

    3、mkdir /mnt/cdrom

    4、mount -t iso9660 -o loop /dev/cdrom /mnt/cdrom

    5、检验挂载是否成功: ls /mnt/cdrom

    6、3、配置yum的仓库地址配置文件

    7、yum的仓库地址配置文件目录: /etc/yum.repos.d

    8、先将自带的仓库地址配置文件批量更名:

     

    9、然后,拷贝一个出来进行修改

     

     

    10、修改完配置文件后,再安装scp命令:

    11、yum install openssh-clients -y

    4.2.4、安装hdfs集群

    1、上传hadoop安装包到hdp-01

    bin文件为hadoop功能命令,sbin中为集群管理命令。

    2、修改配置文件

    核心配置参数:

    1)         指定hadoop的默认文件系统为:hdfs

    2)         指定hdfs的namenode节点为哪台机器

    3)         指定namenode软件存储元数据的本地目录

    4)         指定datanode软件存放文件块的本地目录

    hadoop的配置文件在:/root/apps/hadoop安装目录/etc/hadoop/

    hadoop中的其他组件如mapreduce,yarn等,这些组将会去读数据,指定hadoop的默认文件系统为:hdfs,就是告诉这些组件去hdfs中读数据;该项配置意味dadoop中的组件可以访问各种文件系统。

    若不指定数据的存放目录,hadoop默认将数据存放在/temp下。

    可以参考官网的默认配置信息。

    1) 修改hadoop-env.sh
    export JAVA_HOME=/root/apps/jdk1.8.0_60
    2) 修改core-site.xml
    <configuration>
    
    <property>
    
    <name>fs.defaultFS</name>
    
    <value>hdfs://hdp-01:9000/</value>
    
    </property>
    
    </configuration>

    <value>hdfs://hdp-01:9000</value>包含两层意思:

      1、指定默认的文件系统。

      2、指明了namenode是谁。

    value中的值是URI风格

    3) 修改hdfs-site.xml

    配置namenode和datanode的工作目录,添加secondary name node。

    <configuration>
    
    <property>
    
    <name>dfs.namenode.name.dir</name>
    
    <value>/root/hdpdata/name/</value>
    
    </property>
    
     
    
    <property>
    
    <name>dfs.datanode.data.dir</name>
    
    <value>/root/hdpdata/data</value>
    
    </property>
    
     
    
    <property>
    
    <name>dfs.namenode.secondary.http-address</name>
    
    <value>hdp-02:50090</value>
    
    </property>
    
     
    
    </configuration>
    4) 拷贝整个hadoop安装目录到其他机器 
      scp -r /root/apps/hadoop-2.8.1  hdp-02:/root/apps/
    
      scp -r /root/apps/hadoop-2.8.1  hdp-03:/root/apps/
    
      scp -r /root/apps/hadoop-2.8.1  hdp-04:/root/apps/
    5) 启动HDFS

    所谓的启动HDFS,就是在对的机器上启动对的软件

    提示:

    要运行hadoop的命令,需要在linux环境中配置HADOOP_HOME和PATH环境变量

    vi /etc/profile

    export JAVA_HOME=/root/apps/jdk1.8.0_60 export HADOOP_HOME=/root/apps/hadoop-2.8.1 export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

    首先,初始化namenode的元数据目录

    要在hdp-01上执行hadoop的一个命令来初始化namenode的元数据存储目录

    hadoop namenode -format

      创建一个全新的元数据存储目录

      生成记录元数据的文件fsimage

      生成集群的相关标识:如:集群id——clusterID

    该步骤叫做namenode的初始化也叫格式化,本质是建立namenode运行所需要的目录以及一些必要的文件,所以该操作一般只在集群第一次启动之前执行。

    然后,启动namenode进程(在hdp-01上)

    hadoop-daemon.sh start namenode

    启动完后,首先用jps查看一下namenode的进程是否存在

    namenode就是一个java软件,我们知道启动一个java软件需要主类的main方法 java xxx.java - 若干参数,处于方便的考虑,hadoop中提供了一个通用的软件启动脚本hadoop-daemon.sh,脚本可以接受参数,专门用来启动hadoop中的软件。

     

    可以看到namenode在监听两个端口,9000用来和客户端通信(9000为RPC端口号,内部进程之间互相通信的端口,datanode和namenode的通信),接受hdfs客户端的请求,50070是web服务端口,也就是说namenode内置一个web服务器,http客户端可以通过次端口发送请求。

    然后,在windows中用浏览器访问namenode提供的web端口:50070

    http://hdp-01:50070

    然后,启动众datanode们(在任意地方)

    hadoop-daemon.sh start datanode

    下图是datanode的一下信息展示,可以看到datanode内部通信的端口号是50010,而且datanode也提供了问访问端口50075.

    6) 用自动批量启动脚本来启动HDFS

    hdfs其实就是一堆java软件,我们可以自己手动hadoop-daemon.sh逐个启动,也可以使用hadoop提供的批量启动脚本。

    1)         先配置hdp-01到集群中所有机器(包含自己)的免密登陆

    2)         配完免密后,可以执行一次  ssh 0.0.0.0

    3)         修改hadoop安装目录中/etc/hadoop/slaves(把需要启动datanode进程的节点列入)

    hdp-01

    hdp-02

    hdp-03

    hdp-04

    core-site.xml中配置过namenode,但是需要批量启动那些datanode呢,该文件/etc/hadoop/slaves的配置就是解决这个问题的,该文件就是给启动脚本看的。

    4)         在hdp-01上用脚本:start-dfs.sh 来自动启动整个集群

    5)         如果要停止,则用脚本:stop-dfs.sh

    start-dfs.sh、stop-dfs.sh会启动、关闭namenode,datanode和secondnamenode

    当然你也可以自己写脚本来做上述的事情 ,如下所示。

     

    5、hdfs的客户端操作

    hdfs装好之后,接下来的工作就是hdfs里传东西,去东西,由客户端来完成。

    5.1、客户端的理解

    hdfs的客户端有多种形式:

    1、网页形式

    2、命令行形式

    3、客户端在哪里运行,没有约束,只要运行客户端的机器能够跟hdfs集群联网们

       对于客户端来讲,hdfs是一个整体,网页版的客户端主要是用来查看hdfs信息的,可以创建目录,但是需要权限

    命令行客户端 

    bin命令中的 hadoophdfs  都可以启动 hdfs 客户端,hadoop和hdfs都是脚本,都会去启动一个hdfs的java客户端。java客户端在安装包的jar包中

    ./hadoop fs -ls /

    表示hadoop要访问hdfs,该脚本就会去启动hdfs客户端,客户端可以接收参数,比如查看hdfs根目录。

     

    文件的切块大小和存储的副本数量,都是由客户端决定!

    所谓的由客户端决定,是通过配置参数来定的

    hdfs的客户端会读以下两个参数,来决定切块大小(默认128M)、副本数量(默认3):

    切块大小的参数: dfs.blocksize

    副本数量的参数: dfs.replication

    如果使用命令行客户端时,上面两个参数应该配置在客户端机器的hadoop目录中的hdfs-site.xml中配置,(命令行客户端本质就是启动了一个java客户端,这个客户端在启动的时候会将它依赖的所有jar包加入classpath中,客户端会从jar包中,加载xx-default.xml来获得默认的配置文件,也可以在hadoop/etc/xxx-site.xml中配置具体的参数来覆盖默认值。此时的/etc下的配置文件就是客户自定义的配置文件,也会被java客户端加载【客户端可以运行在任何地方】);

    当然也可以在具体代码中指定,见6节中的核心代码

    <property>
    <name>dfs.blocksize</name>
    <value>64m</value>
    </property>
    
    <property>
    <name>dfs.replication</name>
    <value>2</value>
    </property>

     5.1.1、上传过程

    下图为datanode中的数据,.meta是该block的校验和信息。我们可以通过linux cat命令将两个块合并,会发现与原来的文件是一样的。

     

     5.1.2、下载过程

    客户端首先回去namenode上去查找,有没有请求的hdfs路径下的文件,有的话都有该文件被切割成几块,每块有几个副本,这些副本都存放在集群中的哪些机器上,然后去存放了第一块数据的某一台机器上去下载第一块数据,将数据追加到本地,然后去下载下一块数据,继续追加到本地文件,知道下载完所有的块。

     

    5.2、hdfs客户端的常用操作命令

    1、上传文件到hdfs中

    hadoop fs -put /本地文件  /aaa

    2、下载文件到客户端本地磁盘

    hadoop fs -get /hdfs中的路径   /本地磁盘目录

    3、在hdfs中创建文件夹

    hadoop fs -mkdir  -p /aaa/xxx

    4、移动hdfs中的文件(更名)

    hadoop fs -mv /hdfs的路径1  /hdfs的另一个路径2

    复制hdfs中的文件到hdfs的另一个目录

    hadoop fs -cp /hdfs路径_1  /hdfs路径_2

    5、删除hdfs中的文件或文件夹

    hadoop fs -rm -r /aaa

    6、查看hdfs中的文本文件内容

    hadoop fs -cat /demo.txt

    hadoop fs -tail /demo.txt

    hadoop fs -tail -f /demo.txt

    hadoop fs -text /demo.txt

    7、查看hdfs目录下有哪些文件

    hadoop fs –ls /

    8、追加本地文件到hdfs中的文件

    hadoop fs -appendToFile 本地路径 /hdfs路径

    9、权限修改

     hadoop fs -chmod username1:usergroup1 /hdfs路径

    要说明的是,hdfs中的用户和用户组这是一个名字称呼,与linux不一样,linux中不能将选线分配给一个不存在的用户。

     可以查看hadoop fs 不带任何参数,来查看hdfs所支持的命令

    Usage: hadoop fs [generic options]
            [-appendToFile <localsrc> ... <dst>]
            [-cat [-ignoreCrc] <src> ...]
            [-checksum <src> ...]
            [-chgrp [-R] GROUP PATH...]
            [-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
            [-chown [-R] [OWNER][:[GROUP]] PATH...]
            [-copyFromLocal [-f] [-p] [-l] [-d] <localsrc> ... <dst>]
            [-copyToLocal [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
            [-count [-q] [-h] [-v] [-t [<storage type>]] [-u] [-x] <path> ...]
            [-cp [-f] [-p | -p[topax]] [-d] <src> ... <dst>]
            [-createSnapshot <snapshotDir> [<snapshotName>]]
            [-deleteSnapshot <snapshotDir> <snapshotName>]
            [-df [-h] [<path> ...]]
            [-du [-s] [-h] [-x] <path> ...]
            [-expunge]
            [-find <path> ... <expression> ...]
            [-get [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
            [-getfacl [-R] <path>]
            [-getfattr [-R] {-n name | -d} [-e en] <path>]
            [-getmerge [-nl] [-skip-empty-file] <src> <localdst>]
            [-help [cmd ...]]
            [-ls [-C] [-d] [-h] [-q] [-R] [-t] [-S] [-r] [-u] [<path> ...]]
            [-mkdir [-p] <path> ...]
            [-moveFromLocal <localsrc> ... <dst>]
            [-moveToLocal <src> <localdst>]
            [-mv <src> ... <dst>]
            [-put [-f] [-p] [-l] [-d] <localsrc> ... <dst>]
            [-renameSnapshot <snapshotDir> <oldName> <newName>]
            [-rm [-f] [-r|-R] [-skipTrash] [-safely] <src> ...]
            [-rmdir [--ignore-fail-on-non-empty] <dir> ...]
            [-setfacl [-R] [{-b|-k} {-m|-x <acl_spec>} <path>]|[--set <acl_spec> <path>]]
            [-setfattr {-n name [-v value] | -x name} <path>]
            [-setrep [-R] [-w] <rep> <path> ...]
            [-stat [format] <path> ...]
            [-tail [-f] <file>]
            [-test -[defsz] <path>]
            [-text [-ignoreCrc] <src> ...]
            [-touchz <path> ...]
            [-truncate [-w] <length> <path> ...]
            [-usage [cmd ...]]

    6、hdfs的java客户端编程

    HDFS客户端编程应用场景:数据采集

    业务系统中日志生成机制

    数据采集程序其实就是通过对java客户端编程,将数据不断的上传到hdfs。

    在windows开发环境中做一些准备工作:

    1、在windows的某个路径中解压一份windows版本的hadoop安装包

    2、将解压出的hadoop目录配置到windows的环境变量中:HADOOP_HOME

    原因:若不配置环境变量,会在下载hdfs文件是出错,是由于使用hadoop的FileSystem保存文件到本地的时候出于效率的考虑,会使用hadoop安装包中的c语言库,显然没有配置hadoop环境变量时是找不到该c语言类库中的文件的;然而上传文件到hdfs没有类似问题;

     6.1、核心代码

    1、将hdfs客户端开发所需的jar导入工程(jar包可在hadoop安装包中找到common和hdfs)

    2、写代码

    6.1.1、获取hdfs客户端

    要点:要对hdfs中的文件进行操作,代码中首先需要获得一个hdfs的客户端对象

    Configuration conf = new Configuration();
    
    FileSystem fs = FileSystem.get(new URI("hdfs://hdp-01:9000"),conf,"root");

     完整代码如下:

    /**
             * Configuration参数对象的机制:
             *    构造时,会加载jar包中的默认配置 xx-default.xml(core-default.xmlhdfs-default.xml)
             *    再加载 用户配置xx-site.xml  ,覆盖掉默认参数
             *    构造完成之后,还可以conf.set("p","v"),会再次覆盖用户配置文件中的参数值
             */
            // new Configuration()会从项目的classpath中加载core-default.xml hdfs-default.xml core-site.xml hdfs-site.xml等文件
            Configuration conf = new Configuration();
            
            // 指定本客户端上传文件到hdfs时需要保存的副本数为:2
            conf.set("dfs.replication", "2");
            // 指定本客户端上传文件到hdfs时切块的规格大小:64M
            conf.set("dfs.blocksize", "64m");
            
            // 构造一个访问指定HDFS系统的客户端对象: 参数1:——HDFS系统的URI,参数2:——客户端要特别指定的参数,参数3:客户端的身份(用户名)
            FileSystem fs = FileSystem.get(new URI("hdfs://hdp-01:9000/"), conf, "root");
            
            // 上传一个文件到HDFS中
            fs.copyFromLocalFile(new Path("D:/install-pkgs/hbase-1.2.1-bin.tar.gz"), new Path("/aaa/"));
            
            fs.close();
    View Code

     6.1.2、对文件进行操作

    上传、下载文件;文件夹的创建和删除、文件的移动和复制、查看文件夹和文件等。

    3、利用fs对象的方法进行文件操作

    方法均与命令行方法对应,比如:

    上传文件

    fs.copyFromLocalFile(new Path("本地路径"),new Path("hdfs的路径"));

    下载文件

    fs.copyToLocalFile(new Path("hdfs的路径"),new Path("本地路径"))

     对文件的增删改查如下,对文件数据的操作后续介绍。

    FileSystem fs = null;
        
        @Before
        public void init() throws Exception{
            Configuration conf = new Configuration();
            conf.set("dfs.replication", "2");
            conf.set("dfs.blocksize", "64m");
            
            fs = FileSystem.get(new URI("hdfs://hdp-01:9000/"), conf, "root");
            
        }
        
        
        /**
         * 从HDFS中下载文件到客户端本地磁盘
         * @throws IOException 
         * @throws IllegalArgumentException 
         */
        @Test
        public void testGet() throws IllegalArgumentException, IOException{
            
            fs.copyToLocalFile(new Path("/hdp20-05.txt"), new Path("f:/"));
            fs.close();
            
        }
        
        
        /**
         * 在hdfs内部移动文件修改名称
         */
        @Test
        public void testRename() throws Exception{
            
            fs.rename(new Path("/install.log"), new Path("/aaa/in.log"));
            
            fs.close();
            
        }
        
        /**
         * 在hdfs中创建文件夹
         */
        @Test
        public void testMkdir() throws Exception{
            
            fs.mkdirs(new Path("/xx/yy/zz"));
            
            fs.close();
        }
        
        
        /**
         * 在hdfs中删除文件或文件夹
         */
        @Test
        public void testRm() throws Exception{
            
            fs.delete(new Path("/aaa"), true);
            
            fs.close();
        }
        
        
        
        /**
         * 查询hdfs指定目录下的文件信息
         */
        @Test
        public void testLs() throws Exception{
            // 只查询文件的信息,不返回文件夹的信息
            RemoteIterator<LocatedFileStatus> iter = fs.listFiles(new Path("/"), true);
            
            while(iter.hasNext()){
                LocatedFileStatus status = iter.next();
                System.out.println("文件全路径:"+status.getPath());
                System.out.println("块大小:"+status.getBlockSize());
                System.out.println("文件长度:"+status.getLen());
                System.out.println("副本数量:"+status.getReplication());
                System.out.println("块信息:"+Arrays.toString(status.getBlockLocations()));
                
                System.out.println("--------------------------------");
            }
            fs.close();
        }
        
        /**
         * 查询hdfs指定目录下的文件和文件夹信息
         */
        @Test
        public void testLs2() throws Exception{
            FileStatus[] listStatus = fs.listStatus(new Path("/"));
            
            for(FileStatus status:listStatus){
                System.out.println("文件全路径:"+status.getPath());
                System.out.println(status.isDirectory()?"这是文件夹":"这是文件");
                System.out.println("块大小:"+status.getBlockSize());
                System.out.println("文件长度:"+status.getLen());
                System.out.println("副本数量:"+status.getReplication());
                
                System.out.println("--------------------------------");
            }
            fs.close();
        }
    View Code

     6.1.3、对文件数据进行操作

     同过客户端使用open打开流对象来读取hdfs中文件的具体数据,包括指定偏移量来读取特定范围的数据;通过客户端向hdfs文件追加数据。

    /**
         * 读取hdfs中的文件的内容
         * 
         * @throws IOException
         * @throws IllegalArgumentException
         */
        @Test
        public void testReadData() throws IllegalArgumentException, IOException {
    
            FSDataInputStream in = fs.open(new Path("/test.txt"));
    
            BufferedReader br = new BufferedReader(new InputStreamReader(in, "utf-8"));
    
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
    
            br.close();
            in.close();
            fs.close();
    
        }
    
        /**
         * 读取hdfs中文件的指定偏移量范围的内容
         * 
         * 
         * 作业题:用本例中的知识,实现读取一个文本文件中的指定BLOCK块中的所有数据
         * 
         * @throws IOException
         * @throws IllegalArgumentException
         */
        @Test
        public void testRandomReadData() throws IllegalArgumentException, IOException {
    
            FSDataInputStream in = fs.open(new Path("/xx.dat"));
    
            // 将读取的起始位置进行指定
            in.seek(12);
    
            // 读16个字节
            byte[] buf = new byte[16];
            in.read(buf);
    
            System.out.println(new String(buf));
    
            in.close();
            fs.close();
    
        }
    View Code

     写数据,create提供了丰富的重载函数,轻松实现覆盖,追加,以及指定缓存大小,副本数量等等信息。

    /**
         * 往hdfs中的文件写内容
         * 
         * @throws IOException
         * @throws IllegalArgumentException
         */
    
        @Test
        public void testWriteData() throws IllegalArgumentException, IOException {
    
            FSDataOutputStream out = fs.create(new Path("/zz.jpg"), false);
    
            // D:images06l0mbogy1fhehjb6ikoj30ku0ku76b.jpg
    
            FileInputStream in = new FileInputStream("D:/images/006l0mbogy1fhehjb6ikoj30ku0ku76b.jpg");
    
            byte[] buf = new byte[1024];
            int read = 0;
            while ((read = in.read(buf)) != -1) {
                out.write(buf,0,read);
            }
            
            in.close();
            out.close();
            fs.close();
    
        }
    View Code

    7、HDFS实例

    hdfs版本wordcount程序。

    任务描述:

    1、从hdfs文件中读取数据,每次读取一行数据;

    2、将数据交给具体的单词统计业务去作业(使用面向接口编程,当业务逻辑改变时,无需修改主程序代码);

    3、并将该行数据产生的结果存入缓存中(可以用hashmap模拟)

    数据采集设计:

    1、流程
    启动一个定时任务:
    ——定时探测日志源目录
    ——获取需要采集的文件
    ——移动这些文件到一个待上传临时目录
    ——遍历待上传目录中各文件,逐一传输到HDFS的目标路径,同时将传输完成的文件移动到备份目录

    启动一个定时任务:
    ——探测备份目录中的备份数据,检查是否已超出最长备份时长,如果超出,则删除


    2、规划各种路径
    日志源路径: d:/logs/accesslog/
    待上传临时目录: d:/logs/toupload/
    备份目录: d:/logs/backup/日期/

    HDFS存储路径: /logs/日期
    HDFS中的文件的前缀:access_log_
    HDFS中的文件的后缀:.log

    将路径配置写入属性文件

    MAPPER_CLASS=cn.edu360.hdfs.wordcount.CaseIgnorWcountMapper
    INPUT_PATH=/wordcount/input
    OUTPUT_PATH=/wordcount/output2
    View Code

    主程序代码示例:

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.URI;
    import java.util.HashMap;
    import java.util.Map.Entry;
    import java.util.Properties;
    import java.util.Set;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FSDataInputStream;
    import org.apache.hadoop.fs.FSDataOutputStream;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.LocatedFileStatus;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.fs.RemoteIterator;
    
    public class HdfsWordcount {
        
        public static void main(String[] args) throws Exception{
            
            /**
             * 初始化工作
             */
            Properties props = new Properties();
            props.load(HdfsWordcount.class.getClassLoader().getResourceAsStream("job.properties"));
            
            Path input = new Path(props.getProperty("INPUT_PATH"));
            Path output = new Path(props.getProperty("OUTPUT_PATH"));
            
            
            
            Class<?> mapper_class = Class.forName(props.getProperty("MAPPER_CLASS"));
            Mapper mapper = (Mapper) mapper_class.newInstance();
            
            Context context  =  new Context();
            
            /**
             * 处理数据
             */
            
            FileSystem fs = FileSystem.get(new URI("hdfs://hdp-01:9000"), new Configuration(), "root");
            RemoteIterator<LocatedFileStatus> iter = fs.listFiles(input, false);
            
            while(iter.hasNext()){
                LocatedFileStatus file = iter.next();
                FSDataInputStream in = fs.open(file.getPath());
                BufferedReader br = new BufferedReader(new InputStreamReader(in));
                String line = null;
                // 逐行读取
                while ((line = br.readLine()) != null) {
                    // 调用一个方法对每一行进行业务处理
                    mapper.map(line, context);
                    
                }
                
                br.close();
                in.close();
                
            }
            
        
            
            
            
            /**
             * 输出结果
             */
            HashMap<Object, Object> contextMap = context.getContextMap();
            
            if(fs.exists(output)){
                throw new RuntimeException("指定的输出目录已存在,请更换......!");
            }
            
            
            FSDataOutputStream out = fs.create(new Path(output,new Path("res.dat")));
            
            Set<Entry<Object, Object>> entrySet = contextMap.entrySet();
            for (Entry<Object, Object> entry : entrySet) {
                out.write((entry.getKey().toString()+"	"+entry.getValue()+"
    ").getBytes());
            }
            
            out.close();
            
            fs.close();
            
            
            System.out.println("恭喜!数据统计完成.....");
            
        }
        
        
        
    
    }
    View Code

    自定义的业务接口

    public interface Mapper {
        
        
        public void map(String line,Context context);
        
    
    }
    View Code

    业务实现类1

    public class WordCountMapper implements Mapper{
    
        @Override
        public void map(String line, Context context) {
            
            String[] words = line.split(" ");
            
            for (String word : words) {
                
                Object value = context.get(word);
                if(null==value){
                    context.write(word, 1);
                    
                }else{
                    int v = (int)value;
                    context.write(word, v+1);
                }    
                
            }
        }
    }
    View Code

    业务实现类2

    public class CaseIgnorWcountMapper implements Mapper {
    
        @Override
        public void map(String line, Context context) {
    
            String[] words = line.toUpperCase().split(" ");
    
            for (String word : words) {
    
                Object value = context.get(word);
                if (null == value) {
                    context.write(word, 1);
    
                } else {
                    int v = (int) value;
                    context.write(word, v + 1);
                }
            }
        }
    }
    View Code

    缓存模拟

    import java.util.HashMap;
    
    public class Context {
        
        private HashMap<Object,Object> contextMap = new HashMap<>();
        
        public void write(Object key,Object value){
            
            contextMap.put(key, value);
            
        }
        
        public Object get(Object key){
            
            return contextMap.get(key);
            
        }
        
        public HashMap<Object,Object> getContextMap(){
            return contextMap;
        }
    }
    View Code

    8、实战描述

     需求描述:

    在业务系统的服务器上,业务程序会不断生成业务日志(比如网站的页面访问日志)

    业务日志是用log4j生成的,会不断地切出日志文件

    需要定期(比如每小时)从业务服务器上的日志目录中,探测需要采集的日志文件(access.log,不是直接采集数据),发往HDFS

    注意点:业务服务器可能有多台(hdfs上的文件名不能直接用日志服务器上的文件名)

    当天采集到的日志要放在hdfs的当天目录中

    采集完成的日志文件,需要移动到到日志服务器的一个备份目录中

    定期检查(一小时检查一次)备份目录,将备份时长超出24小时的日志文件清除

    Timer timer = new Timer()
    
    timer.schedual()

     简易版日志采集主程序

    import java.util.Timer;
    
    public class DataCollectMain {
        
        public static void main(String[] args) {
            
            Timer timer = new Timer();
    
            timer.schedule(new CollectTask(), 0, 60*60*1000L);
            
            timer.schedule(new BackupCleanTask(), 0, 60*60*1000L);
            
        }
    }
    View Code

    日志收集定时任务类

    import java.io.File;
    import java.io.FilenameFilter;
    import java.net.URI;
    import java.text.SimpleDateFormat;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.Properties;
    import java.util.TimerTask;
    import java.util.UUID;
    
    import org.apache.commons.io.FileUtils;
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    import org.apache.log4j.Logger;
    
    public class CollectTask extends TimerTask {
    
        @Override
        public void run() {
    
            /**
             * ——定时探测日志源目录 ——获取需要采集的文件 ——移动这些文件到一个待上传临时目录
             * ——遍历待上传目录中各文件,逐一传输到HDFS的目标路径,同时将传输完成的文件移动到备份目录
             * 
             */
            try {
                // 获取配置参数
                Properties props = PropertyHolderLazy.getProps();
    
                // 构造一个log4j日志对象
                Logger logger = Logger.getLogger("logRollingFile");
    
                // 获取本次采集时的日期
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
                String day = sdf.format(new Date());
    
                File srcDir = new File(props.getProperty(Constants.LOG_SOURCE_DIR));
    
                // 列出日志源目录中需要采集的文件
                File[] listFiles = srcDir.listFiles(new FilenameFilter() {
    
                    @Override
                    public boolean accept(File dir, String name) {
                        if (name.startsWith(props.getProperty(Constants.LOG_LEGAL_PREFIX))) {
                            return true;
                        }
                        return false;
                    }
                });
    
                // 记录日志
                logger.info("探测到如下文件需要采集:" + Arrays.toString(listFiles));
    
                // 将要采集的文件移动到待上传临时目录
                File toUploadDir = new File(props.getProperty(Constants.LOG_TOUPLOAD_DIR));
                for (File file : listFiles) {
                    FileUtils.moveFileToDirectory(file, toUploadDir, true);
                }
    
                // 记录日志
                logger.info("上述文件移动到了待上传目录" + toUploadDir.getAbsolutePath());
    
                // 构造一个HDFS的客户端对象
    
                FileSystem fs = FileSystem.get(new URI(props.getProperty(Constants.HDFS_URI)), new Configuration(), "root");
                File[] toUploadFiles = toUploadDir.listFiles();
    
                // 检查HDFS中的日期目录是否存在,如果不存在,则创建
                Path hdfsDestPath = new Path(props.getProperty(Constants.HDFS_DEST_BASE_DIR) + day);
                if (!fs.exists(hdfsDestPath)) {
                    fs.mkdirs(hdfsDestPath);
                }
    
                // 检查本地的备份目录是否存在,如果不存在,则创建
                File backupDir = new File(props.getProperty(Constants.LOG_BACKUP_BASE_DIR) + day + "/");
                if (!backupDir.exists()) {
                    backupDir.mkdirs();
                }
    
                for (File file : toUploadFiles) {
                    // 传输文件到HDFS并改名access_log_
                    Path destPath = new Path(hdfsDestPath + "/" + UUID.randomUUID() + props.getProperty(Constants.HDFS_FILE_SUFFIX));
                    fs.copyFromLocalFile(new Path(file.getAbsolutePath()), destPath);
    
                    // 记录日志
                    logger.info("文件传输到HDFS完成:" + file.getAbsolutePath() + "-->" + destPath);
    
                    // 将传输完成的文件移动到备份目录
                    FileUtils.moveFileToDirectory(file, backupDir, true);
    
                    // 记录日志
                    logger.info("文件备份完成:" + file.getAbsolutePath() + "-->" + backupDir);
    
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
    }
    View Code

    定期清理过时备份日志

    import java.io.File;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.TimerTask;
    
    import org.apache.commons.io.FileUtils;
    
    public class BackupCleanTask extends TimerTask {
    
        @Override
        public void run() {
    
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
            long now = new Date().getTime();
            try {
                // 探测本地备份目录
                File backupBaseDir = new File("d:/logs/backup/");
                File[] dayBackDir = backupBaseDir.listFiles();
    
                // 判断备份日期子目录是否已超24小时
                for (File dir : dayBackDir) {
                    long time = sdf.parse(dir.getName()).getTime();
                    if(now-time>24*60*60*1000L){
                        FileUtils.deleteDirectory(dir);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
    }
    View Code

    配置信息提取到属性配置文件中,并写成常量,以单例设计模式去加载配置信息。

    LOG_SOURCE_DIR=d:/logs/accesslog/
    LOG_TOUPLOAD_DIR=d:/logs/toupload/
    LOG_BACKUP_BASE_DIR=d:/logs/backup/
    LOG_BACKUP_TIMEOUT=24
    LOG_LEGAL_PREFIX=access.log.
    
    HDFS_URI=hdfs://hdp-01:9000/
    HDFS_DEST_BASE_DIR=/logs/
    HDFS_FILE_PREFIX=access_log_
    HDFS_FILE_SUFFIX=.log
    View Code
    public class Constants {
    
        /**
         * 日志源目录参数key
         */
        public static final String LOG_SOURCE_DIR = "LOG_SOURCE_DIR";
        
        /**
         * 日志待上传目录参数key
         */
        public static final String LOG_TOUPLOAD_DIR = "LOG_TOUPLOAD_DIR";
        
        
        public static final String LOG_BACKUP_BASE_DIR = "LOG_BACKUP_BASE_DIR";
        
        
        public static final String LOG_BACKUP_TIMEOUT = "LOG_BACKUP_TIMEOUT";
        
        
        public static final String LOG_LEGAL_PREFIX = "LOG_LEGAL_PREFIX";
        
        
        public static final String HDFS_URI = "HDFS_URI";
        
        
        public static final String HDFS_DEST_BASE_DIR = "HDFS_DEST_BASE_DIR";
        
        
        public static final String HDFS_FILE_PREFIX = "HDFS_FILE_PREFIX";
        
        
        public static final String HDFS_FILE_SUFFIX = "HDFS_FILE_SUFFIX";
    
    }
    View Code
    import java.util.Properties;
    
    /**
     * 单例模式:懒汉式——考虑了线程安全
     * @author ThinkPad
     *
     */
    
    public class PropertyHolderLazy {
    
        private static Properties prop = null;
    
        public static Properties getProps() throws Exception {
            if (prop == null) {
                synchronized (PropertyHolderLazy.class) {
                    if (prop == null) {
                        prop = new Properties();
                        prop.load(PropertyHolderLazy.class.getClassLoader().getResourceAsStream("collect.properties"));
                    }
                }
            }
            return prop;
        }
    
    }
    View Code
    import java.util.Properties;
    
    /**
     * 单例设计模式,方式一: 饿汉式单例
     * @author ThinkPad
     *
     */
    public class PropertyHolderHungery {
    
        private static Properties prop = new Properties();
    
        static {
            try {
                prop.load(PropertyHolderHungery.class.getClassLoader().getResourceAsStream("collect.properties"));
            } catch (Exception e) {
    
            }
        }
    
        public static Properties getProps() throws Exception {
    
            return prop;
        }
    
    }
    View Code

    9、总结

    hdfs有服务端和客户端;

    服务端:

      成员:namenode 管理元数据,datanode存储数据

      配置:需要指定使用的文件系统(默认的配置在core-default.xml,为本地文件系统,需要修改服务器core-site.xml修改为hdfs文件系统,并指定namenode),namenode和datanode的工作目录(服务器的默认配置在hdfs-default.xml中,默认是在/temp下,需要就该hdfs-site.xml来覆盖默认值。);

      细节:第一次启动集群时需要格式化namenode

    客户端:

      形式:网页端,命令行,java客户端api;客户端可以运行在任何地方。

      功能:指定上传文件的信息报括切块大小(hdfs-default.xml中默认值128m,可以在hdfs-site.xml中修改,也可以咋java api 中创建客户端对象的时候指定,总之由客户端来指定),副本数量(hdfs-default.xml中默认值3,同样可以修改覆盖);完成hdfs中文件的系列操作,如上传,下载

    虽然服务端和客户端的共用配置 core-default.xml core-site.xml;hdfs-default.xml hdfs-site.xml,但是不同的程序所需要的参数不同,只不过为了方便,所有参数都写在一个文件中了。即是在服务器的hdfs-site.xml中配置了切块大小和副本数量,服务器的namenode和datanode根本不关心也不使用这些参数,只有启动服务器上的命令行客户端时,该参数才可能起作用。

  • 相关阅读:
    教材全解
    知乎、博客园等开放API接口
    学习正则表达式就这么简单
    C#操作域用户ADHelper
    跨线程时使用静态扩展方法更新控件
    C#中的WinForm的消息机制简述,及消息机制下Invoke,和BeginInvoke的使用和区别
    WinForm 捕获异常 Application.ThreadException + AppDomain.CurrentDomain.UnhandledException
    Winform异常处理之ThreadException、unhandledException及多线程异常处理
    深入理解C#中的IDisposable接口
    批处理应用的几个技巧
  • 原文地址:https://www.cnblogs.com/arjenlee/p/9504534.html
Copyright © 2020-2023  润新知