百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

大数据Hadoop是如何存储数据的(hadoop大数据解决方案)

nanshan 2024-11-10 10:12 9 浏览 0 评论

HDFS概述

产生背景

随着数据量越来越大,在一个操作系统中存不下所有的数据。需要将这些数据分配到更多的操作系统中,带来的问题是多操作系统不方便管理和维护。需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS是分布式文件管理系统中的一种

定义

HDFS(Hadoop Distributed File System)它是一个文件系统,用于存储文件,通过目录树来定位文件。其次,他是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色

HDFS 的使用场景:适合一次写,多次读的场景,且不支持文件的修改。适合用来做数据分析

优缺点

优点

  • 高容错
  • 数据自动保存多个副本,通过增加副本的形式,提高容错性
  • 某一个副本丢失以后,可以自动恢复
  • 适合处理大数据
  • 数据规模:达到 GB、TB、甚至 PB 级别的数据
  • 文件规模:能够处理百万规模以上的文件数量
  • 可以构建在廉价机器上

缺点

  • 不适合低延时数据访问。比如毫秒级的存储数据,做不到
  • 无法高效的对大量小文件进行存储
  • namenode 对每个文件至少需要消耗 150 字节去存储目录和块信息,小文件相比大文件更消耗 namenode 服务器内存
  • 小文件寻址时间会超过读取时间,违背 HDFS 设计初衷
  • 不支持并发写入、文件随机修改一个文件只能有一个写,不允许多线程同时写
  • 仅支持数据追加,不支持修改

组成架构

HDFS 文件块大小

HDFS 中的文件在物理上是分块存储(Block),块的大小可以手动配置参数 dfs.blocksize 来修改(Hadoop 2.x 是 128m,之前是 64m)

为什么取 128m 呢?

普遍认为,寻址时间(即查找目标 block 的时间)为 10ms,而寻址时间为传输时间的 1% 时,为 HDFS 运行的理想最佳状态。此时传输时间为 10ms / 1% = 1000ms = 1s,而目前硬盘的传输速度普遍为 100m/s ,因此 block 的大小取 1s*100m/s = 100m。离它最近的 2 的次幂就是 128 了。这块可以看出,影响 block 大小的主要因素就是硬盘的读取速度。因此当采用固态硬盘的时候完全可以把数值调整到 256 m 甚至更多。

块太小的时候,会增加寻址时间

但当块变得很大时,就要想办法避免热点数据的频繁读取了。这一点在 Google 的论文中有提到,论文中给到的解决思路是客户端缓存,但是并没有提具体实现 https://www.cnblogs.com/zzjhn/p/3834729.html

HDFS 的 Shell 操作

基本语法

bin/hadoop fs 具体命令 OR bin/hdfs dfs 具体命令

dfs是fs的实现类

常用命令

HDFS 客户端操作

环境准备

  1. 把之前下载的 hadoop 安装包解压,复制到一个不含中文路径的目录下(建议所有开发相关东西放在一个目录下,方便管理)
  2. 配置环境变量 HADOOP_HOME 和 Path

项目准备

项目地址 https://github.com/keatsCoder/HdfsClientDemo

创建 maven 项目,引入依赖

 <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.10.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.10.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>2.10.1</version>
        </dependency>
        <dependency>
            <groupId>jdk.tools</groupId>
            <artifactId>jdk.tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
        </dependency>
    </dependencies>

创建 Java 类,HdfsClient 主要进行了三步操作

  1. 创建 FileSystem 对象
  2. 通过 FileSystem 对象执行命令
  3. 关闭 FileSystem
public class HdfsClient {
    static FileSystem fs;

