2. 스톰Storm
● 데이터의 스트림을 처리하는 시스템
○ 실시간 데이터 처리 목적
○ 예 - 주식 실시간 트레이딩, 기계 데이터 분석
● 주요 특징
○ 프로세싱 병렬 실행
■ 클러스터에서 연산 수행
○ 안정성
■ 데이터의 처리를 보장
○ 내고장성(fault-tolerant)
■ 프로세스 재시작으로 작업 재할당
4. 스톰 토폴로지
토폴로지(topology):
데이터 스트림 처리를 위한 작업 단위
스파우트(Spout):
스트림 데이터를
볼트에 전달.
외부 메시징 시스
템 등에서 데이터
가져옴
이미지 출처: http://storm-project.net/
볼트(Bolt):
데이터를 처리해서
다른 볼트에 전달
하거나 외부에 저
장
5. 스트림의 데이터: 튜플
튜플은 [이름:값, 이름:값, 이름:값, …] 형식의 데이터
튜플
튜플
튜플
볼트는 튜플을 입력으로 받아 연산 수행 후,
새로운 튜플을 생성해서 다른 볼트에 전달 또는
연산 결과를 별도 처리(DB 저장 등)
6. 스톰 클러스트
● 토폴로지를 실행
님버스(Nimbus):
토폴로지의 각 태스크를 Worker에
할당하고, 모니터링
수퍼바이저(Supervisor):
토폴로지의 태스크를 실행하기 위
해 worker 프로세스 생성
토폴로지:
jar 파일
Nimbus
(master)
주키퍼(Zookeeper):
토폴로지의 실행 상태 보관
Zookeeper 클러스터
Supervisor
Supervisor
Supervisor
7. 개발자가 해야 할 일
● 스톰 클러스터 구축
● 코드 구현
○ 스파우트 구현
○ 볼트 구현
○ 토폴로지 구성
-- 토폴로지 구성 예
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("word-reader", new WordReader());
builder.setBolt("word-normalizer", new WordNormalizer()).shuffleGrouping("word-reader");
builder.setBolt("word-counter", new WordCounter()).shuffleGrouping("word-reader");
word-reader
word-normalizer
word-counter
8. 병렬 실행
● 워커worker 프로세스
○
클러스터에서 생성되는 JVM 프로세스, 토폴로지 단위로 설정 가능
● executor
○
○
스파우트/볼트를 실행하는 쓰레드, worker가 생성
스파우트/볼트 단위로 개수 지정, 지정하지 않으면 1개 사용
● 태스크task
○
○
스파우트/볼트 당 생성할 작업(인스턴스) 개수
지정하지 않으면 executor 당 1개
builder.setBolt("word-normalizer", new WordNormalizer(), 4) // 4개의 쓰레드
.setNumTasks(8) // 8개의 작업 생성
.shuffleGrouping("word-reader");
conf.setNumWorkers(2); // worker 프로세스 2개
* 1개 worker에서 2개 쓰레드 실행 / 1개 쓰레드가 2개의 태스크를 실행
* 즉, 각 worker에서 4개의 태스크 실행
9. 스트림 그룹핑
● 태스크가 2개 이상일 때 각 태스크에 튜플을
분배하는 기준 필요
1번 태스크로 전
달?
word-reader 스파우트
word-normalizer 볼트
튜플
튜플
1번 태스크
2번 태스크
10. 스트림 그룹핑 종류
그룹핑
설명
Shuffle
(None도 동일)
각 튜플을 랜덤하게 볼트 태스크로 보낸다. 각 태스크에 보내지
는 튜플은 균등한다.
Field
튜플의 지정한 필드 값을 이용해서 볼트 태스크를 선택한다. 동
일 필드 값을 가진 튜플은 같은 볼트 태스크로 전달된다.
All
동일 튜플이 모든 볼트 태스크에 전달된다.
Direct
튜플을 생성하는 쪽에서 직접 볼트 태스크를 지정한다.
Global
n개의 태스크 중, 작은 ID를 가진 태스크로 튜플을 전달한다.
* Global Grouping 사용 이유에 대한 힌트:
https://groups.google.com/forum/#!msg/storm-user/JalFsPvksxw/l4RnjM7qI0wJ
-- 코드 샘플
builder.setBolt("word-normalizer", new WordNormalizer(), 4).shuffleGrouping("word-reader");
builder.setBolt("word-counter", new WordCounter(), 2).fieldGrouping("word-reader", new Field(“word”));
11. 스파우트와 볼트 구현 방식
public class WordReader extends BaseRichSpout {
// 튜플을 스트림으로 보낼 때 사용
private SpoutOutputCollector collector;
public class WordNormalizer extends BaseRichBolt {
private OutputCollector collector;
@Override
public void prepare(Map conf, TopologyContext context,
OutputCollector collector) {
this.collector = collector;
…. // 데이터 처리 위한 준비
}
@Override
public void open(Map conf, TopologyContext context,
SpoutOutputCollector collector) {
this.collector = collector;
// 메시지 큐 연결 등의 처리
}
@Override
public void execute(Tuple input) {
String sentence = input.getString(0);
String[] words = sentence.split(" ");
for (String word : words) {
word = word.trim().toLowerCase();
// 다른 볼트에 전달할 튜플 생성
collector.emit(new Values(word));
}
collector.ack(input);
}
@Override
public void nextTuple() {
… // 데이터를 읽어와 알맞은 튜플 생성
collector.emit(new Values(line), line);
}
@Override
public void declareOutputFields(
OutputFieldsDeclarer declarer) {
// 생성할 튜플의 필드 정의
declarer.declare(new Fields("line"));
}
@Override
public void declareOutputFields(
OutputFieldsDeclarer declarer) {
// 볼트가 튜플 생성할 경우, 필드 정의
declarer.declare(new Fields("word"));
}
@Override
public void close() { …연결 종료 등 처리 }
}
}
12. 메시지(튜플) 처리 여부 확인
public class WordReader extends BaseRichSpout {
@Override
public void nextTuple() {
…
collector.emit(new Values(line), msgId);
}
@Override
public void ack(Object msgId) {
// 처리가 된 튜플의 아이디 수신
}
@Override
public void fail(Object msgId) {
// 처리에 실패한 튜플의 아이디 수신
// 실패 메시지에 대한 알맞은 재처리 필요
}
public class WordNormalizer extends BaseRichBolt {
@Override
public void execute(Tuple input) {
try {
String sentence = input.getString(0);
String[] words = sentence.split(" ");
for (String word : words) {
word = word.trim();
if (!word.isEmpty()) {
word = word.toLowerCase();
collector.emit(new Values(word));
}
}
collector.ack(input); // 메시지 처리 됨을 알림
} catch(Exception e) {
collector.fail(input); // 메시지 처리 실패 알림
}
}
}
* 스파우트는 ack()와 fail()을 통해 발생시킨 메시지가 처리되었는지 알 수 있음
* 스파우트가 fail 메시지를 받는 경우:
- 수신 볼트에서 fail() 호출하거나 메시지를 제한된 시간 안에 처리하지 못한 경우
* 튜플의 처리 여부 상태를 메모리로 관리하므로, 반드시 수신 볼트에서 ack와 fail을 해야 함
13. 최초의 메시지가 여러 튜플을 거치면?
word-reader
ack로 인식
word-normalizer
ack
collector.emit(new Values(word));
collector.ack(input);
fail 응답이 원본 스파우트에 전
달되지 않음
word-counter
collector.fail(input);
14. Anchoring
● 메시지를 연결해서 전체에서 처리되었는지
확인하기 위한 방법
연결된 모든 튜플에 대해
ack를 받을 때 비로서 ack 처리
word-reader
word-normalizer
ack
-- 스트림에서 받은 input을 연결
collector.emit(input, new Values(word));
collector.ack(input);
ack
word-counter
collector.ack(input);
15. Fault tolerance
● 워커 프로세스 죽을 때
○ 수퍼바이저가 워커 프로세스 재 구동
● 님버스/수퍼바이저가 죽을 때
○ 얼른 재시작하면, 태스크 재시작
● 님버스가 실행 중인 노드가 죽을 때
○ 얼른 다른 장비 구해서 님버스 실행
■ 수퍼바이저는 기능 제공
● 수퍼바이저가 실행 중인 노드가 죽을 때
○ 님버스가 노드에서 실행 중인 태스크를 다른 노드에
할당