HDFS 기초개념

2020-07-16

.

Data_Engineering_TIL(20200715)

  • 학습한 프로그램 : 빅데이터 분석을 위한 Hadoop 프로그래밍

  • URL : https://www.udemy.com/course/draft/1434962/learn/lecture/8559202#overview

1. HDFS

수십테라비이트 또는 페타바이트 이상의 대용량 파일을 분산된 서버에 저장하고, 그 저장된 데이터를 빠르게 처리할 수 있게 하는 파일시스템이다. 또한 저사양의 서버를 이용해서 스토리지를 구성할 수 있어 기존의 대용량파일시스템(NAS,DAS,SAN 등)에 비해 장점을 가진다.HDFS는 블록구조의 파일시스템이다. 파일을 특정크기의 블록으로 나누어 분산된 서버에 저장된다. 블록크기는 하둡 2.0부터는 128메가이다.

2. Master Slave 구조

  • HDFS : 한대의 Name노드(+보조네임노드)와 여러대의 데이터노드

    Name 노드는 보조네임노드에 주기적으로 백업(checkpointing)을 하여 만약의 사태를 대비한다

  • MapReduce : 한대의 JobTracker와 여러대의 TaskTracker

    JobTracker : MapReduce가 실행되면 JobTracker가 처리해야할 task를 각각의 슬레이브들에게 병렬로 처리할 수 있도록 나누어주는 역할

3. HDFS의 4가지 목표

1) 장애복구

노드에 장애가 발생하면 빠르게 인지하고, 대처를 할 수 있도록 구현됨

2) 스트리밍 방식의 데이터 접근

스트리밍 방식으로 데이터에 접근이 가능하도록 설계되어 있음.

이렇게되면 클라이언트들이 실시간으로 끊기지 않고 연속된 흐름으로 데이터에 접근할 수 있다.

3) 대용량 데이터 저장

4) 데이터 무결성 보장

데이터가 입력 또는 변경하였을때 중복성을 없애고 항상 일정한 형태의 데이터의 값이 되도록 유지

4. NameNode의 핵심기능

1) 메타데이터 관리 : 파일시스템을 유지하기 위한 메타데이터를 관리

2) 데이터노드 모니터링 : 데이터노드는 네임노드에게 3초마다 heartbeat를 전송한다. 네임노드는 이를 이용하여 데이터노드의 실행상태와 용량을 체크한다. 하트비트를 전송하지 않는 데이터노드는 장애서버로 판단한다.

3) 블록관리 : 장애가 발생한 데이터노드의 블록을 새로운 데이터노드에 복제한다. 용량이 부족하다면 여유가 있는 데이터노드에 블록을 옮긴다. 블록의 복제본수도 관리한다. 복제본수와 일치하지 않는 블록이 존재할 경우에 블록을 새롭게 복제를 한다거나 삭제를 하기도 한다.

4) 클라이언트 요청접수 : 클라이언트가 HDFS에 접근하려면 반드시 네임노드에 먼저 접속해야 한다. HDFS에 파일을 저장할 경우 기존파일의 저장여부와 권한 확인 절차를 거쳐 저장을 승인한다.

5. HDFS에 파일저장하는 아키텍처

file을 블럭단위로 쪼개서 기본 복제본 3개로 분산저장한다.

파일을 HDFS에 저장할때 기본 복제본은 3개씩 저장한다. 사용자가 임의로 복제본의 수를 지정할 수 있다. 기본값은 3이다.

hdfs

6. HDFS 아키텍처

2

7. DataNode

클라이언트가 HDFS에 저장하는 파일을 로컬 디스크에 유지한다. 이때 파일은 두가지로 저장되는데 하나는 실제 저장되는 로우데이터이고, 다른 하나는 체크섬이나 파일생성일자 같은 메타데이터가 저장된 파일이다.

8. HDFS에 파일저장

STEP 1) 클라이언트에서 먼저 네임노드와 통신과정을 통해 스트림(DFSOutputStream)을 생성한다.

클라이언트는 네임노드에 스트림 요청을 날리고 네임노드에서는 유효성등을 검사해서 승인이 나면 스트림(DFSOutputStream)을 이용할 것이다.

STEP 2) 생성된 스트림을 통해 클라이언트에서 파일을 각 데이터노드에 전송한다. 이때 저장할 파일은 패킷단위로 나누어서 전송한다.

STEP 3) 파일전송이 완료되면 클라이언트에서는 네임노드에서 얻은 스트림을 Close하면 남은 모든 패킷이 Flush된다.

