본문 바로가기
Data/Hadoop

[하둡 완벽 가이드] Chapter 3 하둡 분산 파일 시스템

by 양진주 2024. 3. 24.

분산 파일 시스템: 네트워크로 연결된 여러 머신의 스토리지를 관리하는 파일 시스템 (하둡 - HDFS) 

- 네트워크 기반 ➡️ 네트워크 프로그램의 복잡성 소유 

 

3.1 HDFS

HDFS의 설계 특성

  • 매우 큰 파일: 수백 메가바이트 ~ 페타바이트
  • 스트리밍 방식의 데이터 접근: 전체 데이터셋을 모두 읽을 떄 걸리는 시간 중시 
  • 범용 하드웨어: 장애가 발생하더라도 사용자가 모르게 작업 수행 

 

HDFS와 잘 맞지 않는 응용 분야

  • 빠른 데이터 응답 시간(↔️스트리밍 방식의 데이터 접근): 대안 - HBase
  • 수많은 작은 파일(↔️매우 큰 파일): 많은 파일은 하드웨어 용량을 넘어섬
  • 다중 라이터와 파일의 임의수정: 파일에서 임의 위치에 있는 내용을 수정하는 것은 허용하지 않음 

 

3.2 HDFS 개념

3.2.1 블록

블록 크기 = 디스크가 한 번에 읽고 쓸 수 있는 데이터의 최대량 

(파일시스템 블록의 크기 = 수 킬로바이트, 디스크 블록의 크기 = 512 Byte)

HDFS의 블록 크기 = 128MB(탐색 비용을 최소화하기 위해 용량 ⬆️, 데이터 전송할 때 더 많은 시간 할애)

HDFS 파일 ➡️ 특정 블록 크기의 청크로 쪼개짐 ➡️ 각 청크는 독립적으로 저장 

 

블록 추상화 개념을 도입하면서 얻게 된 이득

  • 파일 하나의 크기 > 단일 디스크의 용량: 하나의 파일을 구성하는 여러개의 블록 어떤 디스크에도 저장 가능 
  • 스토리지 서브시스템 단순화: 블록은 고정 크기, 저장에 필요한 디스크 용량만 계산하면 됨, 메타데이터와 블록 분리
  • 복제 구현 적합: 각 블록 - 물리적으로 분리된 다수(3개)의 머신에 복제
#파일 시스템에 있는 각 파일을 구성하는 블록의 목록 노출 

% hdsf fsck / -files -blocks

 

3.2.2 네임노드와 데이터노드

HDFS 클러스터: 네임노드(마스터인 한 개) + 데이터노드(워커인 다수) ➡️ 마스터-워커 패턴으로 동작 

- 네임노드: 파일시스템의 네임스페이스 관리, 파일시스템 트리 & 트리에 포함된 모든 파일과 디렉터리에 대한 메타데이터 유지 ➡️ 네임스페이스 이미지 + 에디트 로그 (로컬 디스크에 영속적으로 저장, 블록의 위치 정보 제외)

 

↕️ HDFS 클라이언크: 네임노드 - 데이터노드 사이에서 통신, 파일시스템 접근 

 

- 데이터노드: 클라이언트 or 네임노드의 요청 ➡️ 블록 저장 & 탐색, 저장하고 있는 블록의 목록 주기적으로 네임노드에 보고

 

if 네임노드 장애 ➡️ 파일 시스템 정보 탐색 불가능

❗장애복구 기능1: 파일시스템의 메타데이터를 지속적인 상태로 보존하기 위해 파일로 백업
로컬 디스크, 원격의 NFS 마운트 두 곳에 동시에 백업

❗ 장애복구 기능2: 보조 네임노드 운영
보조 네임노드: 주기적으로 네임스페이스 이미지 + 에디트 로그 ➡️ 새로운 네임스페이스 이미지 생성 ➡️ 에디트 로그 크기 조절(충분한 CPU & 네임노드와 비슷한 용량의 메모리 필요 ➡️ 별도의 물리 머신에서 실행), 네임스페이스 이미지의 복제본 보관 ➡️ 주 네임노드 장애 발생 대비 

 