    static {
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://linux102:9000");

        try {
            fs = FileSystem.get(conf);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        // 执行操作
        mkDir();

        // 释放资源
        fs.close();
    }

    private static void mkDir() throws IOException {
        fs.mkdirs(new Path("/john/keats"));
    }
}

尝试运行,会得到第一个错误,大意是权限被拒绝,这个时候就需要配置 JVM 参数 -DHADOOP_USER_NAME=root 来告诉集群,使用 root 用户进行操作

配置好之后再运行,会遇到第二个错误

Could not locate Hadoop executable: D:\develop\hadoop\bin\winutils.exe -see https://wiki.apache.org/hadoop/WindowsProblems

这是因为我们之前配置环境的时候,解压的 hadoop 文件 bin 目录下没有 winutils.exe 这个文件,根据后面地址 wiki 百科的指示,可以下载该文件放在 bin 目录下。但是目前那个文件的最新版本是 2.8.1,也许会存在某些方面不兼容的问题,目前还暂时没有发现。因此可以直接下载该版本使用 https://github.com/steveloughran/winutils/releases

简化配置用户名和访问路径

FileSystem.get() 有一个重载方法,三个参数,第一个是 hadoop namenode 地址,第二个是 conf 对象,第三个是用户名。可以一次配好

测试方法详见示例代码 HdfsClient2.java 类

fs = FileSystem.get(new URI("hdfs://linux102:9000"), conf, "root");

上传文件

在项目 resource 目录下创建文件 zhangsan.txt

调用 copyFromLocalFile 方法上传文件

private static void uploadFile() throws IOException {
    URL systemResource = ClassLoader.getSystemResource("zhangsan.txt");
    String path = systemResource.getPath();

    fs.copyFromLocalFile(new Path(path), new Path("/john/keats/love"));
}

copyFromLocalFile 还有三个重载方法,分别提供以下功能

  • 是否删除源文件
  • 当目标文件存在时,是否覆盖目标文件
  • 多文件批量上传,但目标路径必须是文件夹路径

配置文件优先级

之前我们在 hadoop 集群配置的副本数量是 3 ,而 hadoop client 也支持两种方式配置参数

  1. conf 对象通过 key-value 形式配置
  2. resources 目录下放置 xml 配置文件配置

加上默认的 default-xxxx.xml 一共四种配置的方式。他们的优先级是

conf > resources 下的配置文件 > hadoop 集群配置文件 > default

ConfigFileTest.java 类对此处的配置进行的说明与测试,读者可以运行体验

下载文件

fs.copyToLocalFile(new Path("/three.txt"), new Path("D://zhangsan.txt"));

copyToLocalFile 还有两个重载方法,分别添加了。具体代码可参考 DownLoadFileTest.java

// 是否删除源文件
boolean delSrc
// 是否使用RawLocalFileSystem作为本地文件系统
// 默认是 false,目前比较直观的就是 false 状态下下载文件会同时生成 .crc 格式的校验文件,设置为 true 时不会生成
boolean useRawLocalFileSystem

删除文件

删除文件的API,第二个参数表示是否递归删除。如果要删除的 Path 对应的是文件夹,recursive 需要设置为 true ,否则会抛异常。其实就相当于 rm -r 中的参数 -r

public abstract boolean delete(Path f, boolean recursive) throws IOException;
private static void deleteFile() throws IOException {
    // /john/keats 是文件夹目录,递归设置为 false 会报错 PathIsNotEmptyDirectoryException: ``/john/keats is non empty': Directory is not empty
    //  fs.delete(new Path("/john/keats"), false);

    // 先上传,再删除
    HdfsClient2.uploadFile(true);
    fs.delete(new Path("/john/keats/love/zhangsan.txt"), true);
}

这块我在删除之前添加了上传操作,目的是为了防止文件不存在。而上传又存在一种可能就是目标文件已存在。这是个薛定谔的文件- -,因此我查看了 FileSystem 的上传 API,他是提供 overwrite 开关的,默认是 true 即覆盖目标文件

文件重命名

private static void renameFile() throws IOException {

    String dstFileName = "wangwu.txt";
    HdfsClient2.uploadFile();
    deleteFile(dstFileName);
    // 目标文件不存在,则更名成功
    boolean rename = fs.rename(new Path("/john/keats/love/zhangsan.txt"), new Path("/john/keats/love/", dstFileName));
    Assert.assertTrue(rename);

    // 目标文件存在,则更名失败
    boolean renameButDstIsExist = fs.rename(new Path("/john/keats/love/zhangsan.txt"), new Path("/john/keats/love/", dstFileName));
    Assert.assertFalse(renameButDstIsExist);
}

读取文件详细信息

public static void listFiles() throws IOException {
    // 获取文件详情
    RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);

