2. HDFS CLINT WRITE整理版

本文深入剖析HDFS写文件过程,从DistributedFileSystem.create()方法入手,详细讲解了主线程、DataStreamer线程和ResponseProcessor线程的功能,以及它们如何协作完成文件写入。特别关注了DFSOutputStream、Packet等关键数据结构,揭示了HDFS如何高效地处理数据块和校验和。

1.概述

通过fsShell或api在客户端断点调试,可以知道写文件入口DistributedFileSystem.create()。

"main@1" prio=5 tid=0x1 nid=NA runnable

 java.lang.Thread.State: RUNNABLE

     at org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.<init>(DFSOutputStream.java:227)

     at org.apache.hadoop.hdfs.DFSOutputStream.<init>(DFSOutputStream.java:1626)

     at org.apache.hadoop.hdfs.DFSOutputStream.newStreamForCreate(DFSOutputStream.java:1679)

     at org.apache.hadoop.hdfs.DFSClient.create(DFSClient.java:1627)

     at org.apache.hadoop.hdfs.DistributedFileSystem$7.doCall(DistributedFileSystem.java:499)

     at org.apache.hadoop.fs.FileSystemLinkResolver.resolve(FileSystemLinkResolver.java:81)

     at org.apache.hadoop.hdfs.DistributedFileSystem.create(DistributedFileSystem.java:514)

     at org.apache.hadoop.hdfs.DistributedFileSystem.create(DistributedFileSystem.java:442)

     at org.apache.hadoop.fs.FilterFileSystem.create(FilterFileSystem.java:179)

     at org.apache.hadoop.fs.FileSystem.create(FileSystem.java:979

下文从该方法开始分析。

DistributedFileSystem.create()主要调用过程如下。

【流程说明】

  1. 通过DFSClient.create()创建一个新文件,获得输出流。构建输出流时通过ClientProtocol.create() RPC在NameNode创建文件。
  2. 调用DFSOutputStream.start()方法。实际会启动streamer线程,下文详解。
  3. 获得输出流后,通过beginFileLease开始文件租约。

【注意点】

  1. ClientProtocol.create() RPC返回HDFSFileStatus stat状态,从该标识中区分副本文件还是EC文件,创建对应的输出流,
    副本文件DFSOutputStream out =new DFSOutputStream(...),EC文件DFSOutputStream out = new DFSStripedOutputStream(...)。

2.主要数据结构

  1. 先分析创建输出流DFSOutputStream结构,如下:


    DFSOutputStream、DataStreamer、ResponseProcessor是写数据的三个线程类。
    ResponseProcessor是DataStreamer的内部类。2.7版本中DataStreamer也是DFSOutputStream的内部类,3.2已经单独提出。
    定义如下:

    class DataStreamer extends Daemon {...}

    private class ResponseProcessor extends Daemon {...}

    线程类的作用:
    DataStreamer类负责将packets发送到pipeline中的数据节点,DataStreamer线程从dataQueue中picks up packets,发送到pipeline中的第一个datanode
    ResponseProcessor接收datanode返回的ack,并将其从dataQueue移至ackQueue,当所有datanode都返回了这个packets的successful ack,ResponseProcessor从ackQueue中删除相应的packets。
     

  2. DFSOutputStream使用Packet来封装一个数据包,其结构如下。
    在默认情况下(可debug调试),上述数据结构数据大小如下:

    Packet : 64k
    CheckSum : 4Byte
    Data: 512Byte

    每个Packet可以有126个Chunk:Checksum+Data。
    每个packet传输的data+checksum的字节为:126*516=65016字节。
    每个packet传输的有效字节为:126*512=64512字节。

    通过debug,可以看到每个数据结构大小,部分log如下:

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: computePacketChunkSize: src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, chunkSize=516, chunksPerPacket=126, packetSize=65016

    19/12/23 10:35:58 DEBUG hdfs.LeaseRenewer: Lease renewer daemon for [DFSClient_NONMAPREDUCE_-1334506554_1] with renew id 1 started

    19/12/23 10:35:58 DEBUG ipc.Client: IPC Client (1537471098) connection to cluster-host1/10.179.72.122:9000 from hadoop sending #4

    19/12/23 10:35:58 DEBUG ipc.Client: IPC Client (1537471098) connection to cluster-host1/10.179.72.122:9000 from hadoop got value #4

    19/12/23 10:35:58 DEBUG ipc.ProtobufRpcEngine: Call: getFileInfo took 1ms

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: DFSClient writeChunk allocating new packet seqno=0, src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, packetSize=65016, chunksPerPacket=126, bytesCurBlock=0

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: DFSClient writeChunk packet full seqno=0, src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, bytesCurBlock=64512, blockSize=134217728, appendChunk=false

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: Queued packet 0

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: computePacketChunkSize: src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, chunkSize=516, chunksPerPacket=126, packetSize=65016

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: DFSClient writeChunk allocating new packet seqno=1, src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, packetSize=65016, chunksPerPacket=126, bytesCurBlock=64512

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: DFSClient writeChunk packet full seqno=1, src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, bytesCurBlock=129024, blockSize=134217728, appendChunk=false

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: Queued packet 1

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: Allocating new block

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: computePacketChunkSize: src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, chunkSize=516, chunksPerPacket=126, packetSize=65016

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: DFSClient writeChunk allocating new packet seqno=2, src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, packetSize=65016, chunksPerPacket=126, bytesCurBlock=129024

    19/12/23 10:35:58 DEBUG hdfs.DFSClient: DFSClient writeChunk packet full seqno=2, src=/test/write/hadoop-2.7.6-2.tar.gz._COPYING_, bytesCurBlock=193536, blockSize=134217728, appendChunk=false

3.代码分析

获得输出流out后,就可以write数据了。如IOUtils.copyBytes工具。

先看一下调用栈如下:

java.lang.Thread.State: RUNNABLE

     at org.apache.hadoop.hdfs.DFSOutputStream.computePacketChunkSize(DFSOutputStream.java:1741)

     at org.apache.hadoop.hdfs.DFSOutputStream.writeChunkImpl(DFSOutputStream.java:1876)

     - locked <0xaeb> (a org.apache.hadoop.hdfs.DFSOutputStream)

     at org.apache.hadoop.hdfs.DFSOutputStream.writeChunk(DFSOutputStream.java:1813)

     at org.apache.hadoop.fs.FSOutputSummer.writeChecksumChunks(FSOutputSummer.java:206)

     at org.apache.hadoop.fs.FSOutputSummer.write1(FSOutputSummer.java:124)

     at org.apache.hadoop.fs.FSOutputSummer.write(FSOutputSummer.java:110)

     at org.apache.hadoop.fs.FSDataOutputStream$PositionCache.write(FSDataOutputStream.java:58)

     at java.io.DataOutputStream.write(DataOutputStream.java:107)

     - locked <0xb29> (a org.apache.hadoop.hdfs.client.HdfsDataOutputStream)

     at org.apache.hadoop.io.IOUtils.copyBytes(IOUtils.java:87)

以下围绕3个线程进行分析。

1.主线程

1.write

  1)write()

  • 主线程主要执行write()方法。入口是抽象类FSOutputSummer.write()。
    DFSOutputStream.write()继承FSOutputSummer.write()。write方法主要用于向pipeline中写入指定大小的数据及校验和。是客户端写数据操作的入口。

    // FSOutputSummer#write

    @Override

    public synchronized void write(byte b[], int off, int len)

        throws IOException {

      ...

      //循环调用write1写数据,每次写入一个校验块,即checkChunk=checkSum+data

      for (int n=0;n<len;n+=write1(b, off+n, len-n)) {

      }

    }

  2)write1()

  • write1每次写一个校验块数据。
  1. 当数据长度不足一个校验块时,则先写入buffer缓冲区,当buffer数据打到一个校验块时,调用flushBuffer();
  2. 如果buffer为空且数据大于一个校验块,则不经过buffer直接调用writeChecksumChunks()

    // FSOutputSummer#write1

    private int write1(byte b[], int off, int len) throws IOException {

      if(count==0 && len>=buf.length) {

        // local buffer is empty and user buffer size >= local buffer size, so

        // simply checksum the user buffer and send it directly to the underlying

        // stream

        final int length = buf.length;

        writeChecksumChunks(b, off, length);

        return length;

      }

       

      // copy user data to local buffer

      int bytesToCopy = buf.length-count;

      bytesToCopy = (len<bytesToCopy) ? len : bytesToCopy;

      System.arraycopy(b, off, buf, count, bytesToCopy);

      count += bytesToCopy;

      if (count == buf.length) {

        // local buffer is full

        flushBuffer();

      }

      return bytesToCopy;

    }

  3)writeChunk()

  • 无论是flushBuffer()还是writeChecksumChunks(),最终都调用writeChunk(),writeChunk()是抽象方法。最终由DFSOutputStream实现。
  • writeChunk()首先构造Packet对象,然后将校验块数据及校验和写入Packet对象中。当Packet对象写满时(maxChunks如126个校验块),则调用enqueueCurrentPacketFull()-->enqueueCurrentPacket()-->getStreamer().waitAndQueuePacket(currentPacket)将当前Packet放入输出队列dataQueue等待streamer处理,等待加入队列和发送。

    protected synchronized void writeChunk(ByteBuffer buffer, int len,

        byte[] checksum, int ckoff, int cklen) throws IOException {

      //构建currentPacket

      writeChunkPrepare(len, ckoff, cklen);

     

      currentPacket.writeChecksum(checksum, ckoff, cklen);

      currentPacket.writeData(buffer, len);

      currentPacket.incNumChunks();

      getStreamer().incBytesCurBlock(len);

     

      // If packet is full, enqueue it for transmission

      if (currentPacket.getNumChunks() == currentPacket.getMaxChunks() ||

              getStreamer().getBytesCurBlock() == blockSize) {

        //放入dataQueue队列

        enqueueCurrentPacketFull();

      }

    }