패킷 Flush란 stream의 버퍼에 남아있는 데이터들을 모두 내보낸다는 의미임.

스트림을 close하기 전에 남아있는 패킷들을 다 처리한다는 것이다.

STEP 4) 클라이언트에서 네임노드의 complete 메소드를 호출해서 정상적으로 저장되었다면 True가 반환된다. 그러면 파일저장이 완료된 것이다.

[STEP 1) 상세설명]

3

[STEP 2) 상세설명]

4

Name노드로부터 제어권을 받으면 클라이언트는 file을 네임노드로 전송하는 것이 아니라 데이터노드에게 파일을 전송을 할 것이다. 그리고 file을 패킷단위로 나누게 된다.

DFSoutputstream에는 두개의 객체가 있는데 DataStreamer라는 내장클래스가 있고, ResponseProcessor라는 쓰레드가 있다.

DFSoutputstream에서는 file을 패킷단위로 분할을 먼저 해준다. 그 다음에 전송할 패킷을 내부에 데이터큐라는 곳에 하나하나씩 등록을 하게 된다. 그 다음에 DataStreamer가 NameNode에게 “파일을 저장할테니까 블럭을 추가해달라는 addBlock이라는 메소드를 호출하게 된다. NameNode는 DataStreamer에게 블럭을 저장할 수 있도록 DataNode의 목록을 알려준다. 이 목록에는 데이터를 저장할때 복제본의 수도 있을것이다. 예를들어서 복제본의 수가 3이라고 한다면 클라이언트-DataNode1-DataNode2-DataNode3 이 연결된 형태의 파이프라인이 구성된다.

그러면 DataStreamer는 파이프라인의 첫번째 노드 DataNode1과 연결을 한다. 그다음에 첫번째 노드 DataNode1에 패킷전송을 시작한다. 해당 데이터노드는 패킷을 주고 받기 위해서 DataXceiverServer 데몬을 구동하게 된다. DataXceiverServer 데몬은 클라이언트로부터 패킷을 받고 또는 보내는 역할을 한다.

첫번째 데이터노드에서 데이터를 저장을 하고 두번째 데이터노드에게 패킷저장을 요청하게 된다. 두번째 데이터 노드도 DataXceiverServer 데몬이 동작하게 된다. 두번째 데이터노드에서 데이터를 저장을 하고 세번째 데이터노드에게 패킷저장을 요청하게 된다. 세번째 데이터노드도 DataXceiverServer가 패킷을 주고받을 수 있도록 한다.

첫번째 데이터노드에 데이터를 저장할때 DFSoutputstream 내부의 큐에 승인큐라는 패킷을 등록하게 되고 승인큐라는 것은 데이터 전송이 완료되어 완료되었다는 응답을 받게되면 승인큐라는 해당패킷이 자동으로 사리지게 된다. 정상적으로 file이 각 데이터노드에 저장이 되면 순차적으로 응답을 한다.응답은 Ack로 해주게 된다.

그 다음에 각각의 데이터 노드는 네임노드에게 정상적으로 잘 저장이 되었다는 것을 알려줘야 한다. 각각의 데이터노드들은 역시 Ack 메세지로 네임노드에게 알려준다. 그런다음에 저장완료 결과정보를 BlockReceived라는 메소드를 호출하여 네임노드에게 알려주게 된다.

그러면 DFSOutputStream에서는 각각의 데이터노드들로부터 Ack 메세지를 받게되면 해당 패킷을 내부의 승인큐에서 제거한다. 만약에 패킷전송중에 문제가 발생하면 승인큐에 있는 모든 패킷을 다시 데이터 큐로 이동시키고 네임노드에서는 장애가 발생한 데이터노드외에 새로운 데이터노드를 알려주게 된다. 그 새로운데이터노드 정보를 DFSOutputStream에서 받아서 다시 파이프라인을 형성하게 될 것이다. 그 다음에 다시 패킷 전송작업을 수행할 것이다. 정상적으로 완료되면 ResponseProcessor라는 쓰레드가 승인큐에 대한 내용을 제거해준다. 큐에 있는 내용이 전부 제거되면 정상적으로 file 저장작업이 완료된 것이다.

[STEP 3) ~ 4) 상세설명]

5

Close 메소드를 호출했을때 DFSoutputstream의 버퍼상에 남아있는 모든 패킷들을 파이프라인을 통해서 전부 데이터노드에 내보낸다. 이를 Flush 작업이라고 한다.