3.2.3 블록 캐싱

데이터노드: 빈번하게 접근하는 블록 파일 ➡️ 오프-힙 블록 캐시(데이터노드의 메모리)에 명시적으로 캐싱 ➡️ 읽기 성능 ⬆️(캐시 풀 + 캐시 지지자 ➡️ 특정 파일 캐싱 명령 가능)

 

3.2.4 HDFS 패더레이션 

HDFS 패더레이션 ➡️ 네임노드가 파일시스템의 네임스페이스 일부를 나누어 관리하는 방식으로 새로운 네임노드 추가 ➡️ 네임노드의 확장성 문제 해결 

 

각 네임노드: 네임스페이스 볼륨(네임스페이스의 메타데이터 구성, 서로 독립 ➡️ 각 네임노드 통신 필요 ❌) + 블록 풀(네임스페이스에 포함된 파일의 전체 블록을 보관, 서로 연결) 관리 

 

3.2.5 HDFS 고가용성

네임노드 메타데이터 다수의 파일 시스템 복제 + 보조 네임노드 ➡️ 데이터 손실 방지 

but, 네임노드: 단일 고장점 ➡️ 파일 시스템의 고가용성 궁극적 보장 ❌, 새로운 네임노드가 투입될 때까지 하둡 시스템 전체 먹통

 

네임노드 장애 복구 방법

파일시스템 메타데이터 복제본을 가진 새로운 네임노드 구동 ➡️ 모든 데이터노드 & 클라이언트에 새로운 네임노드 사용 고지 

but, 새로운 네임노드: 네임스페이스 이미지 메모리 로드 ➡️ 에디트 로그 갱신 ➡️ 전체 데이터노드에서 충분한 블록 리포트 받아 안전모드 탈출 
동안 어떤 요청도 처리 ❌ 

❗ 해결방안: 하둡 2.x 릴리즈부터 HDFS 고가용성(HA) 지원
활성 네임노드 장애 ➡️ 대시 네임노드가 역할을 이어받음 ➡️ 큰 중단 없이 클라이언트의 요청 처리 

 

장애복구 컨트롤러(객체) : 대기 네임노드 활성화 전환 작업

- 주키퍼 이용 ➡️ 단 하나의 네임노드만 활성 상태에 있는 것을 보장 

- 각 네임노드: 경량의 장애복구 컨트롤러 프로세스로 네임노드 장애 감시(하트비트 방식), 장애 발생 ➡️ 장애복구 지시 

- 우아한 장애복구: 정기적인 유지관리 ➡️ 관리자가 수동으로 초기화, 네임노드 2개 서로 역할 바꾸기 ➡️ 전환 순서 제어

    -❓문제점: 장애가 발생한 네임노드가 현재 실행되지 않고 있다는 확신 ❌

 

3.3 명령행 인터페이스 

3.3.1 기본적인 파일시스템 연산 

일반적인 파일 시스템 연산(파일 읽기, 디렉터리 생성, 파일 이동, 데이터 삭제, 디렉터리 목록 출력) 수행 

 

% hadoop fs -copyFromLocal input/docs/quangle.txt \
hdfs://localhost/user/tom/quangle.txt

#하둡 시스템의 쉘 명령어 fs 호출
% hadoop fs -copyFromLocal input/docs/quangle.txt /user/tom/quangle.txt

#상대 경로를 사용하여 HDFS의 홈 디렉터리로 파일 복사
#HDFS의 홈 디렉터리: /user/tom
% hadoop fs -mkdir books
% hadoop fs -ls  .

