HDFS写文件过程分析
HDFS是一个分布式文件系统,在HDFS上写文件的过程与我们平时使用的单机文件系统非常不同,从宏观上来看,在HDFS文件系统上创建并写一个文件,流程如下图(来自《Hadoop:The Definitive Guide》一书)所示: 具体过程描述如下: Client调用DistributedFileSy
HDFS是一个分布式文件系统,在HDFS上写文件的过程与我们平时使用的单机文件系统非常不同,从宏观上来看,在HDFS文件系统上创建并写一个文件,流程如下图(来自《Hadoop:The Definitive Guide》一书)所示:
具体过程描述如下:
- Client调用DistributedFileSystem对象的create方法,创建一个文件输出流(FSDataOutputStream)对象
- 通过DistributedFileSystem对象与Hadoop集群的NameNode进行一次RPC远程调用,在HDFS的Namespace中创建一个文件条目(Entry),该条目没有任何的Block
- 通过FSDataOutputStream对象,向DataNode写入数据,数据首先被写入FSDataOutputStream对象内部的Buffer中,然后数据被分割成一个个Packet数据包
- 以Packet最小单位,基于Socket连接发送到按特定算法选择的HDFS集群中一组DataNode(正常是3个,可能大于等于1)中的一个节点上,在这组DataNode组成的Pipeline上依次传输Packet
- 这组DataNode组成的Pipeline反方向上,发送ack,最终由Pipeline中第一个DataNode节点将Pipeline ack发送给Client
- 完成向文件写入数据,Client在文件输出流(FSDataOutputStream)对象上调用close方法,关闭流
- 调用DistributedFileSystem对象的complete方法,通知NameNode文件写入成功
下面代码使用Hadoop的API来实现向HDFS的文件写入数据,同样也包括创建一个文件和写数据两个主要过程,代码如下所示:
static String[] contents = new String[] { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "dddddddddddddddddddddddddddddddd", "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", }; public static void main(String[] args) { String file = "hdfs://h1:8020/data/test/test.log"; Path path = new Path(file); Configuration conf = new Configuration(); FileSystem fs = null; FSDataOutputStream output = null; try { fs = path.getFileSystem(conf); output = fs.create(path); // 创建文件 for(String line : contents) { // 写入数据 output.write(line.getBytes("UTF-8")); output.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } }
结合上面的示例代码,我们先从fs.create(path);开始,可以看到FileSystem的实现DistributedFileSystem中给出了最终返回FSDataOutputStream对象的抽象逻辑,代码如下所示:
public FSDataOutputStream create(Path f, FsPermission permission, boolean overwrite, int bufferSize, short replication, long blockSize, Progressable progress) throws IOException { statistics.incrementWriteOps(1); return new FSDataOutputStream (dfs.create(getPathName(f), permission, overwrite, true, replication, blockSize, progress, bufferSize), statistics); }
上面,DFSClient dfs的create方法中创建了一个OutputStream对象,在DFSClient的create方法:
public OutputStream create(String src, FsPermission permission, boolean overwrite, boolean createParent, short replication, long blockSize, Progressable progress, int buffersize ) throws IOException { ... ... }
创建了一个DFSOutputStream对象,如下所示:
final DFSOutputStream result = new DFSOutputStream(src, masked, overwrite, createParent, replication, blockSize, progress, buffersize, conf.getInt("io.bytes.per.checksum", 512));
下面,我们从DFSOutputStream类开始,说明其内部实现原理。
DFSOutputStream内部原理
打开一个DFSOutputStream流,Client会写数据到流内部的一个缓冲区中,然后数据被分解成多个Packet,每个Packet大小为64k字节,每个Packet又由一组chunk和这组chunk对应的checksum数据组成,默认chunk大小为512字节,每个checksum是对512字节数据计算的校验和数据。
当Client写入的字节流数据达到一个Packet的长度,这个Packet会被构建出来,然后会被放到队列dataQueue中,接着DataStreamer线程会不断地从dataQueue队列中取出Packet,发送到复制Pipeline中的第一个DataNode上,并将该Packet从dataQueue队列中移到ackQueue队列中。ResponseProcessor线程接收从Datanode发送过来的ack,如果是一个成功的ack,表示复制Pipeline中的所有Datanode都已经接收到这个Packet,ResponseProcessor线程将packet从队列ackQueue中删除。
在发送过程中,如果发生错误,所有未完成的Packet都会从ackQueue队列中移除掉,然后重新创建一个新的Pipeline,排除掉出错的那些DataNode节点,接着DataStreamer线程继续从dataQueue队列中发送Packet。
下面是DFSOutputStream的结构及其原理,如图所示:
我们从下面3个方面来描述内部流程:
- 创建Packet
Client写数据时,会将字节流数据缓存到内部的缓冲区中,当长度满足一个Chunk大小(512B)时,便会创建一个Packet对象,然后向该Packet对象中写Chunk Checksum校验和数据,以及实际数据块Chunk Data,校验和数据是基于实际数据块计算得到的。每次满足一个Chunk大小时,都会向Packet中写上述数据内容,直到达到一个Packet对象大小(64K),就会将该Packet对象放入到dataQueue队列中,等待DataStreamer线程取出并发送到DataNode节点。
- 发送Packet
DataStreamer线程从dataQueue队列中取出Packet对象,放到ackQueue队列中,然后向DataNode节点发送这个Packet对象所对应的数据。
- 接收ack
发送一个Packet数据包以后,会有一个用来接收ack的ResponseProcessor线程,如果收到成功的ack,则表示一个Packet发送成功。如果成功,则ResponseProcessor线程会将ackQueue队列中对应的Packet删除。
DFSOutputStream初始化
首先看一下,DFSOutputStream的初始化过程,构造方法如下所示:
DFSOutputStream(String src, FsPermission masked, boolean overwrite, boolean createParent, short replication, long blockSize, Progressable progress, int buffersize, int bytesPerChecksum) throws IOException { this(src, blockSize, progress, bytesPerChecksum, replication); computePacketChunkSize(writePacketSize, bytesPerChecksum); // 默认 writePacketSize=64*1024(即64K),bytesPerChecksum=512(没512个字节计算一个校验和), try { if (createParent) { // createParent为true表示,如果待创建的文件的父级目录不存在,则自动创建 namenode.create(src, masked, clientName, overwrite, replication, blockSize); } else { namenode.create(src, masked, clientName, overwrite, false, replication, blockSize); } } catch(RemoteException re) { throw re.unwrapRemoteException(AccessControlException.class, FileAlreadyExistsException.class, FileNotFoundException.class, NSQuotaExceededException.class, DSQuotaExceededException.class); } streamer.start(); // 启动一个DataStreamer线程,用来将写入的字节流打包成packet,然后发送到对应的Datanode节点上 } 上面computePacketChunkSize方法计算了一个packet的相关参数,我们结合代码来查看,如下所示: int chunkSize = csize + checksum.getChecksumSize(); int n = DataNode.PKT_HEADER_LEN + SIZE_OF_INTEGER; chunksPerPacket = Math.max((psize - n + chunkSize-1)/chunkSize, 1); packetSize = n + chunkSize*chunksPerPacket;
我们用默认的参数值替换上面的参数,得到:
int chunkSize = 512 + 4; int n = 21 + 4; chunksPerPacket = Math.max((64*1024 - 25 + 516-1)/516, 1); // 127 packetSize = 25 + 516*127;
上面对应的参数,说明如下表所示:
参数名称 | 参数值 | 参数含义 |
chunkSize | 512+4=516 | 每个chunk的字节数(数据+校验和) |
csize | 512 | 每个chunk数据的字节数 |
psize | 64*1024 | 每个packet的最大字节数(不包含header) |
DataNode.PKT_HEADER_LEN | 21 | 每个packet的header的字节数 |
chunksPerPacket | 127 | 组成每个packet的chunk的个数 |
packetSize | 25+516*127=65557 | 每个packet的字节数(一个header+一组chunk) |
在计算好一个packet相关的参数以后,调用create方法与Namenode进行RPC请求,请求创建文件:
if (createParent) { // createParent为true表示,如果待创建的文件的父级目录不存在,则自动创建 namenode.create(src, masked, clientName, overwrite, replication, blockSize); } else { namenode.create(src, masked, clientName, overwrite, false, replication, blockSize); }
远程调用上面方法,会在FSNamesystem中创建对应的文件路径,并初始化与该创建的文件相关的一些信息,如租约(向Datanode节点写数据的凭据)。文件在FSNamesystem中创建成功,就要初始化并启动一个DataStreamer线程,用来向Datanode写数据,后面我们详细说明具体处理逻辑。
Packet结构与定义
Client向HDFS写数据,数据会被组装成Packet,然后发送到Datanode节点。Packet分为两类,一类是实际数据包,另一类是heatbeat包。一个Packet数据包的组成结构,如图所示:
上图中,一个Packet是由Header和Data两部分组成,其中Header部分包含了一个Packet的概要属性信息,如下表所示:
字段名称 | 字段类型 | 字段长度 | 字段含义 |
pktLen | int | 4 | 4 + dataLen + checksumLen |
offsetInBlock | long | 8 | Packet在Block中偏移量 |
seqNo | long | 8 | Packet序列号,在同一个Block唯一 |
lastPacketInBlock | boolean | 1 | 是否是一个Block的最后一个Packet |
dataLen | int | 4 | dataPos – dataStart,不包含Header和Checksum的长度 |
Data部分是一个Packet的实际数据部分,主要包括一个4字节校验和(Checksum)与一个Chunk部分,Chunk部分最大为512字节。
在构建一个Packet的过程中,首先将字节流数据写入一个buffer缓冲区中,也就是从偏移量为25的位置(checksumStart)开始写Packet数据的Chunk Checksum部分,从偏移量为533的位置(dataStart)开始写Packet数据的Chunk Data部分,直到一个Packet创建完成为止。如果一个Packet的大小未能达到最大长度,也就是上图对应的缓冲区中,Chunk Checksum与Chunk Data之间还保留了一段未被写过的缓冲区位置,这种情况说明,已经在写一个文件的最后一个Block的最后一个Packet。在发送这个Packet之前,会检查Chunksum与Chunk Data之间的缓冲区是否为空白缓冲区(gap),如果有则将Chunk Data部分向前移动,使得Chunk Data 1与Chunk Checksum N相邻,然后才会被发送到DataNode节点。
我们看一下Packet对应的Packet类定义,定义了如下一些字段:
ByteBuffer buffer; // only one of buf and buffer is non-null byte[] buf; long seqno; // sequencenumber of buffer in block long offsetInBlock; // 该packet在block中的偏移量 boolean lastPacketInBlock; // is this the last packet in block? int numChunks; // number of chunks currently in packet int maxChunks; // 一个packet中包含的chunk的个数 int dataStart; int dataPos; int checksumStart; int checksumPos;
Packet类有一个默认的没有参数的构造方法,它是用来做heatbeat的,如下所示:
Packet() { this.lastPacketInBlock = false; this.numChunks = 0; this.offsetInBlock = 0; this.seqno = HEART_BEAT_SEQNO; // 值为-1 buffer = null; int packetSize = DataNode.PKT_HEADER_LEN + SIZE_OF_INTEGER; // 21+4=25 buf = new byte[packetSize]; checksumStart = dataStart = packetSize; checksumPos = checksumStart; dataPos = dataStart; maxChunks = 0; }
通过代码可以看到,一个heatbeat的内容,实际上只有一个长度为25字节的header数据。通过this.seqno = HEART_BEAT_SEQNO;的值可以判断一个packet是否是heatbeat包,如果seqno为-1表示这是一个heatbeat包。
Client发送Packet数据
可以DFSClient类中看到,发送一个Packet之前,首先需要向选定的DataNode发送一个Header数据包,表明要向DataNode写数据,该Header的数据结构,如图所示:
上图显示的是Client发送Packet到第一个DataNode节点的Header数据结构,主要包括待发送的Packet所在的Block(先向NameNode分配Block ID等信息)的相关信息、Pipeline中另外2个DataNode的信息、访问令牌(Access Token)和校验和信息,Header中各个字段及其类型,详见下表:
字段名称 | 字段类型 | 字段长度 | 字段含义 |
Transfer Version | short | 2 | Client与DataNode之间数据传输版本号,由常量DataTransferProtocol.DATA_TRANSFER_VERSION定义,值为17 |
OP | int | 4 | 操作类型,由常量DataTransferProtocol.OP_WRITE_BLOCK定义,值为80 |
blkId | long | 8 | Block的ID值,由NameNode分配 |
GS | long | 8 | 时间戳(Generation Stamp),NameNode分配blkId的时候生成的时间戳 |
DNCnt | int | 4 | DataNode复制Pipeline中DataNode节点的数量 |
Recovery Flag | boolean | 1 | Recover标志 |
Client | Text | Client主机的名称,在使用Text进行序列化的时候,实际包含长度len与主机名称字符串ClientHost | |
srcNode | boolean | 1 | 是否发送src node的信息,默认值为false,不发送src node的信息 |
nonSrcDNCnt | int | 4 | 由Client写的该Header数据,该数不包含Pipeline中第一个节点(即为DNCnt-1) |
DN2 | DatanodeInfo | DataNode信息,包括StorageID、InfoPort、IpcPort、capacity、DfsUsed、remaining、LastUpdate、XceiverCount、Location、HostName、AdminState | |
DN3 | DatanodeInfo | DataNode信息,包括StorageID、InfoPort、IpcPort、capacity、DfsUsed、remaining、LastUpdate、XceiverCount、Location、HostName、AdminState | |
Access Token | Token | 访问令牌信息,包括IdentifierLength、Identifier、PwdLength、Pwd、KindLength、Kind、ServiceLength、Service | |
CheckSum Header | DataChecksum | 1+4 | 校验和Header信息,包括type、bytesPerChecksum |
Header数据包发送成功,Client会收到一个成功响应码(DataTransferProtocol.OP_STATUS_SUCCESS = 0),接着将Packet数据发送到Pipeline中第一个DataNode上,如下所示:
Packet one = null; one = dataQueue.getFirst(); // regular data packet ByteBuffer buf = one.getBuffer(); // write out data to remote datanode blockStream.write(buf.array(), buf.position(), buf.remaining()); if (one.lastPacketInBlock) { // 如果是Block中的最后一个Packet,还要写入一个0标识该Block已经写入完成 blockStream.writeInt(0); // indicate end-of-block }
否则,如果失败,则会与NameNode进行RPC调用,删除该Block,并把该Pipeline中第一个DataNode加入到excludedNodes列表中,代码如下所示:
if (!success) { LOG.info("Abandoning " + block); namenode.abandonBlock(block, src, clientName); if (errorIndex <p><strong>DataNode端服务组件</strong></p> <p>数据最终会发送到DataNode节点上,在一个DataNode上,数据在各个组件之间流动,流程如下图所示:<br> <img class="alignnone size-full wp-image-947 lazy" src="/static/imghw/default1.png" data-src="http://www.68idc.cn/help/uploads/allimg/150119/0RA42a0-4.png" alt="hdfs-write-pipeline-single" style="max-width:90%" style="max-width:90%"><br> DataNode服务中创建一个后台线程DataXceiverServer,它是一个SocketServer,用来接收来自Client(或者DataNode Pipeline中的非最后一个DataNode节点)的写数据请求,然后在DataXceiverServer中将连接过来的Socket直接派发给一个独立的后台线程DataXceiver进行处理。所以,Client写数据时连接一个DataNode Pipeline的结构,实际流程如图所示:<br> <img class="alignnone size-full wp-image-948 lazy" src="/static/imghw/default1.png" data-src="http://www.68idc.cn/help/uploads/allimg/150119/0RA440a-5.png" alt="hdfs-write-pipeline-datanodes" style="max-width:90%" style="max-width:90%"><br> 每个DataNode服务中的DataXceiver后台线程接收到来自前一个节点(Client/DataNode)的Socket连接,首先读取Header数据:</p> <pre class="brush:php;toolbar:false"> Block block = new Block(in.readLong(), dataXceiverServer.estimateBlockSize, in.readLong()); LOG.info("Receiving " + block + " src: " + remoteAddress + " dest: " + localAddress); int pipelineSize = in.readInt(); // num of datanodes in entire pipeline boolean isRecovery = in.readBoolean(); // is this part of recovery? String client = Text.readString(in); // working on behalf of this client boolean hasSrcDataNode = in.readBoolean(); // is src node info present if (hasSrcDataNode) { srcDataNode = new DatanodeInfo(); srcDataNode.readFields(in); } int numTargets = in.readInt(); if (numTargets accessToken = new Token<blocktokenidentifier>(); accessToken.readFields(in); </blocktokenidentifier>
上面代码中,读取Header的数据,与前一个Client/DataNode写入Header字段的顺序相对应,不再累述。在完成读取Header数据后,当前DataNode会首先将Header数据再发送到Pipeline中下一个DataNode结点,当然该DataNode肯定不是Pipeline中最后一个DataNode节点。接着,该DataNode会接收来自前一个Client/DataNode节点发送的Packet数据,接收Packet数据的逻辑实际上在BlockReceiver中完成,包括将来自前一个Client/DataNode节点发送的Packet数据写入本地磁盘。在BlockReceiver中,首先会将接收到的Packet数据发送写入到Pipeline中下一个DataNode节点,然后再将接收到的数据写入到本地磁盘的Block文件中。
DataNode持久化Packet数据
在DataNode节点的BlockReceiver中进行Packet数据的持久化,一个Packet是一个Block中一个数据分组,我们首先看一下,一个Block在持久化到磁盘上的物理存储结构,如下图所示:
每个Block文件(如上图中blk_1084013198文件)都对应一个meta文件(如上图中blk_1084013198_10273532.meta文件),Block文件是一个一个Chunk的二进制数据(每个Chunk的大小是512字节),而meta文件是与每一个Chunk对应的Checksum数据,是序列化形式存储。
写文件过程中Client/DataNode与NameNode进行RPC调用
Client在HDFS文件系统中写文件过程中,会发生多次与NameNode节点进行RPC调用来完成写数据相关操作,主要是在如下时机进行RPC调用:
- 写文件开始时创建文件:Client调用create在NameNode节点的Namespace中创建一个标识该文件的条目
- 在Client连接Pipeline中第一个DataNode节点之前,Client调用addBlock分配一个Block(blkId+DataNode列表+租约)
- 如果与Pipeline中第一个DataNode节点连接失败,Client调用abandonBlock放弃一个已经分配的Block
- 一个Block已经写入到DataNode节点磁盘,Client调用fsync让NameNode持久化Block的位置信息数据
- 文件写完以后,Client调用complete方法通知NameNode写入文件成功
- DataNode节点接收到并成功持久化一个Block的数据后,DataNode调用blockReceived方法通知NameNode已经接收到Block
具体RPC调用的详细过程,可以参考源码。
原文地址:HDFS写文件过程分析, 感谢原作者分享。

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











컴퓨터에서 폴더를 삭제하거나 압축을 풀 때 "오류 0x80004005: 지정되지 않은 오류"라는 프롬프트 대화 상자가 나타나는 경우가 있습니다. 이러한 상황이 발생하면 어떻게 해야 합니까? 실제로 오류 코드 0x80004005가 나타나는 데에는 여러 가지 이유가 있지만 대부분은 바이러스로 인해 발생합니다. 문제를 해결하기 위해 dll을 다시 등록할 수 있습니다. 아래에서는 편집기에서 0x80004005 오류 코드를 처리한 경험을 설명합니다. . 일부 사용자는 컴퓨터를 사용할 때 오류 코드 0X80004005가 표시됩니다. 0x80004005 오류는 주로 컴퓨터가 특정 동적 링크 라이브러리 파일을 올바르게 등록하지 않거나 컴퓨터와 인터넷 간의 HTTPS 연결을 허용하지 않는 방화벽으로 인해 발생합니다. 그렇다면 어떨까요?

Quark Netdisk와 Baidu Netdisk는 현재 파일 저장에 가장 일반적으로 사용되는 Netdisk 소프트웨어입니다. Quark Netdisk의 파일을 Baidu Netdisk에 저장하려면 어떻게 해야 합니까? 이번 호에서는 편집자가 Quark Network Disk 컴퓨터에서 Baidu Network Disk로 파일을 전송하는 방법에 대한 튜토리얼 단계를 정리했습니다. Quark 네트워크 디스크 파일을 Baidu 네트워크 디스크에 저장하는 방법은 무엇입니까? Quark Network Disk에서 Baidu Network Disk로 파일을 전송하려면 먼저 Quark Network Disk에서 필요한 파일을 다운로드한 다음 Baidu Network Disk 클라이언트에서 대상 폴더를 선택하고 열어야 합니다. 그런 다음 Quark Cloud Disk에서 다운로드한 파일을 Baidu Cloud Disk 클라이언트가 연 폴더에 끌어서 놓거나 업로드 기능을 사용하여 Baidu Cloud Disk에 파일을 추가합니다. 업로드가 완료된 후 파일이 Baidu Cloud Disk에 성공적으로 전송되었는지 확인하세요. 그게 다야

최근 많은 네티즌들이 편집자에게 hiberfil.sys 파일이 무엇인지 문의했습니다. hiberfil.sys가 C 드라이브 공간을 많이 차지하고 삭제될 수 있나요? 편집자는 hiberfil.sys 파일을 삭제할 수 있음을 알려줄 수 있습니다. 아래에서 자세한 내용을 살펴보겠습니다. hiberfil.sys는 Windows 시스템의 숨겨진 파일이자 시스템 최대 절전 모드 파일입니다. 일반적으로 C 드라이브의 루트 디렉터리에 저장되며 크기는 시스템에 설치된 메모리 크기와 동일합니다. 이 파일은 컴퓨터가 최대 절전 모드일 때 사용되며, 복구 시 빠르게 이전 상태로 복원할 수 있도록 현재 시스템의 메모리 데이터를 담고 있습니다. 크기가 메모리 용량과 동일하므로 하드 드라이브 공간을 더 많이 차지할 수 있습니다. 동면

파일 경로는 운영 체제에서 파일이나 폴더를 식별하고 찾는 데 사용되는 문자열입니다. 파일 경로에는 경로를 구분하는 두 가지 공통 기호, 즉 슬래시(/)와 백슬래시()가 있습니다. 이 두 기호는 운영 체제에 따라 용도와 의미가 다릅니다. 슬래시(/)는 Unix 및 Linux 시스템에서 일반적으로 사용되는 경로 구분 기호입니다. 이러한 시스템에서 파일 경로는 루트 디렉터리(/)에서 시작하고 각 디렉터리 사이를 슬래시로 구분합니다. 예를 들어 /home/user/Docume 경로는 다음과 같습니다.

MySQL에서 .ibd 파일의 역할 및 관련 주의사항에 대한 자세한 설명 MySQL은 널리 사용되는 관계형 데이터베이스 관리 시스템이며 데이터베이스의 데이터는 서로 다른 파일에 저장됩니다. 그 중 .ibd 파일은 InnoDB 스토리지 엔진의 데이터 파일로, 테이블에 데이터와 인덱스를 저장하는 데 사용됩니다. 이 기사에서는 MySQL에서 .ibd 파일의 역할에 대한 자세한 분석을 제공하고 관련 코드 예제를 제공하여 독자의 이해를 돕습니다. 1. .ibd 파일의 역할: 데이터 저장: .ibd 파일은 InnoDB 저장소입니다.

Linux 시스템에서는 다음 명령을 사용하여 로그 파일의 내용을 볼 수 있습니다. tail 명령: tail 명령은 로그 파일 끝에 내용을 표시하는 데 사용됩니다. 최신 로그 정보를 보기 위한 일반적인 명령어입니다. tail [옵션] [파일 이름] 일반적으로 사용되는 옵션은 다음과 같습니다. -n: 표시할 줄 수를 지정합니다. 기본값은 10줄입니다. -f: 파일 내용을 실시간으로 모니터링하고, 파일이 업데이트되면 자동으로 새 내용을 표시합니다. 예: tail-n20logfile.txt#logfile.txt 파일의 마지막 20줄 표시 tail-flogfile.txt#logfile.txt 파일의 업데이트된 내용을 실시간으로 모니터링 head 명령: head 명령은 시작 부분을 표시하는 데 사용됩니다. 로그 파일의

Linux 운영 체제에서 파일을 작업하려면 개발자가 파일, 코드, 프로그램, 스크립트 및 기타 항목을 효율적으로 생성하고 실행할 수 있도록 하는 다양한 명령과 기술을 사용해야 합니다. Linux 환경에서는 확장자가 ".a"인 파일이 정적 라이브러리로서 매우 중요합니다. 이러한 라이브러리는 소프트웨어 개발에서 중요한 역할을 수행하므로 개발자는 여러 프로그램에서 공통 기능을 효율적으로 관리하고 공유할 수 있습니다. Linux 환경에서 효과적인 소프트웨어 개발을 위해서는 ".a" 파일을 생성하고 실행하는 방법을 이해하는 것이 중요합니다. 이번 글에서는 리눅스 ".a" 파일을 포괄적으로 설치하고 구성하는 방법을 소개한다. 리눅스 ".a" 파일의 정의, 목적, 구조, 생성 및 실행 방법을 살펴보자. L은 무엇입니까?

제목: DreamWeaver CMS의 보조 디렉터리를 열 수 없는 이유와 해결 방법 분석 Dreamweaver CMS(DedeCMS)는 다양한 웹 사이트 구축에 널리 사용되는 강력한 오픈 소스 콘텐츠 관리 시스템입니다. 그러나 때로는 웹사이트를 구축하는 과정에서 보조 디렉토리를 열 수 없는 상황이 발생할 수 있으며, 이로 인해 웹사이트의 정상적인 작동에 문제가 발생할 수 있습니다. 이 기사에서는 보조 디렉터리를 열 수 없는 가능한 이유를 분석하고 이 문제를 해결하기 위한 구체적인 코드 예제를 제공합니다. 1. 예상 원인 분석: 의사 정적 규칙 구성 문제: 사용 중
