6.1 환경 설정 API
Configuration 클래스 인스턴스: 환경 설정 속성 & 값의 집합
<?xml version="1.0"?>
<configuration>
<property>
<name>color</name>
<value>yellow</value>
<description>Color</description>
</property>
<property>
<name>size</name>
<value>10</value>
<description>Size</description>
</property>
<property>
<name>weight</name>
<value>heavy</value>
<final>true</final>
<description>Weight</description>
</property>
<property>
<name>size-weight</name>
<value>${size},${weight}</value>
<description>Size and weight</description>
</property>
</configuration>
6.1.1 리소스 결합하기
환경설정: 하나 이상의 리소스 파일(XML) 사용 가능
- 기본 속성: core-default.xml 파일
- 특정 속성: core-site.xml 파일
<?xml version="1.0"?>
<configuration>
<property>
<name>size</name>
<value>12</value>
</property>
<property>
<name>weight</name>
<value>light</value>
</property>
</configuration>
나중에 추가된 리소스에 정의된 속성: 이전에 정의된 속성 오버라이드 (size)
final 지정 속성: 오버라이드 ❌, 경고 메시지 로그 (weight)
➡️ 클라이언트의 설정 파일/잡 제출 매개변수 final 지정 ➡️ 사용자 변경 ❌
6.1.2 변수 확장
환경 설정 속성: 다른 속성/시스템 속성 정의 가능
size-weight 속성 ➡️ ${size}, ${weight} 정의
6.2 개발환경 설정하기
프로젝트 생성 ➡️ 맵리듀스 프로그램 명령행/IDE 환경 빌드 ➡️ 로컬(독립) 모드 실행
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.hadoopbook</groupId>
<artifactId>hadoop-book-mr-dev</artifactId>
<packaging>jar</packaging>
<version>4.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hadoop.version>3.3.6</hadoop.version>
</properties>
<dependencies>
<!-- Hadoop main client artifact -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
<!-- Unit test artifacts -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.mrunit</groupId>
<artifactId>mrunit</artifactId>
<version>1.1.0</version>
<scope>test</scope>
<classifier>hadoop2</classifier>
</dependency>
<!-- Hadoop test artifact for running mini clusters -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-minicluster</artifactId>
<version>${hadoop.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>hadoop-examples</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<outputDirectory>${basedir}</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>
만약 POM에서 정의한 의존성과 동일) 빌드 도구를 사용하는 것이 직관적
맵리듀스 잡 빌드: hadoop-client(하둡의 클라이언트 클래스 모두 포함) 의존성만 필요
- 단위 테스트: junit 사용
- 맵리듀스 테스트: mrunit 사용
hadoop-minicluster 라이브러리: '미니' 클러스터(단일 JVM으로 실행되는 하둡 클러스터에서 테스트할 때 유용) 포함
6.2.1 환경 설정 파일 관리하기
하둡 애플리케이션: 로컬 모드/클러스터 모드 번갈아가며 테스트
환경 설정 파일(각 클러스터의 연결 정보 가짐) 생성 ➡️ 하둡 애플리케이션/도구 실행 시 하나 지정
로컬 모드(hadoop-local.xml): 기본 파일 시스템 & 로컬 프레임워크(맵리듀스 잡 실행)에 적합
<?xml version="1.0"?>
<configuration>
<property>
<name>fs.defaultFS</name>
<value>file:///</value>
</property>
<property>
<name>mapreduce.framework.name</name>
<value>local</value>
</property>
</configuration>
의사 분산 모드(hadoop-localhost.xml): 로컬에서 작동하는 네임노드 & YARN 리소스 매니저 위치 설정
<?xml version="1.0"?>
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000/</value>
</property>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>yarn.resourcemanager.address</name>
<value>localhost:8032</value>
</property>
</configuration>
완전분산 모드(hadoop-cluster.xml): 클러스터의 네임노드 & YARN 리소스 매니저의 주소
<?xml version="1.0"?>
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://namenode/</value>
</property>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>yarn.resourcemanager.address</name>
<value>resourcemanager:8032</value>
</property>
</configuration>
사용자 인증 설정
하둡: 사용자 HDFS의 접근 권한 확인
if 하둡의 사용자 이름 != 클라이언트 머신의 사용자 계정) HADOOP_USER_NAME 환경 변수에 사용자 이름 명시적 설정
-conf + 파일 명시적 지정: 원하는 환경 설정 쉽게 이용
% hadoop fs -conf conf/hadoop-localhost.xml -ls .
6.2.2 GenericOptionsParser, Tool, ToolRunner
헬퍼 클래스: 명령행에서 잡을 쉽게 실행
- GenericOptionsParser 클래스: 명령행 옵션 해석 ➡️ Configuration 객체에 값을 설정
(Tool 인터페이스 구현 애플리케이션 작성 ➡️ ToolRunner(GenericOptionsParser를 내부에서 호출)로 프로그램 실행하는 것이 편리)
public class ConfigurationPrinter extends Configured implements Tool {
static {
Configuration.addDefaultResource("hdfs-default.xml");
Configuration.addDefaultResource("hdfs-site.xml");
Configuration.addDefaultResource("mapred-default.xml");
Configuration.addDefaultResource("mapred-site.xml");
}
@Override
public int run(String[] args) throws Exception {
Configuration conf = getConf();
for (Entry<String, String> entry: conf) {
System.out.printf("%s=%s\n", entry.getKey(), entry.getValue());
}
return 0;
}
public static void main(String[] args) throws Exception {
int exitCode = ToolRunner.run(new ConfigurationPrinter(), args);
System.exit(exitCode);
}
}
//Tool(Configuration 객체 내부의 속성 출력) 구현
Configured 클래스(Configurable 인터페이스 구현) 상속 ➡️ ConfigurationPrinter 만듦
run 메서드: getConf() 메서드 ➡️ Configuration 객체 반복적으로 얻고 모든 속성 표준 출력으로 출력
정적 블록: 핵심 환경 설정 외에 HDFS, YARN, 맵리듀스 환경 설정 명시적으로 추가
main() 메서드: 자신의 run() 메서드 직접 호출 ❌, Toolruner의 run() 정적 메서드 호출
Toolrunner: GenericOptionsParser 사용 ➡️ 명령행에 명시된 표준 옵션 추출 ➡️ Configuration 객체 설정
GenericOptionsParser: 개별 속성 지정 기능 제공
% hadoop ConfigurationPrinter -D color=yellow | grep color
color=yellow
우선순위: -D 명시 옵션 > 환경 설정 파일에서 설정한 속성 ➡️ 기본값: 환경 설정 파일에 정의, -D 옵션으로 필요할 때 값 재정의
옵션 | 설명 |
-D property=value | 지정된 하둡 환경 설정 속성 ➡️ 주어진 값으로 설정 기본/사이트 속성(환경 설정 파일에 존재) + -conf 옵션 ➡️ 설정한 모든 속성을 오버라이드 |
-conf filename | 환경 설정의 리소스 목록 + 지정 파일 |
-fs uri | 지정된 URI로 기본 파일시스템 설정 |
-jt host:port | 지정된 호스트/포트 ➡️ YARN 리소스 매니저 설정 |
-files file1, file2, ... | 지정된 파일: 공유 파일시스템으로 복사 ➡️ 맵리듀스 프로그램 사용 가능 |
-archives archive1, archive2, ... | 지정된 아카이브: 공유 파일시스템으로 복사, 아카이브 해제 |
-libjars jar1, jar2, ... | 지정된 JAR 파일: 공유 파일시스템으로 복사, 맵리듀스 태스크의 클래스패스에 추가 |
6.3 엠알유닛으로 단위 테스트 작성하기
맵 & 리듀스 함수: 개별적으로 테스트 용이(함수형 스타일의 장점)
엠알유닛: 테스트 라이브러리(매퍼, 리듀서 데이터 전달 ➡️ 예상대로 출력되는지 점검)
6.3.1 매퍼
//매퍼 테스트 코드
import java.io.IOException;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mrunit.mapreduce.MapDriver;
import org.junit.*;
public class MaxTemperatureMapperTest {
@Test
public void processesValidRecord() throws IOException {
MaxTemperatureMapper mapper = new MaxTemperatureMapper();
Text value = new Text("0043011990999991950051518004+68750+023550FM-12+0382" +
// Year ^^^^
"99999V0203201N00261220001CN9999999N9-00111+99999999999");
// Temperature ^^^^^
OutputCollector<Text, IntWritable> output = mock(OutputCollector.class);
mapper.map(null, value, output, null);
verify(output).collect(new Text("1950"), new IntWritable(-11));
}
@Test
public void ignoresMissingTemperatureRecord() throws IOException {
MaxTemperatureMapper mapper = new MaxTemperatureMapper();
Text value = new Text("0043011990999991950051518004+68750+023550FM-12+0382" +
// Year ^^^^
"99999V0203201N00261220001CN9999999N9+99991+99999999999");
// Temperature ^^^^^
OutputCollector<Text, IntWritable> output = mock(OutputCollector.class);
mapper.map(null, value, output, null);
Text outputKey = anyObject();
IntWritable outputValue = anyObject();
verify(output, never()).collect(outputKey, outputValue);
}
}
테스트 방법: 입력 - 날씨 레코드 ➡️ 출력값 확인(입력 연도, 기온 일치)
MapDriver 사용 ➡️ 매퍼 테스트
- MaxTemperatureMapper 인스턴스 설정 ➡️ (입력키 & 값), (예상되는 출력키 & 값) 설정 ➡️ runTest() 함수 호출
//MaxTemperatureMapperTest를 통과한 첫 번째 버전의 매퍼
public class MaxTemperatureMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String string = value.toString();
String year = line.substring(15, 19);
int airTemperature = Integer.parseInt(line.substring(87, 92));
context.write(new Text(year), new IntWritable(airTemperature));
}
}
//원복 데이터에서 +9999로 나타내는 결측값 테스트 코드 추가
@Test
public void ignoresMissingTemperatureRecord() throws IOException, InterruptedException {
Text value = new Text("0043011990999991950051518004+68750+023550FM-12+0382" +
//Year ^^^^
"99999V0203201N00261220001CN9999999N9+99991+99999999999");
//Temperature^^^^^
new MapDriver<LongWritable, Text, Text, IntWritable>()
.withMapper(new MaxTemperatureMapper())
.withInput(new LongWritable(0), value)
.runTest();
}
MapDriver: withOutput() 함수의 호출 횟수에 따라 0개 이상 출력 레코드 점검
+9999 특별 입력 다루지 ❌ ➡️ 새로운 테스트 실패
매퍼 로직 추가하는 것보다 파서 클래스(파싱 로직 포함)에서 처리하는 것이 좋음 🔽
//Ncdc 날씨 레코드 파싱 클래스
public class NcdcRecordParser {
private static final int MISSING_TEMPERATURE = 9999;
private String year;
private int airTemperature;
private String quality;
public void parse(String record) {
year = record.substring(15, 19);
String airTemperatureString;
if (record.charAt(87) == '+')
airTemperatureString = record.substring(88, 92);
else
airTemperatureString = record.substring(87, 92);
airTemperature = Integer.parseInt(airTemperatureString);
quality = record.substring(92, 93);
}
public void parse(Text record) {
parse(record.toString());
}
public boolean isTemperatureValid() {
return airTemperature != MISSING_TEMPERATURE && quality.matches("[01459]");
}
public String getYear() {
return year;
}
public int getAirTemperature() {
return airTemperature;
}
}
입력 텍스트 행으로부터 parse() 메서드(입력 텍스트 행으로부터 관심 있는 필드 파싱하는 파서) 호출
➡️ isValidTemperature() 메서드로 기온값이 유효한지 점검
public class MaxTemperatureMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private NCDCRecordParser parser = new NCDCRecordParser();
@Override
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
parser.parse(value);
if (parser.isValidTemperature())
context.write(new Text(parser.getYear()), new IntWritable(parser.getAirTemperature()));
}
}
6.3.2 리듀서
리듀서: 입력받은 키의 최대 기온값 찾음
구현 코드 🔽
@Test
public void returnsMaximumIntegerInValues() throws IOException, InterruptedException {
new ReduceDriver<Text, IntWritable, Text, IntWritable>()
.withReducer(new MaxTemperatureReducer())
.withInput(new Text("1950"), Arrays.asList(new IntWritable(10), new IntWritable(5)))
.withOutput(new Text("1950"), new IntWritable(10))
.runTest();
}
}
테스트를 통과한 MaxTemperatureReducer 구현
public class MaxTemperatureReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int maxValue = Integer.MIN_VALUE;
for (IntWritable value : values) {
maxValue = Math.max(maxValue, value.get());
}
context.write(key, new IntWritable(maxValue));
}
}
6.4 로컬에서 실행하기
6.4.1 로컬 잡 실행하기
public class MaxTemperatureDriver extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
if (args.length != 2) {
System.err.printf("Usage: %s [generic options] <input> <output>\n", getClass().getSimpleName());
ToolRunner.printGenericCommandUsage(System.err);
return -1;
}
Job job = new Job(getConf(), "Max temperature");
job.setJarByClass(getClass());
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.setMapperClass(MaxTemperatureMapper.class);
job.setCombinerClass(MaxTemperatureReducer.class);
job.setReducerClass(MaxTemperatureReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
return job.waitForCompletion(true) ? 0 : 1;
}
public static void main(String[] args) throws Exception {
int exitCode = ToolRunner.run(new MaxTemperatureDriver(), args);
System.exit(exitCode);
}
}
MaxTemperatureDriver: Tool 인터페이스의 구현체, GenericOptionsParser가 제공하는 옵션 설정 기능 이용
run() 메서드: Tool(잡 시작될 때 사용)의 환경 설정 기반 ➡️ Job 객체 생성
잡 환경 설정 中 입출력 파일 경로, 매퍼, 리듀서, 컴바이너 클래스, 출력 타입 설정
- 입력 타입: TextInput Format(기본 입력 포맷) - LongWritable 키 & Text 값
하둡: 맵리듀스 실행 엔진의 축소 버전 ➡️ 단일 JVM에서 동작하는 로컬 잡 실행자(테스트 용도) 제공
환경 설정에서 mapreduce.framework.name 속성: local(기본값)로 설정 ➡️ 로컬 잡 실행자(모든 파일 시스템에서 작동) 사용
% mvn compile
% export HADOOP_CLASSPATH=target/classes/
% hadoop v2.MaxTemperatureDriver -conf conf/hadoop-local.xml \input/ncdc/micro output
+) -fs, -jt 옵션 사용 방법 (GenericOptionsParser 제공)
% hadoop v2.MaxTemperatureDriver -fs file:/// -jt local /input/ncdc/micro output
MaxTemperatureDriver(로컬 input/ncdc/micro 디렉터리의 데이터 입력 ➡️ 로컬 output 디렉터리에 결과 저장) 실행
6.4.2 드라이버 테스트하기
Tool 구현 애플리케이션 제장의 장점: 환경 설정 옵션의 유연성, Configuration 객체 직접 입력 ➡️ 테스트 용이
테스트 코드(로컬 잡 실행자에 테스트용 입력 데이터 입력 ➡️ 출력 결과 점검) 작성 시 활용 가능
i) 로컬 잡 실행자 사용 ➡️ 테스트 파일(in 로컬 파일시스템)로 실행
@Test
public void test() throws Exception {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "file:///");
conf.set("mapreduce.framework.name", "local");
conf.setInt("mapreduce.task.io.sort.mb", 1);
Path input = new Path("/mnt/sda6/NCDC.txt");
Path output = new Path("/mnt/sda6/output");
FileSystem fs = FileSystem.getLocal(conf);
fs.delete(output, true);
MaxTemperatureDriver driver = new MaxTemperatureDriver();
driver.setConf(conf);
int exitCode = driver.run(new String[]{
input.toString(), output.toString()
});
assertThat(exitCode, is(0));
checkOutput(conf,output);
}
}
fs.defaultFS & mapreduce.framework.name 명시적 설정 ➡️ 로컬 파일시스템 & 로컬 잡 실행자 사용
MaxTemperatureDriver(Tool 인터페이스 구현) 작은 입력 데이터로 실행
checkOutput() 메서드: 실제 출력 결과 & 예상 결과 한 줄씩 비교
ii) '미니' 클러스터에서 수행
하둡: MiniDFS, Cluster, MiniMRCluster, MiniYARNCluster 테스트 클래스 제공 ➡️ 단일 프로세스 클러스터 생성 가능 + HDFS, 맵리듀스, YARN과 기술적으로 똑같은 환경에서 테스트 가능
+) 미니 클러스터: 사용자 코드 테스트할 때 사용 가능 (ClusterMaprReduceTestCase: 테스트 코드 작성을 위한 원형)
- setUp(), tearDown(): 단일 프로세스 HDFS & YARN 클러스터 시작/중단 제어
- Configuration 객체 생성
➡️ 회귀 검사, 입력 극한 사례를 위한 테스트 저장소로 유용
6.5 클러스터에서 실행하기
~6.4: 작은 테스트 데이터셋 확인
6.5~: 하둡 클러스터의 전체 데이터셋
6.5.1 잡 패키징
로컬 잡 실행자: 단일 JVM 사용 ➡️ 잡에 필요한 모든 클래스: 로컬의 클래스 경로에 존재하면 잘 작동함
분산 환경: 필요한 모든 클래스 잡 JAR 파일에 패키징 ➡️ 클러스터로 전송
잡 JAR 파일: 빌드 도구(앤트, 메이븐)를 사용해 쉽게 생성
POM 이용하여 메이븐 명령어 실행 ➡️ hadoop-examples.jar 파일(프로젝트 디렉터리에 컴파일된 클래스 모두 포함) 생성
% mvn package -DskipTests
JAR 파일에 잡 한 개 ➡️ JAR 파일의 manifest에 메인 클래스 지정 or 명령행 지정
클라이언트 클래스경로
hadoop jar <jar>로 지정하는 클라이언트 클래스경로
= 잡 JAR 파일 + 잡 JAR 파일의 lib 디렉터리에 있는 모든 JAR 파일과 classes 디렉터리+ HADOOP_CLASSPATH에 정의한 클래스 경로
태스크 클래스경로
클러스터에서 맵 & 리듀스 태스크: 개별 JVM으로 실행 ➡️ 클래스 경로로 지정해도 소용 ❌
태스크 클래스경로
= 잡 JAR 파일 + 잡 JAR 파일의 lib 디렉터리에 있는 모든 JAR 파일과 classes 디렉터리 + -libjars 옵션/DistributedCache/Job의 addFileToClassPath() 메서드를 사용해 분산 캐리세 추가한 모든 파일
의존 라이브러리 패키징
- 라이브러리 풀기 ➡️ JAR에 넣고 다시 패키징
- 잡 JAR의 lib 디렉터리에 라이브러리 패키징
- 잡 JAR와 다른 위치에 라이브러리 지정 ➡️ HADOOP_CLASSPATH & -libjars 이용 ➡️ 클라이언트의 클래스경로 & 태스크의 클래스 경로 각각 추가
➡️ 의존 라이브러리 잡 JAR에 다시 넣지 않아도 됨 ➡️ 가장 빌드하기 간단, 전송할 JAR 파일 개수 ⬇️
태스크 클래스경로의 우선순위
사용자 JAR 파일: 클라이언트 클래스경로 & 태스크 클래스 경로 마지막에 추가 ➡️ 내장 라이브러리와 충돌 가능 ➡️ 태스크 클래스 경로 제어 ➡️ 사용자 클래스 먼저 선택
HADOOP_USER_CLASSPATHFIRST 환경 변수 = true ➡️ 사용자 클래스 경로: 강제로 하둡의 클래스 경로 검색 순서의 맨 앞
mapreduce.job.user.classpath.first 속성 = true
* 잡 제출/태스크 실패 가능 유의
6.5.2 잡 구동하기
잡 구동하기 위해 드라이버 실행: -conf 옵션 ➡️ 실행할 클러스터 지정
% unset HADOOP_CLASSPATH
% hadoop jar hadoop-examples.jar v2.MaxTemperatureDriver \ -conf conf/hadoop-Cluster.xml input/ncdc/all mzx-temp
watForcompletion(): 잡 구동 후 진행 상황 주기적으로 보고, 맵 & 리듀스 처리 과정에 변화 ➡️ 요약하여 출력
실행 결과: ID 출력, (잡 완료 시) 카운터의 통계 정보 출력
6.5.3 맵리듀스 웹 UI
리소스 매니저 페이지
'클러스터 메트릭스': 클러스터에서 현재 실행 중인 애플리케이션 개수, 클러스터 가용 자원의 용량, 노드 매니저 정보
메인 테이블: 클러스터에서 완료/실행 중인 모든 애플리케이션, 검색 창
잡 히스토리 페이지: 리소스 매니저에서 삭제된 애플리케이션(영속적)
맵리듀스 잡 페이지
Tracking UI 클릭 ➡️ 애플리케이션 마스터의 웹 UI로 이동
실행 중인 잡의 진행 상황 확인
테이블: 맵 & 리듀스의 진행 상황
테이블의 마지막 부분: 맵 / 리듀스 태스크의 실패/죽은 태스크 시도의 전체 개수 (killed)
Configuration 링크 클릭 ➡️ 통합 설정 파일(잡을 실행할 때 영향을 미치는 모든 속성 & 값을 가짐)로 이동
'Data > Hadoop' 카테고리의 다른 글
[하둡, 하이브로 시작하기] 2. 하둡(hadoop) (~2.2 HDFS) (0) | 2024.06.03 |
---|---|
[하둡, 하이브로 시작하기] 1. 빅데이터 (0) | 2024.05.26 |
[하둡 완벽 가이드] Chapter 4 하둡 I/O (0) | 2024.05.05 |
[하둡] 하둡 설치하기 (0) | 2024.04.07 |
[하둡 완벽 가이드] Chapter 3 하둡 분산 파일 시스템 (0) | 2024.03.24 |