#출력 결과
#첫 번째 열 - 파일의 모드
#두 번째 열 - 파일의 복제 계수
#세 번째 & 네 번째 - 파일의 소유자 & 그룹
#다섯 번째 - 바이트 단위의 파일 크기(디렉터리: 0)
#여섯 번째 & 일곱 번째 - 마지막으로 수정된 날짜 & 시간 
#여덟 번째 - 파일 or 디렉터리의 절대 경로로 표시된 이름
HDFS
읽기 권한(r), 쓰기 권한(w), 실행 권한(x) 존재 
파일의 실행 지원 ❌ ➡️ 파일에 대한 실행 권한 무시, but 디렉터리에 대한 실행 권한: 하위 디렉터리의 접근 위해 필요 
각 파일 & 디렉터리: 소유자, 그룹, 모드(소유자, 그룹 멤버, 그 밖의 사용자)

 

 

3.4 하둡 파일시스템

자바 추상 클래스(org.appache.hadoop.fs.FileSystem): 하둡의 파일시스템을 접근할 수 있는 클라이언트 인터페이스

파일 시스템 URI 스킴 자바 구현체(org.apache.hadoop 아래) 설명
Local file fs.LocalFileSystem 클라이언트 측의 체크섬 사용하는 로컬 디스크를 위한 파일시스템. 
if 로컬 파일 시스템 체크섬 사용 ❌: RawLocalFileSystem 사용
HDFS hdfs hdfs.DistributedFileSystem 하둡 분산 파일 시스템. 
HDFS: 맵리듀스와 효율적으로 연동하기 위해 설계
WebHDFS webhdfs hdfs.web.WebHdfsFileSystem HTTP를 통해 HDFS에 인증을 통한 읽기/쓰기 제공 파일시스템 
Secure
WebHDFS
swebhdfs hdfs.web.SWebHdfsFileSystem WebHDFS의 HTTPS 버전
HAR har fs.HarFileSystem 아카이브 파일을 위한 파일 시스템
하둡 아카이브: 네임노드의 메모리 용량을 줄이기 위해 HDFS에 수많은 파일을 묶어놓은 파일
hadoop archive 명령어 실행 ➡️ HAR 파일 생성 
View viewfs viewfs.ViewFileSystem 다른 하둡 파일시스템을 위한 클라이언트 측 마운트 테이블
페더레이션 네임노드의 마운트 지점을 생성할 때 주로 사용
FTP ftp  fs.ftp.FTPFileSystem FTP 서버를 지원하는 파일시스템 
S3 s3a fd.s3a.S3AFileSystem 아마존 S3를 지원하는 파일시스템
구 버전의 s3n(S3 네이티브) 구현체 대체
Azure wasb fs.azure.NativeAzureFileSystem 마이크로소프트의 애저(Azure)를 지원하는 파일시스템
Swift swift fs.swift.snative.SwiftNativeFileSystem 오픈스택 스위프트(Swift)를 지원하는 파일시스템 

 

 

% hadoop fs -ls file:///

#로컬 파일시스템의 루트 디렉터리의 파일 목록

 

 

3.4.1 인터페이스

 HTTP: HTTP REST API 사용 

1) 클라이언트의 HTTP 요청 HDFS 데몬이 직접 처리
- 네임노드 & 데이터노트에 내장된 웹 서버가 WebHDFS의 말단으로 작용
- WebHDFS의 활성화 여부: dfs.webhdfs.enabled 속성에 정의(기본값: true)
- 네임노드: 파일에 대한 메타데이터 연산 처리, 파일 데이터를 스트리밍할 데이터노드를 알려주는 HTTP 리다이렉트를 클라이언트에 전송


2) 클라이언트 대신 DistributedFileSystem API로HDFS에 접근하는 프록시 경유
- 하나 또는 그 이상의 독립 프록시 서버 통함 ➡️ 클라이언트: 네임노드 & 데이터노드 직접 접근 필요 ❌

- 프록시 서버: 상태 저장 필요 ❌, 표준 로드 밸런서 사용 가능

C

- libhdfs: HDFS에 접근하기 위해 작성한 C 라이브러리, 모든 하둡 파일시스템에 접근 가능
- 자바 네이티브 인터페이스(JNI) 사용 

 

NFS