    while (listFiles.hasNext()) {
        LocatedFileStatus status = listFiles.next();

        // 输出详情
        // 文件名称
        System.out.println(status.getPath().getName());
        // 长度
        System.out.println(status.getLen());
        // 权限
        System.out.println(status.getPermission());
        // 分组
        System.out.println(status.getGroup());

        // 获取存储的块信息
        BlockLocation[] blockLocations = status.getBlockLocations();

        for (BlockLocation blockLocation : blockLocations) {

            // 获取块存储的主机节点
            String[] hosts = blockLocation.getHosts();

            for (String host : hosts) {
                System.out.println(host);
            }
        }

        System.out.println("-----------分割线----------");
    }
}

判断某路径下的内容是文件还是文件夹

public static void isFile() throws IOException {
    // FileStatus[] listStatus = fs.listStatus(new Path("/"));
    FileStatus[] listStatus = fs.listStatus(new Path("/three.txt"));

    for (FileStatus fileStatus : listStatus) {
        // 如果是文件
        if (fileStatus.isFile()) {
            System.out.println("f:"+fileStatus.getPath().getName());
        }else {
            System.out.println("d:"+fileStatus.getPath().getName());
        }
    }
}

HDFS的I/O流操作

// 从本地上传到HDFS
public static void copyFileFromDiskByIO() throws IOException {
    // 2 创建输入流
    FileInputStream fis = new FileInputStream(new File("D:/zhangsan.txt"));

    // 3 获取输出流
    FSDataOutputStream fos = fs.create(new Path("/zhangsan.txt"));

    // 4 流对拷
    IOUtils.copyBytes(fis, fos, conf);
}

// 从HDFS拷贝到本地
public static void copyFileFromHDFSByIO() throws IOException {
    FSDataInputStream fis = fs.open(new Path("/zhangsan.txt"));

    // 3 获取输出流
    FileOutputStream fos = new FileOutputStream(new File("D:/zhangsan1.txt"));

    // 4 流的对拷
    IOUtils.copyBytes(fis, fos, conf);
}

文件的定位读取

/**
 * 从某个位置开始拷贝文件,用于读取某个完整文件的部分内容
 */
public static void copyFileSeek() throws Exception{
    // 2 打开输入流
    FSDataInputStream fis = fs.open(new Path("/hadoop-2.10.1.tar.gz"));

    // 3 定位输入数据位置
    fis.seek(1024*1024*128);

    // 4 创建输出流
    FileOutputStream fos = new FileOutputStream(new File("D:/hadoop-2.7.2.tar.gz.part2"));

    // 5 流的对拷
    IOUtils.copyBytes(fis, fos, conf);
}

HDFS 原理

HDFS的读写数据流程

写数据

1)客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在

2)NameNode返回是否可以上传

3)客户端请求第一个 Block上传到哪几个DataNode服务器上(根据服务器距离以及负载排序,取前副本数个服务器返回)

4)NameNode返回3个DataNode节点,分别为dn1、dn2、dn3

5)客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成

6)dn1、dn2、dn3逐级应答客户端

7)客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答

8)当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器。(重复执行3-7步)

读数据

1)客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址

2)挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据

3)DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)

4)客户端以Packet为单位接收,先在本地缓存,然后写入目标文件

网络拓扑图,节点距离计算

在HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据。那么这个最近距离怎么计算呢?

节点距离:两个节点到达最近的共同祖先的距离总和

机架感知,副本存储节点选择

机架感知说明

http://hadoop.apache.org/docs/r2.10.1/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html#Data_Replication

For the common case, when the replication factor is three, HDFS’s placement policy is to put one replica on one node in the local rack, another on a different node in the local rack, and the last on a different node in a different rack.

这样布置,第一考虑的是速度,也兼顾了容灾的要求

NameNode和SecondaryNameNode

NameNode中的元数据是存储在哪里的?

首先,我们做个假设,如果存储在NameNode节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,必然是效率过低。因此,元数据需要存放在内存中。但如果只存在内存中,一旦断电,元数据丢失,整个集群就无法工作了。因此产生在磁盘中备份元数据的FsImage

这样又会带来新的问题,当在内存中的元数据更新时,如果同时更新FsImage,就会导致效率过低,但如果不更新,就会发生一致性问题,一旦NameNode节点断电,就会产生数据丢失。因此,引入Edits文件(只进行追加操作,效率很高)。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。这样,一旦NameNode节点断电,可以通过FsImage和Edits的合并,合成元数据。