2.close

关闭DFSOutputStream并向NamenodeRPC,更新租约管理。dfsClient.namenode.complete(src, dfsClient.clientName, last, fileId);


 

说明:

  1. 如果是追加写(append),有可能最后一个校验块(chunk)并没有写满,新写的第一个校验块及Packet需要响应较少以至于补充满;
  2. dataQueue.size() + ackQueue.size() > 80(默认值)时,Packet将等待。

主线程write流程如下:    

2.DataStreamer

  • DataStreamer在DFSOutputStream的构造器中创建。

    protected DFSOutputStream(DFSClient dfsClient, String src,

        HdfsFileStatus stat, EnumSet<CreateFlag> flag, Progressable progress,

        DataChecksum checksum, String[] favoredNodes, boolean createStreamer) {

      ...

      if (createStreamer) {

        streamer = new DataStreamer(stat, null, dfsClient, src, progress,

            checksum, cachingStrategy, byteArrayManager, favoredNodes,

            addBlockFlags);

      }

    }

    DataStreamer是一个线程类。从run方法入口,我们看到主要流程如下:

    public void run() {

        ...

        // if the Responder encountered an error, shutdown Responder

        ...

        // get new block from namenode.

        ... 

        // wait for all data packets have been successfully acked

        ...  

        // send the packet

        ... 

        // move packet from dataQueue to ackQueue

        ...

    }

    1)首先调用nextBlockOutputStream()向NameNode申请新的数据块blk;
    2)建立这个blk块对应的数据流管道pipeline;
    3)从dataQueue中取出待发送的Packet,并通过pipeline发送给dataNode;
    4)该blk的所有Packet都发送完毕并且获得ack后,streamer线程关闭该pipeline;
    5)若还有数据再次申请数据块blk1并新建pipeline,重复上述1-4。

     

  • 说明:DataStreamer针对错误处理做了很多复杂的工作。