4번 NameNode의 complete 메소드를 호출하면 네임노드가 패킷이 전부 데이터노드에 정상저장이 되었는지 확인한다. 네임노드에서는 최소한의 복제본수 설정값을 만족하면 정상적으로 저장된것으로 간주한다. 예를들어서 복제본수 설정값이 3인데 4개의 복제본이 저장되어도 True를 리턴한다.

5번 True값이 리턴되면 stream 객체는 file 저장이 완료된것으로 처리한다.

8. HDFS에 파일읽기

  • 클라이언트에서 네임노드의 입력스트림객체(DFSInputStream)를 통해 스트림객체를 생성한다.

DFSInputStream가 있어야만 이 통로로 데이터를 읽어올 것이다.

생성된 스트림 객체를 이용하여 기본 블록의 10배수만큼 조회한다.

  • 클라이언트에서 스트림객체에서 블록리더기 생성하는데 블록이 저장된 데이터노드가 같은 서버에 있다면 블록리더기(BlockReaderLocal)를 생성하고, 원격에 있다면 원격블록리더기(RemoteBlockReader)를 생성한다.

  • DFSInputStream은 파일모두 읽을때까지 블록을 조회한다. 모두 읽었다면 Close를 통해 닫아주어야 한다.

stream 객체는 항상 생성하고 사용한 다음에는 close를 해줘야 한다.

[HDFS에 파일읽는 과정 상세내용]

1번 클라이언트가 DistributedFilesystem 클래스에 스트림을 요청한다.그래서 DistributedFilesystem의 Open메서드를 호출한다.

그 다음에 DistributedFilesystem에서 FSDataInputStream이라는 객체를 먼저 생성한다.

2번 DFSInputStream 객체를 생성하는데 DFSInputStream를 생성하기 위해서 DSClient의 Open 메소드를 호출한다.

그 다음에 FSDataInputStream은 DFSDataInputStream 객체와 DFSInputStream를 차례대로 lapping한다.

3번 DFSClient가 생성한 DFSInputStream 객체로 NameNode에게 File을 불러오기 위한 블럭의 위치를 요청한다. 이때 getBlockLocations 메소드를 사용한다.

클라이언트는 네임노드로부터 파일의 제어권을 넘겨받는 과정 한번만 통신한다.

4번 NameNode는 블록의 위치정보를 DFSInputStream로 return한다.

6

file을 읽을때 모든블럭의 위치를 다 조회하지 않고 10배수 길이만큼의 블록을 조회한다. 예를들어서 128MB가 기본블록 단위라면 1280MB 블록의 길이를 조회한다.

5번 그래서 DistibutedFilesystem은 블록위치를 네임노드로부터 반환받게 되는데 이거를 클라이언트에게 DFSInputStream 객체형태로 return 해주게 된다.

DFSInputStream는 DFSDataInputStream으로 lapping이 되어있는 상태이다. 즉 내부에 DFSInputStream가 있고 외부에 DFSDataInputStream가 감싸고 있는 형태이다.

그런다음에 실제 블록을 조회하는 과정으로 진행된다.

7

네임노드에서 블록위치정보를 DFSInputStream이 받으면 그 위치정보를 가지고 리더기를 로컬리더기로 생성할지 원격리더기으로 생성할지 결정한다. 그래서 로컬리더기를 생성하거나 원격리더기를 생성하거나 둘중 하나를 생성하게 된다. 원격리더기인 경우에 데이터노드가 같은서버에 있는 경우에 로컬리더기로 블록조회를 하고 그렇지 않는 경우 원격리더기를 생성해서 블록을 조회한다.

각각의 데이터 노드 안에는 DataXceiverServer가 데몬으로 돌고 있다. 데이터노드가 로컬인 경우 로컬리더기가 DataXceiverServer가 블록을 읽어서 DFSDataInputStream에게 보내준다. 데이터노드가 원격인 경우 원격리더기가 해당 데이터노드에 요청을 하면 DataXceiverServer가 원격리더기를 거치는게 아니라 다이렉트로 DFSDataInputStream에게 블록을 반환한다.

그런 다음에 DFSDataInputStream는 조회한 데이터에 대한 오류검사를 한다. 오류검사는 체크섬을 조회를 하게 되는데 오류가 있는경우 다른 데이터노드에게 블록조회를 요청한다.

마지막으로 DFSDataInputStream을 Close 해줘야 한다. close 메소드를 이용해서 블록리더기를 닫고, 원격리더기도 닫고 그런다음에 데이터노드와의 커넥션도 종료하게 된다.