但是,如果长时间添加数据到Edits中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行FsImage和Edits的合并,如果这个操作由NameNode节点完成,又会效率过低。因此,引入一个新的节点SecondaryNamenode,专门用于FsImage和Edits的合并

整体的操作机制和 Redis 差不多,FsImage 相当于 Redis 中的 RDB 快照,Edits 相当于 Redis 中的 AOF 日志,两者结合。而 Redis 合并两个文件是采用的 Fork 进程的方式

第一阶段:NameNode启动

(1)第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存

(2)客户端对元数据进行增删改的请求

(3)NameNode记录操作日志,更新滚动日志

(4)NameNode在内存中对数据进行增删改

第二阶段:Secondary NameNode工作

(1)Secondary NameNode询问NameNode是否需要CheckPoint。直接带回NameNode是否检查结果

(2)Secondary NameNode请求执行CheckPoint

? (3)NameNode滚动正在写的Edits日志

? (4)将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode

? (5)Secondary NameNode加载编辑日志和镜像文件到内存,并合并

? (6)生成新的镜像文件fsimage.chkpoint

? (7)拷贝fsimage.chkpoint到NameNode

? (8)NameNode将fsimage.chkpoint重新命名成fsimage

DataNode 工作机制

1)一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳

2)DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息

3)心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用

4)集群运行中可以安全加入和退出一些机器

DataNode 多目录配置

DataNode 也可以配置成多个目录,每个目录存储的数据不一样。不同于 NameNode 多目录配置,NameNode 多个目录直接的数据是一样的,仅做备份和容灾用。我想是因为 DataNode 已经使用副本来做备份了,如果还继续在本机复制多份,不是很有必要。而 NameNode 在未做高可用之前并没有足够的备份,因此产生了差异

hdfs-site.xml

<property>
	<name>dfs.datanode.data.dir</name>
	<value>file:///${hadoop.tmp.dir}/dfs/data1,file:///${hadoop.tmp.dir}/dfs/data2</value>
</property>

添加新数据节点

(1)在hadoop104主机上再克隆一台hadoop105主机

(2)修改IP地址和主机名称

(3)删除原来HDFS文件系统留存的文件(/opt/module/hadoop/data和log)

(4)source一下配置文件

(5)直接启动DataNode,即可关联到集群

如果数据不均衡,可以使用 ./start-balancer.sh 命令实现集群的再均衡

但是这样存在一个问题:如果某些恶意分子知道了 NameNode 的地址,便可以连接集群并克隆出集群的数据,这样是极不安全的

添加白名单

只允许白名单内的地址连接 NameNode

在 NameNode 的 /opt/module/hadoop/etc/hadoop目录下创建 dfs.hosts 文件,并添加如下主机名称

linux102
linux103
linux104

在 NameNode 的 hdfs-site.xml 配置文件中增加 dfs.hosts 属性

<property>
    <name>dfs.hosts</name>
    <value>/opt/module/hadoop/etc/hadoop/dfs.hosts</value>
</property>

文件分发

xsync hdfs-site.xml

刷新NameNode

hdfs dfsadmin -refreshNodes

打开 Web 页面,可以看到不在白名单的 DataNode 会被下线

黑名单退役

在黑名单上的节点会被强制退出

黑名单的配置 key 如下

<property>
	<name>dfs.hosts.exclude</name>
	<value>/opt/module/hadoop/etc/hadoop/dfs.hosts.exclude</value>
</property>

需要注意

  1. 退役节点时,需要等待退役节点状态为 decommissioned(所有块已经复制完成),停止该节点及节点资源管理器
  2. 如果副本数是3,服役的节点小于等于3,是不能退役成功的,需要修改副本数后才能退役
  3. 不允许黑白名单同时出现一个主机名

HDFS 2.X 新特性

集群间数据拷贝

bin/hadoop distcp
hdfs://linux102:9000/user/keats/hello.txt hdfs://linux103:9000/user/keats/hello.txt

小文件存档

回收站

开启回收站功能,可以将删除的文件在不超时的情况下,恢复原数据,起到防止误删除、备份等作用

快照管理

快照相当于对目录做一个备份,并不会立刻复制所有文件。而是记录文件变化


原作者:@后青春期的Keats

链接:https://www.cnblogs.com/keatsCoder/p/14233180.html

最后