3.ResponseProcessor

  • DataStreamer每次发送一个Packet,pipeline中的datanode都会回复ack给客户端。PresponseProcessor就是处理ack响应的线程类。类似datanode上的PacketProcessor。

副本和EC文件流程

流程图如下:

  • 副本
  • EC
    EC文件采用Striped块组方式存储。DataStreamer和ResponseProcessor都是线程组的方式出现。客户端具体对应的实现类为DFSStripedOutputStream。以下给出流程图。
内容概要:本文系统研究了基于豪猪优化算法(CPO)的多无人机协同集群在三维空间中的避障路径规划问题,聚焦于实现以最低成本为目标的航迹优化,综合考虑路径长度、飞行高度、威胁规避及转弯角度等多个关键因素。通过构建精细化的三维环境模型与多无人机协同机制,采用Matlab平台实现CPO算法的仿真与验证,充分展示了该算法在复杂动态障碍环境下的高效搜索能力与全局优化性能。研究不仅涵盖了路径规划的数学建模与目标函数设计,还深入探讨了算法的收敛特性与鲁棒性,为智能群体系统在实际场景中的应用提供了理论依据与技术支撑。; 适合人群:具备一定编程基础和优化算法背景,从事无人机系统控制、智能路径规划、群体协同、人工智能与自动化等相关领域的科研人员、高校研究生及工程技术人员。; 使用场景及目标:①应用于多无人机协同执行侦察、灾害监测、应急救援、区域巡检等复杂任务中的自主路径规划;②为智能优化算法在三维动态环境下的路径决策问题提供可复现的技术范例;③支持研究人员对CPO算法与其他主流群智能算法(如PSO、GWO、WOA等)进行性能对比与改进研究,推动路径规划技术的发展。; 阅读建议:建议结合提供的Matlab代码进行实践操作,重点理解目标函数的多维度建模方式与CPO算法的迭代优化流程,可通过调整环境参数与约束条件进行仿真实验,对比不同算法在相同场景下的路径质量与收敛速度,从而深入掌握其优势与适用边界。
内容概要:本文围绕电动汽车参与电力系统运行备用的能力评估展开深入研究,利用Matlab代码实现对电动汽车集群提供运行备用服务的建模与仿真分析。研究重点在于量化电动汽车作为分布式灵活资源参与电网辅助服务的潜力,通过构建精细化的数学模型,分析其可调功率容量、响应速度、时空分布特性及聚合能力,并采用多面体聚合、内近似模型与闵可夫斯基和等先进方法精确刻画其可调度能力边界。研究进一步结合大规模电动汽车接入场景,探讨其在多时间尺度调度框架下参与调峰、调频等辅助服务的优化策略,评估其对提升高比例可再生能源电网灵活性与稳定性的贡献,最终通过仿真验证所提模型与方法的有效性与实用性。; 适合人群:具备电力系统分析、智能电网、新能源汽车或优化调度等相关专业背景,熟悉Matlab/Simulink仿真工具,从事科研、工程应用的高校研究生、科研人员及电力行业工程师。; 使用场景及目标:①精确评估大规模电动汽车集群在不同约束条件下可提供的运行备用容量;②研究电动汽车在日前、日内及实时调度中的动态响应能力与优化调度策略;③为高渗透率新能源电力系统提供基于移动储能的灵活性资源解决方案,支撑电网安全经济运行。; 阅读建议:建议结合Matlab代码与技术文档同步学习,重点关注多面体聚合建模、能力边界计算及优化调度算法的设计与实现,可进一步拓展至V2G(车辆到电网)、需求响应等互动场景进行二次开发与应用验证。
打开链接下载源码: https://pan.quark.cn/s/a4b39357ea24 OpenCV(开源计算机视觉库)中的DNN(Deep Neural Network)模块是一种功能强大的工具,其目的是用于深度学习模型的操作。该模块使得开发人员能够在OpenCV环境中直接运用已经训练好的深度学习网络,以执行图像识别、目标检测、图像分割等多种功能。DNN模块能够兼容多种深度学习框架的模型,包括TensorFlow、Caffe、ONNX等。 一、DNN模块概述 OpenCV的DNN模块是为了简化深度学习模型的集成过程而专门设计的,它允许开发人员加载预先训练好的神经网络模型,并在图像数据上执行前向传播操作。借助这个模块,用户可以选用GPU或者CPU来提升计算效率,从而构建出高效的应用程序。 二、目标检测案例 在OpenCV的DNN模块中,目标检测是一个常见的应用情形。例如,可以选用SSD(Single Shot Multibox Detector)、YOLO(You Only Look Once)或者 Faster R-CNN 等模型进行实时的目标检测。这些模型能够识别并定位图像中的多个对象,并返回每个对象的类别和边界框坐标。 三、模型转换:PB到PBTXT 在OpenCV中运用TensorFlow模型时,通常需要处理的是`.pb`格式的模型文件,这是TensorFlow的二进制模型文件格式。然而,为了能够读取模型的结构信息,我们需要`.pbtxt`格式的文本文件。转换过程涉及解析`.pb`文件并将其结构信息导出为`.pbtxt`格式,这样做可以让人清晰地了解网络层和参数的配置。在OpenCV中,可以使用`tf.train.write_graph()`函数将.pb...
内容概要:本文围绕虚拟同步发电机(VSG)接入弱电网的序阻抗建模与稳定性分析开展研究,基于Matlab/Simulink平台搭建详细的仿真模型,系统复现并验证相关理论方法。研究重点包括VSG在弱电网条件下的正负序阻抗特性建模、基于小信号分析的扫频法建模流程、系统阻抗交互特性及潜在的失稳机理分析。通过具体仿真案例,深入探讨了VSG控制参数对系统稳定性的影响,旨在为新能源并网系统的稳定运行提供理论依据与技术支撑。该内容属于电力电子与电力系统稳定性交叉领域的前沿课题,具有重要的学术价值与工程应用前景。; 适合人群:具备电力系统分析、电力电子变换器控制等基础知识,熟悉Matlab/Simulink仿真环境,从事新能源并网、微电网控制、电力系统稳定性研究的研究生、科研人员及工程师;有志于复现高水平期刊论文中阻抗建模与稳定性分析方法的技术开发者。; 使用场景及目标:① 掌握虚拟同步发电机在弱电网中的序阻抗建模理论与实现方法;② 理解并实践基于扫频法的小信号稳定性分析全过程;③ 应用于构网型变流器、虚拟同步机等先进并网技术的稳定性研究与仿真验证。; 阅读建议:建议结合所提供的Simulink仿真模型与技术资料,按照文档结构循序渐进地学习,重点关注建模原理、仿真参数设置与结果分析过程,同时参考链接中的完整资源进行代码调试与深入探究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值