그런데 블록을 모두 읽었는데(10배수 만큼의 블록을 조회했을때) 파일을 다 읽지 못했다면 다시 네임노드에게 필요한 블록에 대한 위치조회를 해서 데이터를 읽어오는 과정을 거치게 된다.

9. 보조네임노드(Secondary Name Node)

네임노드가 메타데이터를 메모리에 담고 처리하는데 만약에 서버가 리부팅되면 사라질 것이다. HDFS는 이러한점 때문에 editslog과 fsimage라는 두개의 파일을 생성한다. editslog는 HDFS의 모든 변경이력을 저장하고, fsimage는 메모리에 저장된 메타데이터를 파일시스템 이미지를 저장한 파일이다. 그런데 만약 editslog가 커지면 fsimage를 만드는데 시간이 오래 소요될 것이다.

이러한 문제를 해결하기 위해서 보조네임노드가 있다. 보조네임노드는 fsimage를 갱신해준다. 이러한 작업을 체크포인트라고 한다. 그래서 보조네임노드를 체크포인팅 서버라고도 한다. 보조네임노드는 네임노드의 백업이 아니고 단순히 fsimage를 줄여주는 역할만 한다. fsimage가 너무커서 네임노드가 메모리에 로딩되지 못하는 경우를 예방하기 위해 사용되는 것이다.

네임노드에 장애나 문제가 발생했을때 최소한의 시간에서 복구를 할 수 있도록 해주는 역할이다. 주의해야할 점은 보조네임노드는 장애가 발생하면 복구를 해주는 용도이지 네임노드를 통째로 백업을 하는 용도는 아니다. 즉, 보조네임노드는 fsimage를 갱신해줌으로써(사이즈를 줄여줌으로써) 네임노드에 장애가 발생하지 않도록 서포트 하는 역할임.

** 참고사항

editslog는 파일크기나 변동이력 저장제한사항이 없어서 갱신을 안해주면 계속 커질 것이다.

사실 그래서 네임노드 하나만 있어도 상관이 없는데 문제는 네임노드 하나만 있으면 editslog가 점점 커져서 언젠가는 감당이 안되는 수준까지 다다를 것이라는 것이다.

10. 데이터노드로 부터 받은 블록 레포트들을 메모리에 로딩된 fsimage에 적용하는 과정

STEP 1) 네임노드가 처음에 부팅되면 로컬에 저장되어 있는 두개의 파일, fsimage과 editslog를 조회한다. 만약에 editslog 변경이력이 있다면 새로운 fsimage을 만들어줘야 한다.

STEP 2) 조회를해서 fsimage를 메모리에 로딩한다.

STEP 3) fsimage(현재 메모리에 올라와있는)에 editslog에 변경이력이 있으면 fsimage에 변경이력을 적용한다.

STEP 4) fsimage(로컬에 있는 file)을 새로운 fsimage(로컬에 있는 file)로 update해준다.

STEP 5) editslog file을 초기화

11. checkpointing

체크포인팅은 기본값으로 1시간이다. 다시말해서 1시간마다 한번씩 체크포인팅이 일어나는 것이 기본옵션이다.

8

STEP 1) 보조네임노드는 네임노드에게 editslog 파일을 rolling 해달라는 요청을 한다. 이거를 로그롤링 요청이라고 한다. 이게 뭐냐면 서버에는 로그파일이 있는데 그 로그파일의 이름을 변경하고 원래이름으로 다시 새로운 로그파일을 만드는 것을 말한다. 로그파일을 백업하기 위한 과정으로 생각하면 된다.

STEP 2) 네임노드에 있는 editslog을 예를들어서 editslog_20200715 로 만들고, 그다음에 그 파일을 editslog.new로 만든다.

STEP 3) 그러면 보조네임노드는 editslog_20200715와 fsimage를 네임노드로부터 다운로드 받는다.

STEP 4) 보조네임노드는 다운로드 받은 editslog_20200715와 fsimage를 병합해서 새로운 fsimage.ckpt라는 파일을 만들어준다.

STEP 5) 생성한 fsimage.ckpt를 네임노드로 전송한다.

STEP 6) 네임노드는 fsimage.ckpt를 이용해서 기존의 fsimage 파일을 갱신하는 새로운 fsimage 파일을 생성한다.

STEP 7) 네임노드는 갱신된 fsimage 파일을 메모리에 로드해서 사용한다.

STEP 8) 네임노드는 editslog.new 파일을 editslog 파일로 만들어준다. 로그롤링이 된 것이고, 변동내역이었던 editslog 파일이 초기화가 된 것이다.