我这边整理了一份:架构师全套关于Java的系统化资料,包括Java核心知识点、面试专题和最新的互联网真题、电子书等都有,有需要的朋友可以私信回复【面试题】即可领取。

相关推荐

F5负载均衡器如何通过irules实现应用的灵活转发?

F5是非常强大的商业负载均衡器。除了处理性能强劲,以及高稳定性之外,F5还可以通过irules编写强大灵活的转发规则,实现web业务的灵活应用。irules是基于TCL语法的,每个iRules必须包含...

映射域名到NAS

前面介绍已经将域名映射到家庭路由器上,现在只需要在路由器上设置一下端口转发即可。假设NAS在内网的IP是192.168.1.100,NAS管理端口2000.你的域名是www.xxx.com,配置外部端...

转发(Forward)和重定向(Redirect)的区别

转发是服务器行为,重定向是客户端行为。转发(Forward)通过RequestDispatcher对象的forward(HttpServletRequestrequest,HttpServletRe...

SpringBoot应用中使用拦截器实现路由转发

1、背景项目中有一个SpringBoot开发的微服务,经过业务多年的演进,代码已经累积到令人恐怖的规模,亟需重构,将之拆解成多个微服务。该微服务的接口庞大,调用关系非常复杂,且实施重构的人员大部分不是...

公司想搭建个网站,网站如何进行域名解析?

域名解析是将域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。IP地址是网络上标识站点的数字地址,为方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转...

域名和IP地址什么关系?如何通过域名解析IP?

一般情况下,访客通过域名和IP地址都能访问到网站,那么两者之间有什么关系吗?本文中科三方针对域名和IP地址的关系和区别,以及如何实现域名与IP的绑定做下介绍。域名与IP地址之间的关系IP地址是计算机的...

分享网站域名301重定向的知识

网站域名做301重定向操作时,一般需要由专业的技术来协助完成,如果用户自己在维护,可以按照相应的说明进行操作。好了,下面说说重点,域名301重定向的操作步骤。首先,根据HTTP协议,在客户端向服务器发...

NAS外网到底安全吗?一文看懂HTTP/HTTPS和SSL证书

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:可爱的小cherry搭好了NAS,但是不懂做好网络加密,那么隐私泄露也会随时发生!大家好,这里是Cherry,喜爱折腾、玩数码,热衷于分享数...

ForwardEmail免费、开源、加密的邮件转发服务

ForwardEmail是一款免费、加密和开源的邮件转发服务,设置简单只需4步即可正常使用,通过测试来看也要比ImprovMX好得多,转发近乎秒到且未进入垃圾箱(仅以Mailbox.org发送、Out...

使用CloudFlare进行域名重定向

当网站变更域名的时候,经常会使用域名重定向的方式,将老域名指向到新域名,这通常叫做:URL转发(URLFORWARDING),善于使用URL转发,对SEO来说非常有用,因为用这种方式能明确告知搜索引...

要将端口5002和5003通过Nginx代理到一个域名上的操作笔记

要将端口5002和5003通过Nginx代理到域名www.4rvi.cn的不同路径下,请按照以下步骤配置Nginx:步骤说明创建或编辑Nginx配置文件通常配置文件位于/etc/nginx/sites...

SEO浅谈:网站域名重定向的三种方式

在大多数情况下,我们输入网站访问网站的时候,很难发现www.***.com和***.com的区别,因为一般的网站主,都会把这两个域名指向到同一网站。但是对于网站运营和优化来说,www.***.com和...

花生壳出现诊断域名与转发服务器ip不一致的解决办法

出现诊断域名与转发服务器ip不一致您可以:1、更改客户端所处主机的drs为223.5.5.5备用dns为119.29.29.29;2、在windows上进入命令提示符输入ipconfig/flush...

涨知识了!带你认识什么是域名

1、什么是域名从技术角度来看,域名是在Internet上解决IP地址对应的一种方法。一个完整的域名由两个或两个以上部分组成,各部分之间用英文的句号“.”来分隔。如“abc.com”。其中“com”称...

域名被跳转到其他网站是怎么回事

当你输入域名时被跳转到另一个网站,这可能是由几种原因造成的:一、域名可能配置了域名转发服务。无论何时有人访问域名,比如.com、.top等,都会自动重定向到另一个指定的URL,这通常是在域名注册商设...

取消回复欢迎 发表评论: