본문 바로가기
Data Engineering/하둡 2.0

하둡 HDFS (Hadoop Distributed File System)

by 무언가 하는 소소 2024. 2. 9.
728x90
반응형

1. HDFS (Hadoop Distributed File System)

하둡에서 제공하는 분산 파일시스템으로 하둡에서 제공하는 여러 파일시스템 중 대표적인 파일시스템이다. 

 

2. HDFS의 특징

1) 아주 큰 파일을 관리할 수 있다.

2) 스트리밍 방식의 데이터 접근을 지원한다. 따라서 응답시간은 느릴 수 있어도 처리량이 많다. 

3) 한번 쓴 데이터는 수정할 수 없다. (append는 가능)

4) 장애 복구, 데이터 무결성 등을 지원한다.

 

3. HDFS 블록

HDFS는 블록 단위로 데이터를 관리한다. 블록 단위의 관리를 통해 서브시스템을 단순하게 유지하고 데이터의 복제(레플리케이션)를 용이하게 한다. HDFS에서 블록의 사이즈는 기본적으로 128MB로 비교적 크다. 블록 사이즈가 큰 이유는 서치타임과 블록의 메타데이터를 줄이기 위함이다. 

 

4. HDFS의 동작

HDFS는 마스터-워커역할을 하는 두 종류의 노드가 있다. 마스터는 네임 노드(namenode), 워커는 데이터 노드(datanode)라고 한다. 네임 노드와 데이터 노드의 역할은 다음과 같다. 

 

1) 네임노드

- 메타데이터 관리 (블록관리)

- 데이터노드 주기적 모니터링

- 데이터노드에 블록 매핑

- 클라이언트 요청 접수

 

2) 데이터노드

- 네임노드의 요청에 따라 블록을 저장하고 탐색

- 하트비트를 통해 저장하고 있는 블록의 목록을 주기적으로 네임노드에 전송

 

3) 보조 네임노드

네임노드가 모든 정보를 포함하고 있으므로 네임노드가 한 개만 있을시 SPOF(Single Point of Failure)의 위험이 있다. 따라서 보조 네임노드를 둠으로써 네임노드의 이미지를 주기적으로 저장한다.

  

5. HDFS 인터페이스

HDFS는 사용자가 파일을 조작할 수 있도록 매우 다양한 인터페이스를 지원한다. 그 중에서 커맨드라인 인터페이스는 아래 코드와 같이 리눅스의 기본적인 파일시스템 연산과 거의 유사한 기능을 제공한다. 

hdfs dfs -<명령어>
#hdfs dfs -help를 실행하면 사용가능한 명령어와 설명을 볼 수 있다.

 

예시) 

#로컬에 있는 파일을 HDFS 파일시스템으로 카피 
hdfs dfs -put <로컬파일경로> <HDFS경로>
ex) hdfs dfs -put ~/docs/input.txt /user/gaebalja/input.txt

#HDFS 파일 목록 서치
hdfs dfs -ls

 

6. HDFS 파일 권한

HDFS는 POSIX처럼 소유자, 그룹, 그 밖의 사용자에 대한 읽기, 쓰기, 실행 권한을 가지고 있다. HDFS는 파일의 실행을 지원하지 않으므로 파일의 실행권한은 무시되나 디렉터리에 대한 실행권한은 서브 디렉터리 접근을 위해 사용된다. 

 

7. 하둡과 자바

하둡은 자바로 작성되었기 때문에 자바 API를 통해 하둡의 파일시스템과 연동할 수 있다. 후에 다룰 맵리듀스도 자바를 이용해 구현할 수 있다. 이번에는 하둡 파일시스템의 자바 연동 API인 FileSystem 클래스에 대해 설명하겠다.

 

1) 하둡 파일시스템

하둡 파일시스템의 파일은 Path 객체로 표현된다. Path는 하둡 파일시스템 URL을 의미한다. (ex: hdfs://localhost/user)

 

2) 하둡 파일시스템 조작

하둡 파일시스템에 파일을 읽고 쓰기 위해서는 우선 FileSystem 인스턴스를 얻어야 한다. FileSystem 인스턴스를 얻는 메서드는 아래와 같이 세개가 있다. conf은 각종 설정, URI는 파일경로, user는 사용자를 의미한다. 

public static FileSystem get(Configuration conf) throws IOException
public static FileSystem get(URI uri, Configuration conf) throws IOException
public static FileSystem get(URI uri, Configuration conf, String user) throws IOException

 

3) 읽기 (FSDataInputStream)

FileSystem의 open() 메서드는 표준 java.io 클래스가 아닌 FSDataInputStream 클래스를 반환한다. 해당 클래스는 랜덤 접근이 가능해 스트림에서 임의의 부분을 읽을 수 있다. 아래는 리눅스의 cat과 같은 역할을 하는 자바 예제코드다. 참고로 랜덤 접근을 위해서는 FSDataInputStream 클래스의 seek()함수나 PositionedReadable이라는 인터페이스를 사용할 수 있다.

public class FileSystemDoubleCat {
	
    public static void main(String[] args) throws Exception {
    	String uri = args[0];
        Configuration conf = new Configuration();
        //하둡 파일시스템 인스턴스 얻기
        FileSystem fs = FileSystem.get(URI.create(uri), conf);
        FSDataInputStream in = null;
        try{
        	//파일시스템 인스턴스를 사용하여 파일 inputstream 생성
            in = fs.open(new Path(uri));
            //Standard out으로 파일 내용 출력 (리눅스의 cat과 같은 역할)
            IOUtils.copyBytes(in, System.out, 4096, false);
        } finally {
        	IOUtils.closeStream(in);
        }
    }
}

 

4) 쓰기 (FSDataOutputStream)

하둡은 파일을 새로/덮어 쓰거나 기존 파일에 append 하는 쓰기만 허용한다. 아래는 로컬에 있는 파일을 하둡 파일시스템으로 복사하는 예제코드다. 

public class FileCopyWithProgress {
	
    public static void main(String[] args) throws Exception {
    	String localSrc = args[0];
        String dst = ars[1];
        
        InputStream in = new BufferedInputStream(new FileInputStream(localSrc));
        
        Configuration conf = new Configuration();
        //하둡 파일시스템 인스턴스 얻기
        FileSystem fs = FileSystem.get(URI.create(dst), conf);
        //파일 outputstream 생성, create() 메서드는 파일을 생성
        // Progressable 인퍼페이스는 데이터노드의 쓰기 모니터링
        OutputStream out = fs.create(new Path(dst), new Progressable() {
        	public void progress() {
            	System.out.print(".");
            }
        });
        }
        IOUtils.copyBytes(in, out, 4096, true);
    }
}

 

5) 기타

FileSystem에는 디렉터리를 생성할 수 있는 mkdirs() 메서드, 파일의 status를 확인할 수 있는 getFileStatus() 메서드, 파일 목록을 조회할 수 있는 listStatus() 메서드, 파일을 삭제하는 delete() 메서드 등이 있다. 또한 와일드카드도 쓸 수 있다. 자세한 건 구글링바람.

 

8. 데이터 흐름

8의 Java API를 이용한 읽기와 쓰기 연산에서 데이터의 흐름은 다음과 같다. 

 

1) 읽기

① 클라이언트가 open() 메서드를 호출하여 파일을 연다.

② HDFS는 파일의 첫 번째 블록 위치를 얻기 위해 네임노드를 호출한다. 네임노드는 파일에 속한 모든 블록에 대해 블록의 복제본을 가진 데이터노드의 주소를 반환한다. (이때 데이터노드는 클라이언트와 가까운 순서로 정렬되어있다.) 

③ HDFS는 클라이언트에게 FSDataInputStream을 반환하고 클라이언트는 FSDataInputStream 인스턴스를 받아 read() 메서드를 호출한다. 

④ FSDataInputStream은 데이터노드와 네임노드의 I/O를 관리하는 DFSInputStream을 래핑하고 있는데 DFSInputStream은 첫 번째 블록에서부터 마지막 블록까지 순차적으로 접근하여 read() 메서드를 반복적으로 호출한다. 이 과정은 transparent하여 클라이언트 입장에서는 연속적인 스트림을 읽는 것과 같은 효과를 준다. 

⑤ 모든 블록에 대한 읽기가 끝나면 클라이언트는 close() 메서드를 호출한다. 

 

읽기의 핵심은 클라이언트가 데이터노드에 직접적으로 접촉하고 네임노드는 각 블록이 있는 데이터노드의 위치만 알려주는 것이다. 이를 통해 데이터 트래픽은 클러스터의 데이터노드에 고르게 분산되고 네임노드에는 바틀넥이 걸리지 않아 클라이언트의 수를 크게 늘릴 수 있다. 

 

2) 쓰기

① 클라이언트가 create() 메서드를 호출하여 파일을 생성한다.

② HDFS는 파일을 생성하기 위한 요청을 네임노드에게 보낸다. 네임노드가 파일생성을 허용하면  HDFS는 클라이언트에게 FSDataOutputStream을 반환한다. 클라이언트는 FSDataOutputStream 인스턴스를 받아 데이터를 쓴다. 

③ 클라이언트가 데이터를 쓸 때 DFSOutputStream은 데이터를 패킷으로 분리하고 내부 큐인 데이터 큐로 패킷을 전송한다. DataStreamer는 데이터 큐에 있는 패킷에 대해 네임노드에 복제본을 저장할 데이터노드 목록을 요청한다. 레플리케이션 팩터가 2라 하면 DataStramer가 첫 번째 데이터노드로 패킷을 전송하고 첫 번째 데이터노드는 저장된 파일을 두 번째 데이터노드로 보낸다. 

④  DFSOutputStreamdms ack 큐를 가지고 있어 모든 데이터노드로부터 ack응답을 받은 후 패킷을 제거한다. 이때 데이터노드에 장애가 발생할 경우 장애 복구 작업(구글링 참고바람)이 시행된다. 

⑤ 데이터 쓰기가 완련되면 클라이언트는 close() 메서드를 호출하고 모든 패킷이 flush되어 전송되면 네임노드에 파일완료 신호를 보낸다. 

 

하둡은 쓰기에 있어 데이터의 복제본을 균일하게 분배할 수 있도록 한다. 레플리케이션 팩터가 3이라 할때는 다음과 같은 배치전략을 쓴다. 첫 번째 복제본은 클라이언트가 클러스터 내부에 있을 시 클라이언트와 같은 노드에 배치한다. 만약 클라이언트가 외부에 있다면 무작위로 노드를 선택한다. 두 번째 복제본은 첫 번째 노드와 다른 랙의 노드에 배치하고 세 번째 복제본은 두 번째 노드와 같은 랙의 다른 노드에 배치한다. 

728x90
반응형

댓글