- NFSv3 게이트웨이 이용 ➡️ 로컬 클라이언트 파일시스템에 HDFS 마운트 가능, Unix 유틸리티 & POSIX 라이브러리 (파일 업로드 및 일반적인 프로그래밍 언어에서 파일시스템 다룸) 사용 가능 

 

FUSE: Filesystem in Userspace(사용자 공간에서의 파일시스템), 사용자 공간 &유닉스 파일시스템 통합 파일시스템 지원 

- Fuse-DFS contrib 모듈: 표준 로컬 파일시스템에 HDFS 마운트 기능 제공
- Fuse-DFS: C로 작성된 libhdfs로 HDFS 인터페이스 구현

 

3.5 자바 인터페이스 

3.5.1 하둡 URL로 데이터 읽기

InputStream in = null;
try {
	in = new URL("hdfs://host/path").openStream();
    //in을 처리한다. 
} finally {
	IOUtils.closeStream(in);
}

 

FsUrlStramHandlerFactory의 인스턴스와 함께 URL 클래스의 setURLStramHandlerFactory() 메서드 호출해 수행 

JVM 하나당 한 번씩만 호출 가능 ➡️ 정적 블록에서 실행

❓ 한계: 프로그램의 일부 다른 부분에서 URLStramHandlerFactory 설정 ➡️ 하둡 URL로부터 데이터를 읽을 수 없음

❗ 대안: 3.5.2 파일시스템 API로 데이터 읽기

 

//하둡 파일시스템의 파일을 URLStreamHandler를 사용하여 표준 출력으로 보여주기

public class URLCat {
	static {
    	URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
    }
    
    public static void main(String[] args) throws Exception {
    	InputStream in = null;
        try {
        	in = new URL(args[0]).openStram();
            IOUtils.copyBytes(in, System.out, 4096, false);
            // IOUtils 클래스: 바이트 복사 
            // 세 번째 인자: 버퍼 크기, 네 번째 인자: 복사 완료 시 스트림 닫기 여부(System.out - 닫을 필요 X)
        } finally {
        	IOUtils.closeStram(in);
        }
    }
}

 

3.5.2 파일시스템 API로 데이터 읽기

애플리케이션에서 URLStramHandlerFactory 설정 불가능 ➡️ FileSystem API 사용 ➡️ 파일에 대한 입력 스트림 열기 

하둡 파일시스템의 파일: 하둡 Path(하둡 파일시스템 URL) 객체로 표현

FileSystem: 일반적인 파일시스템 API, 접근할 파일시스템(HDFS)에 대한 인스턴스 필요

//FileSystem 인스턴스를 얻을 수 있는 정적 팩토리 메서드

public static FileSystem get(Configuration conf) throws IOException
// Configuration 객체: 클라이언트/서버의 환경 설정 포함
public static FileSystem get(URL uri, Configuration conf) throws IOException
public static FileSystem get(URL uri, Configuration con, String user) throws IOException

// 첫 번째 메서드: 기본 파일시스템 반환
// 두 번째 메서드: 주어진 URI 스킴 & 권한으로 파일 시스템 결정(URI에 스킴 명시 X: 기본 파일시스템으로 간주)
// 세 번째 메서드: 특정 사용자를 명시하여 파일시스템 추출
public static LocalFileSystem getLocal(Configuration conf) throws IOException

//FileSystem 인스턴스를 얻으면 open() 메서드를 호출해 파일에 대한 입력 스트림 열 수 있음
public FSDataInputStream open(Path f) throws IOException
public abstract FSDataInputStream open(Paht f, int bufferSize) throws IOException

// 첫 번째 메서드: 기본 버퍼 크기(4KB) 사용

 

//FileSystem API를 직접 사용하여 하둡 파일시스템의 파일을 표준 출력으로 보여주기

public class FileSystemcat {
	public static void main(String[] args) throws Exception {
    	String uri = args[0];
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(URI.create(uri), conf);
        InputStream in = null;
        try {
        	in = fs.open(new Path(uri));
            IOUtils.copyBytes(in, System.out, 4096, false);
        } finally {
        	IOUtils.closeStream(in);
        }
    }
}
//FileSystem의 open() 메서드: FSDataInputStream 클래스 반환

package org.apache.hadoop.fs;

public class FSDataInputStream extends DataInputStream
	implements Seekable, PositionedReadable {
    // Seekable 인터페이스: 파일에서 특정 위치로 이동하는 것을 허용
    // 코드 생략
}
public interface Seekable {
	void seek(long pos) throws IOException;
    long getPos() throws IOException;
}
//seek를 사용해서 하둡 파일시스템의 파일을 표준 출력으로 두 번 보여주기

public class FileSystemDoubleCat {
	public static void main(String[] args) throws Exception {
    	Stirng uri = args[0];
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(URI.create(uri), conf);
        FSDataInputStream in = null
        try {
        	in = fs.open(new Path(uri));
            IOUtils.copyBytes(in, System.out, 4096, false);
            in.seek(0); //파일의 처음으로 돌아간다
            IOUtils.copyBytes(in, System.out, 4096, false);
        } finally {
        	IOUtils.closeStream(in);
        }
    }
}
// FSDataInputStream: 오프셋에서 파일의 일부를 읽기 위한 PositionedReadable 인터페이스 제공

public interface PositionedReadable {

	public int read(long position, byte[] buffer, int offset, int length)
    // read() 메서드: 파일의 주어진 position에서 length만큼의 바이트를 읽어서 buffer의 주어진 offset에 그 내용 복사
    // 반환값: 실제로 읽은 바이트 크기 
    // 호출자: 반환값 < length 반드시 확인 
    	throws IOException;
        
    public void readFully(long position, byte[] buffer, int offset, int length)
    	throws IOException;
        
    public void readFully(long position, byte[] buffer) throws IOException; 
}

 

3.5.3 데이터 쓰기

public FSDataOutputStream create(Path f) throws IOException

// 생성할 파일을 Path 객체로 받아서 출력 스트림으로 씀 
// create() 메서드: 오버로드 버전(기존 파일을 강제로 덮어쓰기, 파일의 복제 계수, 파일 쓰기의 버퍼 크기, 파일의 블록 크기, 파일의 권한 명시) 허용
// create() 메서드: 부모 디렉터리가 없으면 자동으로 생성, exists() 메서드 호출로 부모 디렉터리 유무 확인
package org.apache.hadoop util;

public interface Progressable {
	public void progress();
}

// Progressable: 오버로드 메서드, 콜백 인터페이스 넘겨줌
public FSDataOutputStream append(Path f) throws IOException?

//append() 메서드로 기존 파일에 데이터 추가
// 로컬 파일을 하둡 파일시스템으로 복사하기

public class FileCopyWithProgress {
	public static void main(String[] args) throws Exception {
    	String localSrc = args[0];
        String dst = args[1];
        
        InputStream in = inew BufferedInputStream(new FileInputStream(localSrc));
        
        Configuration conf - new Configuration();
        FileSystem fs = FileSystem.get(URI.create(dst), conf);
        OutputStream out = fs.create(new Path(dst), new Progressable() {
        	public void progress() {
            	System.out.print(".");
            }
        });
        
        IOUtils.copuBytes(in, out, 4096, true);
    }
}
// FileSystem 클래스의 create() 메서드: FSDataOutputStream 반환, 파일에서 현재 위치를 얻기 위한 메서드 지원

package org.apache.hadoop.fs;

public class FSDataOutputStream extends DataOutputStream implements Syncable {
	public long getPos() throws IOException {
    // 코드 생략
    }
    
    //코드 생략
}

//FSDataOutputStream: 파일 탐색(Seek 기능) 지원 X

 

3.5.4 디렉터리

public boolean mkdirs(Path f) throws IOException

//FileSystem: 디렉터리를 생성하기 위한 메서드 제공 
// 존재하지 않는 모든 부모 디렉터리 생성

 

 

3.5.5 파일시스템 질의 

FileStatus 클래스: 파일 & 디렉터리 메타데이터(파일 길이, 블록 크기, 복제, 수정 시간, 소유권, 권한 정보) 포함 

public class ShowFileStatusTest {
	private MiniDFSCluster cluster; //현재 운영되고 있는 HDFS 클러스터를 테스트하기 위해 사용
    private FileSystem fs;
    
    @Before
    public void setUp() throws IOException {
    	Configuration conf = new Configuration();
        if (System.getProperty("test.build.data") == null {
        	System.setProperty("test.build.data", "/tmp");
        }
        cluster = new MiniDFSCluster.Builder(conf).build();
        fs = cluster.getFileSystem();
        Outputstream out = fs.create(new Path("/dir/file"));
        out.write("content".getBytes("UTF-8"));
        out.close();
    }
    
    @After
    public void tearDown() throws IOException {
    	if (fs != null) { fs.close(); }
        if (cluster != null) { cluster.shutdown(); }
    }
    
    @Test(expected = FileNotFoundException.class)
    public void throwsFileNotFoundExceptionFile() throws IOException {
    	fs.getFileStatus(new Path("no-such-file"));
    }
    
    @Test
    public void fileStatusForFile() throws IOException {
    	Path file = new Path("/dir/file");
        FileStatus stat = fs.getFileStatus(file);
        assertThat(stat.getPath().toUri().getPath(), is("/dir/file"));
        assertThat(stat.isDirectory(), is(false));
        assertThat(stat.getLen(), is(7L));
        assertThat(stat.getModificationTime(), is(lessThanOrEqualTo(System.currentTimeMillis())));
        assertThat(stat.getReplication(), is((short) 1));
        assertThat(stat.getBlockSize(), is(128 * 1024 * 1024L));
        assertThat(stat.getOwner(), is(System.getProperty("user.name")));
        assertThat(stat.getGroup(), is("supergroup"));
        assertThat(stat.getPermission().toString(), is("rw-r--r--"));
    }
    
    @Test
    public void fileStatusForDirectory() throws IOException {
    	Path dir = new Path("/dir");
        FileStatus stat = fs.getFileStatus(dir);
        assertThat(stat.getPath().toUri().getPath(), is("/dir"));
        assertThat(stat.isDirectory(), is(true));
        assertThat(stat.getLen(), is(0L));
        assertThat(stat.getModificationTime(), is(lessThanOrEqualTo(System.currentTimeMillis())));
        assertThat(stat.getReplication(), is((short) 0));
        assertThat(stat.getBlockSize(), is(0L));
        assertThat(stat.getOwner(), is(System.getProperty("user.name")));
        assertThat(stat.getGroup(), is("supergroup"));
        assertThat(stat.getPermission().toString(), is("rwxr-xr-x"));
        
// 파일 or 디렉터리가 없으면: FileNotFoundException
// 파일 or 디렉터리 존재여부: FileSystem의 exists() 메서드 호출

public boolean exists(Path f) throws IOException
public Filestatus[] listStatus(Path f) throws IOException 
public Filestatus[] listStatus(Path f, PathFilter filter) throws IOException
public Filestatus[] listStatus(Path[] files) throws IOException
public Filestatus[] listStatus(Path[] files, PathFilter filter) throws IOException

// FileSystem의 listStatus() 메서드: 특정 디렉터리의 내용 조회
//하둡 파일시스템의 경로 집합에 대한 파일 상태 보기

public class ListStatus {

	public static void main(String[] args throws Exception {
    	String uri = args[0];
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(URI.create(uri), conf);
        
        Path[] paths = enw Path[args.length];
        for(int i = 0; i < paths.length; i++) {
        	paths[i] = new Path(args[i]);
        }
        
        FileStatus[] status = fs.listStatus(paths);
        Path[] listedPaths = FileUtil.stat2Paths(status);
        for (Paht p : listedPaths) {
        	System.out.println(p);
        }
    }
}
// 글로빙: 모든 파일과 디렉터리를 단일 표현식으로 와일드카드 문자를 이용하여 다중 파일을 매칭
// 하둡에서 지원하는 두 개의 FileSystem 메서드

public FileStatus[] globStatus(Path pathPattern) throws IOException 
public FileStatus[] globstatus(Path pathPattern, PathFilter filter) throws IOException 

// globstatus() 메서드: 주어진 패턴에 매칭되는 경로에 대한 FileStatus 객체의 배열을 경로를 기준으로 정렬하여 반환
글로브 이름 일치되는 대상
* 별표 0개 이상의 문자
? 물음표 단일 문자
[ab] 문자 집합 집합 {a, b}에 있는 단일 문자
[^ab] 부정된 문자 집합 집합 {a, b}에 없는 단일 문자
[a - b] 문자 영역 (닫힌) 영역 [a, b]에 있는 단일 문자 (사전적으로는 a <= b)
[^a - b] 부정된 문자 영역 (닫힌) 영역 [a, b]에 없는 단일 문자 (사전적으로는 a <= b)
{a, b} 양자택일 표현 a 또는 b에 일치
\c 이스케이프 문자  문자 c가 메타문자일 때 문자 c와 일치 
// FileSystem의 listStatus() & globStatus() 메서드: 프로그래밍 방식으로 매칭을 허용하는 PathFilter를 옵션으로 지원

package org.apache.hadoop.fs;

public interface PathFilter {
	boolean accept(Paht path);
}
//정규표현식에 매칭되는 특정 경로를 제외하기 위한 PathFilter 

public class RegexExcludePathFilter implements PahtFilter {
	private final String regex;
    
    public RegexExcludePathFilter(String regex) {
    	this.regex = regex;
    }
    
    public boolean accept(Path path){
    	return !path.toString().matches(regex);
    }
}

//필터: 정규표현식에 매칭되지 않는 파일만 통과

 

3.5.6 데이터 삭제 

// FileSystem의 delete() 메서드: 파일과 디렉터리 영구적으로 삭제

public boolean delete(Path f, boolean recursive) throws IOException

 

3.6 데이터 흐름

3.6.1 파일 읽기 상세 

1. 열기

클라이언트: HDFS가 DistributedFileSystem 인스턴스인 FileSystem 객체의 open() 메서드 호출 ➡️ 원하는 파일 열기 

 

2. 블록 위치 얻기

DistributedFileSystem: RPC 사용 ➡️ 네임노드 호출 ➡️ 파일의 첫 번째 블록 위치 파악

 

3. 읽기

클라이언트: 스트림을 읽기 위해 read() 메서드 호출 

 

4. 읽기

DFSInputStream(파일의 첫 번째 블록의 데이터노드 주소 저장): 가장 가까운 데이터노드와 연결 

해당 스트림에 대해 read() 메서드를 반복적으로 호출 ➡️ 데이터노드 ▶️ 클라이언트 (모든 데이터 전송)

 

5. 읽기

블록의 끝에 도달 ➡️ DFSInputstream: 데이터노드의 연결 닫음, 다음 블록의 데이터노드 찾음

 

6. 닫기

클라이언트: 스트림 ➡️ 블록을 순서대로 하나씩 읽음, 다음 블록의 데이터노드 위치를 얻기 위해 네임노드 호출

DFSInputStream: 블록마다 데이터노드와 새로운 연결

if 모든 블록에 대한 읽기 끝) 클라이언트: FSDataInputStream의 close() 메서드 호출 

 

3.6.2 파일 쓰기 상세

1. 생성

클라이언트: DistributedFileSystem의 Create() 호출 ➡️ 파일 생성 

 

2. 생성

DistributedFileSystem: 네임노드에 RPC 요청

➡️ 네임노드: 요청한 파일과 동일한 파일 존재 여부, 클라이언트 파일 생성 권한 등 다양한 검사 

➡️ if 검사 통과) 네임노드: 새로운 파일의 레코드 생성 / if 검사 통과 ❌)  파일 생성 실패, 클라이언트의 IOException 발생 

 

3. 쓰기 

DFSOutputStream: 데이터 패킷으로 분리 ➡️ 내부 큐(데이터 큐)로 패킷 전송 ➡️ DataStreamer: 데이터 큐에 있는 패킷 처리(복제 수준 = 파이프라인의 개수)

 

4. 패킷 쓰기 

첫 번째 데이터노드: 각 패킷 저장 ➡️ 파이프라인의 두 번째 데이터노드로 전송 ➡️ 두 번째 데이터노드: 받은 패킷 저장 ➡️ 파이프라인의 세 번째(마지막) 데이터노드로 전달 

 

5. ack 패킷

DFSOutputStream: 데이터노드의 승인 여부를 기다리는 내부 패킷 큐(ack 큐) 유지

ack 內 패킷: 파이프라인의 모든 데이터노드로부터 ack 응답을 받아야 제거 

 

6. 닫기 

클라이언트: 데이터 쓰기를 완료할 때 크트림에 close() 메서드 호출(close(): 데이터노드 파이프라인에 남아있는 모든 패킷 플러시 ➡️ 승인 대기)

 

7. 완료

모든 패킷 완전히 전송 ➡️ 네임노드에 '파일 완료' 신호 전송 

 

신뢰성 쓰기 대역폭 ↔️ 읽기 대역폭

i.e., 단일 노드에 모든 복제본 배치 ➡️ 중복성 ❌, 대역폭 ⬇️ ➡️ 해당 노드 장애 발생 시 블록의 데이터 유실
i.e., 서로 다른 데이터 센터에 복제본 배치 ➡️ 중복성 최대화, 대역폭 비용 ⬆️

❗ 전략
첫 번째 복제본: 클라이언트와 같은 노드에 배치
두 번째 복제본: 첫 번째 노트와 다른 랙의 노드
세 번째 복제본: 두 번째 노드와 같은 랙, 다른 노드
... n 번째 복제본: 무작위 배치 
➡️ 신뢰성(블록을 두 랙에 저장) + 쓰기 대역폭(쓰기: 하나의 네트워크 스위치만 통과) + 읽기 성능(가까운 랙 선택) + 블록 분산(클라이언트: 로컬 랙에 하나의 블록만 저장)

 

3.6.3 일관성 모델 

Path p = new Path("p");
FSDataOutputStream out = fs.create(p);
out.write("content".getBytes("UTF-8"));
out.hflush(); //운영 체제에 플러시
out.getFD().sync(); //디스크에 동기화
assertThat(fs.getFileStatus(p).getLen(), is(((long "content".length())));

//hflush() 메서드: 데이터노트에 있는 모든 버퍼의 내용 플러시 강제 
//hsync() 메서드: 강한 신뢰성 보장, fsync() 시스템 호출과 유사

 

hflush() or hsync() 호출

❌, 클라이언트 or 시스템 장애 발생 ➡️ 데이터 블록 유실

❗ 적절한 시점에 hflush() 반드시 호출

 

강건성 ↔️ 처리량 ➡️ 적절한 균형 필요

 

3.7 distcp로 병렬 복사하기

distcp: 병렬로 다량의 데이터를 하둡 파일시스템으로부터 복사하기 위한 프로그램

% hadoop distcp file1 file2
// 파일 복사

% hadoop distcp dir1 dir2
// 디렉터리 복사
//if dir2 존재 X: 새롭게 생성, dir1 안의 파일 복사
//if dir2 존재: dir1은 dir2 하위에 복사(디렉터리 구조: dir2/dir1)

% hadoop distcp -update dir1 dir2
//-update  옵션: 변경이 이루어진 파일들만 복사

 

3.7.1 HDFS 클러스터 균형 유지

❓ 다른 잡을 위해 일부 노드를 사용, 맵의 수 제한 ➡️ 클러스터 불균형

❗ balancer 도구 사용