순서

1) 하둡 RPC
2) 하둡 스트리밍
3) 하둡 & 보안
4) 하둡 HDFS 읽기
5) 하둡 HDFS 쓰기
6) 하둡 IO  (Writable / Avro)
7) 하둡 & 가용성  (Zookeeper) 
8) 하둡 쉘 스크립트 및 환경


하둡은 HDFS 라는 분산파일시스템과 맵리듀스라는 그것을 이용하여 계산을 하는 도구를 가지고있습니다.  (YARN 이전) 계산에는 간단한 배치성 작업이 주를 이루며 다양한 머신러닝 알고리즘 (머하웃 라이브러리) 을 실행할수도 있습니다.계산복잡도,알고리즘형태에 따라서  지라프,하마같은 다른 도구를 사용할수도 있으며 , 하둡 YARN 과 함께 다양한 빅데이터 솔루션들이 하모니를 이루고 있습니다. 
Storm-yarn 같은 도구를 사용하여 실시간 분석을 용이하게 할수도있으며, 메모리를 적극적으로 활용한 Spark 라는 제품도 각광을 받고 있습니다.


...작성중..

순서


1) 하둡 RPC
2) 하둡 스트리밍
3) 하둡 & 보안
4) 하둡 HDFS 읽기
5) 하둡 HDFS 쓰기
6) 하둡 IO  (Writable / Avro)
7) 하둡 & 가용성  (Zookeeper) 
8) 하둡 쉘 스크립트 및 환경



하둡은 HDFS 라는 분산파일시스템과 맵리듀스라는 그것을 이용하여 계산을 하는 도구를 가지고있습니다.  (YARN 이전) 계산에는 간단한 배치성 작업이 주를 이루며 다양한 머신러닝 알고리즘 (머하웃 라이브러리) 을 실행할수도 있습니다.계산복잡도,알고리즘형태에 따라서  지라프,하마같은 다른 도구를 사용할수도 있으며 , 하둡 YARN 과 함께 다양한 빅데이터 솔루션들이 하모니를 이루고 있습니다. Storm-yarn 같은 도구를 사용하여 실시간 분석을 용이하게 할수도있으며, 메모리를 적극적으로 활용한 Spark 라는 제품도 각광을 받고 있습니다.



HDFS 라는 분산파일 시스템은 여러대의 컴퓨터에 파일을 분산하여 놓고 읽기/쓰기를 지원해주는 단순한 기능을 합니다.
NameNode 는 분산된 파일의 위치/크기등의 정보를 가지고있으며
DataNode 는 실제 데이터를 가지고있습니다.

하둡 클라이언트에서 파일을 가져오기 위해서는

0. KDC (케버로스센터) 에 네임노드 / 데이터노드 / 클라이언트는 각각 인증합니다.
1. 네임노드와 데이터노드간에 서로 비밀키를 교환합니다.
2. 클라이언트는 KDC 에 TGT 를 요청합니다.
3. 클라이언트는 KDC 에 네임노드 서비스 티켓을 요청합니다.
4. NameNode 에게 내가 원하는 파일의 위치 (블럭ID) 를 알려달라고 요청합니다. (티켓과 함께)
5. 1번에서 획득한 블럭ID  및 블록 접근 토큰을 가지고 해당 DataNode 에게 데이터를 달라고 요청합니다.

위에

5번에서 데이타를 가져올때는 하둡 스트리밍 (TCP/IP) 를 이용합니다.
나머지 0~3번은 보안과 관련된 이슈이므로 다음 연재때 자세히 다뤄보도록 하겠습니다.


하둡 스트리밍을  이해하기위해서는 자바IO/NIO 에 대한 몇가지 선행학습이 필요합니다. 
(하둡은 자바로 만들어짐. NIO2가 안정화되면 코어구현도 바뀌겠지요. 네티가 들어갈수도) 


1. 자바 IO /NIO ( http://javacan.tistory.com/entry/87 

* InputStream / OutputStream
* Selctor
* Channel
* ByteBuffer
* Socket


소켓 통신 일반  




전형적인 소켓 프로그래밍 인터렉션입니다. 서버는 대기하고, 클라이언트가 접속하면 서버는 받아드리고, 

받아드리면서 클라이언트와 통신하기위한 소켓하나 만들어서 쓰레드하나 만들어서

던진후에 그 쓰레드에서 클라이언트와 입/출력.  




자바 NIO 



  자바NIO (New I/O)는 비동기 입출력은 아닙니다.   

  쪼 위에 blocking 되있는걸 보실수있습니다. (타임아웃 가능) 

  Select 모델이라고 하지요. 소켓통신 모델에는 다양한 모델이 있으며, IOCP 모델이 빠르기로 유명함.

  개인적으로 자바 NIO/NIO2 프로그래밍이 C++ 을 이용한 IOCP 프로그래밍 보다는 애매하게 느꼈던것 

  같습니다. (추상화 하다보니 좀 희미하다고 해야하나? 그에 반해 IOCP 모델은 굉장히 클리어 하죠.)


하둡 스트리밍 


사실 하둡 스트리밍에 대한 글을 쓸까 말까 좀 고민을 했었습니다.  왜냐? 할게 없기때문에.. 

그냥 자바 소켓통신입니다. 따라서 자바소켓통신에 대한 소스레벨 분석 글을 보신다고 생각하면 편하실듯합니다. :-)  독자분이 소켓통신을 구현할때 가져다 쓰면 좋겠지요. 구태여 바퀴를 또 만들필요가 없지 않겠습니까?   (이런 의미에서 Netty 를 사용하는게 좋죠. Vert.x / Couchbase / OpenTSDB 등이 사용)




위의 그림은 클라이언트에서 데이타노드로 블록ID 을 보내어 데이터를 달라고 하는 상황에서

그와 관련된 클래스들의 모식도 입니다.

소스 조각을  하나씩 살펴보겠습니다.

DFSClient

 - 하둡 HDFS  읽기/쓰기 연재에서 설명 예정.

RemoteBlockReader

 - 하둡 HDFS  읽기/쓰기 연재에서 설명 예정.

NetUtils

public static SocketFactory getDefaultSocketFactory(Configuration conf) {

    String propValue = conf.get("hadoop.rpc.socket.factory.class.default");

    if ((propValue == null) || (propValue.length() == 0))

      return SocketFactory.getDefault();

    return getSocketFactoryFromProperty(conf, propValue);

  }

 하둡 환경설정에 따라 소켓팩토리를 선정합니다. getDefaultSocketFactory 가 

 기본.SocketFactory.getDefault(); 참고로 소켓팩토리를 통해서 SSLSocket 을 사용할수있습니다. 


 public static InputStream getInputStream(Socket socket, long timeout) 
                                           throws IOException {
    return (socket.getChannel() == null) ? 
          socket.getInputStream() : new SocketInputStream(socket, timeout);
  }
소켓의 속성에  채널이 있을때와 없을때 , 그러니깐 IO / NIO 에 따라서  리턴되는스트림객체가 다릅니다. 

public static void connect(Socket socket, 
                             SocketAddress endpoint,
                             SocketAddress localAddr,
                             int timeout) throws IOException {
  
...
  
    SocketChannel ch = socket.getChannel();
    
    if (localAddr != null) {
      socket.bind(localAddr);
    }

    if (ch == null) {
      socket.connect(endpoint, timeout);
    } else {
      SocketIOWithTimeout.connect(ch, endpoint, timeout);
    }
    ......
  }

 역시 소켓의 속성이 IO/ NIO 냐에 따라서 connection 방법이 달라집니다. IO 이면 일반 connect 를 맺고NIO 면 SocketIOWithTimeout 클래스를 사용하네요.


SocketInputStream 


생성자 


public SocketInputStream(Socket socket, long timeout) 

                                         throws IOException {

    this(socket.getChannel(), timeout);

  }


 public SocketInputStream(ReadableByteChannel channel, long timeout)

                                                        throws IOException {

    SocketIOWithTimeout.checkChannelValidity(channel);

    reader = new Reader(channel, timeout);

 }


내부에서 Reader 를 사용합니다. 


public int read(byte[] b, int off, int len) throws IOException {

    return read(ByteBuffer.wrap(b, off, len));

  }


 public int read(ByteBuffer dst) throws IOException {

    return reader.doIO(dst, SelectionKey.OP_READ);

  }


원하는 데이터의 양만큼을 ByteBuffer 클래스를 통해 할당하여 요청합니다.

  


SocketOutputStream


 public SocketOutputStream(WritableByteChannel channel, long timeout) 

                                                         throws IOException {

    SocketIOWithTimeout.checkChannelValidity(channel);

    writer = new Writer(channel, timeout);

  }


 public void write(byte[] b, int off, int len) throws IOException {

    ByteBuffer buf = ByteBuffer.wrap(b, off, len);

    while (buf.hasRemaining()) {

      try {

        if (write(buf) < 0) {

          throw new IOException("The stream is closed");

        }

      } catch (IOException e) {

        if (buf.capacity() > buf.remaining()) {

          writer.close();

        }

        throw e;

      }

    }

  }


ByteBuffer 로 쓸것을 저장한후에 남는게 없을동안 씁니다. write 를 호출한 쓰레드는 계속 기다려야합니다.
IOCP 라는 윈도우OS가 제공하는 비동기 입출력 통신 기술에서는 기다리지 않습니다. C++ 은 네이티브라 
당연히 사용가능하며, 자바도 NIO 는 지원안하지만 지원하는 라이브러리도 있는것으로 알고있습니다.


Reader 

: SocketIOWithTimeout 를 상속받은 클래스 

  int performIO(ByteBuffer buf) throws IOException {

      return channel.read(buf);

  }


 채널에서 버퍼의 크기만큼 읽는다. 

 


Writer 

: SocketIOWithTimeout 를 상속받은 클래스 


public int write(ByteBuffer src) throws IOException {

    return writer.doIO(src, SelectionKey.OP_WRITE);

  }

  

 int performIO(ByteBuffer buf) throws IOException {

      return channel.write(buf);

 }



SocketIOWithTimeout  (이게 핵심) 


private static SelectorPool selector = new SelectorPool();


int doIO(ByteBuffer buf, int ops) throws IOException {

 

    if (!buf.hasRemaining()) {

      throw new IllegalArgumentException("Buffer has no data left.");

    }


    while (buf.hasRemaining()) {

     int n = performIO(buf);

       if (n != 0) {

         return n;

        }

       selector.select(channel, ops, timeout);  

    }

    return 0; // does not reach here.

  }


Buffer 에 담을 수있는 데이터가 있는지 확인한후에 ,  -->  if (!buf.hasRemaining()) {

더이상 담을 공간이 없을때까지 -->  while (buf.hasRemaining()) 

입력/출력을 합니다. --> int n = performIO(buf);

입력/출력한게 있으면 리턴해줍니다. --> return n;

입력/출력한게 없으면 다시 입력/출력하기 위해 대기합니다. -->  selector.select(channel, ops, timeout);  


static void connect(SocketChannel channel, 

                      SocketAddress endpoint, int timeout) throws IOException {

    

    boolean blockingOn = channel.isBlocking();

    if (blockingOn) {

      channel.configureBlocking(false);

    }

    

    try { 

      if (channel.connect(endpoint)) {

        return;

      }


      while (true) {

        

        int ret = selector.select((SelectableChannel)channel, 

                                  SelectionKey.OP_CONNECT, timeoutLeft);

        

         ...

      }

    } catch (IOException e) {

   ...

  }


SelectorPool

 private static class SelectorInfo {
      Selector              selector;
      long                  lastActivityTime;
      LinkedList<SelectorInfo> queue; 
      
      void close() {
        if (selector != null) {
          try {
            selector.close();
          } catch (IOException e) {
            LOG.warn("Unexpected exception while closing selector : " +
                     StringUtils.stringifyException(e));
          }
        }
      }    
    }
    
    private static class ProviderInfo {
      SelectorProvider provider;
      LinkedList<SelectorInfo> queue; // lifo
      ProviderInfo next;
    }

int select(SelectableChannel channel, int ops, long timeout) 
                                                   throws IOException {
     
      SelectorInfo info = get(channel);
      
      SelectionKey key = null;
      int ret = 0;
      
      try {
        while (true) {
          long start = (timeout == 0) ? 0 : System.currentTimeMillis();

          key = channel.register(info.selector, ops);
          ret = info.selector.select(timeout);
          
         ...
        }
      } finally {
      ...
    }


  private synchronized SelectorInfo get(SelectableChannel channel) 

                                                         throws IOException {

      SelectorInfo selInfo = null;

      

      SelectorProvider provider = channel.provider();

      

      // pick the list : rarely there is more than one provider in use.

      ProviderInfo pList = providerList;

      while (pList != null && pList.provider != provider) {

        pList = pList.next;

      }      

      if (pList == null) {

        //LOG.info("Creating new ProviderInfo : " + provider.toString());

        pList = new ProviderInfo();

        pList.provider = provider;

        pList.queue = new LinkedList<SelectorInfo>();

        pList.next = providerList;

        providerList = pList;

      }

      

      LinkedList<SelectorInfo> queue = pList.queue;

      

      if (queue.isEmpty()) {

        Selector selector = provider.openSelector();

        selInfo = new SelectorInfo();

        selInfo.selector = selector;

        selInfo.queue = queue;

      } else {

        selInfo = queue.removeLast();

      }

      

      trimIdleSelectors(System.currentTimeMillis());

      return selInfo;

    }

    

   셀렉터를 하나만 만들면 , 여러 소켓들이 하나의 셀렉터를 통해서 , 신호를 받기때문에 효율적이지 못합니다.

   따라서 각 이벤트들마다 셀렉터를 만들면, 효율적이게 운영할수있는데, 셀렉터를 풀로 만들어서 

   셀렉터 생성에 대한 부담을 줄였습니다. 상황에 따라서 Read / Write / Acceptor 등을 별도의 셀렉터로 

   등록 시키면 좋습니다. 위의 셀렉터풀 말고도 NIO에서는 ByteBuffer  Pool을 만들어서 사용해도 좋습니다.


DataNode 


 - 하둡 HDFS  읽기/쓰기 연재에서 설명 예정.


DataXeiverServer


 - 하둡 HDFS  읽기/쓰기 연재에서 설명 예정.


DataXceiver


 - 하둡 HDFS  읽기/쓰기 연재에서 설명 예정.


헤즐케스트 소개


해즐케스트의 소켓 통신을 위한 클래스들 


Storm 소개


Storm 의 소켓 통신을 위한 클래스들 



.............. 작성중 ................


0. 인증 / 인가 / 무결성 이란 ? 


인증 (Authentication)  :  A 라는 사람이  그 싸이트 혹은 그 서비스를 사용할수있는가?  신원 확보 목적.
인가 (Authorization)    :  A 라는 사람이 서비스에서 어떤것을 할수있도록 하는가 ? ( 파일 리딩만 하게하자) 

무결성                  :   A -> B 로 어떤 메세지를 보냈을때 그 메세지가 바뀌거나 깨지지 않음을 보장하는것.


1. 암호화 / 복호화 란 ? 


암호화 :  어떤 문자열 "hello world"  를  "332XF3FX_*)3" 로 바꾸는것.

복호화 :   "332XF3FX_*)3"  을 다시  "hello world" 로 환원하는것.


암호화 알고리즘에는 DES , AES 등이 있다. 

JAVA AEC :  http://andang72.blogspot.kr/2012/02/aes.html


암호화와 복호화를 위해서는 key  (비밀키) 라는것이 필요하다. 


2. 대칭형 암호화 


A 와 B  라는 각각의 Peer 를 상상해보자. 

각각의 컴퓨터에 동일한 key 를 복사해두고, 동일한 key 를 이용해서 암호화 / 복호화를 하는것이 

대칭형 암호화이다. A,B 모두를 제어권에 두고있다면 대칭형 암호화로 충분하다.

하지만  A 는 내가 관리하는데 B 를 관리할수없을경우  B 에게 나의 비밀키를 보내야하는데

그 중간에서 탈취를 당하면 탈취한사람은 A 와 B 의 비밀 이야기를 엿들을수있게 된다.


3. 공개키 암호화 ( 비대칭형 ) 


공개키 암호화 (비대칭형) 방법이 대칭형과 다른것은 키를 2개 만드는것이다.

비밀키 1개에서 ->   공개키,개인키 요렇게 2개!!

개인키는 자신이 가지고 있고, 공개키를 B 에게 전달한다. 전달받은 B 는 자신의 공개키를 A에게 보낸다.

공개키로 암호화하고 개인키로 복호화한다. 암호화는 누구나 할수있지만 , 복호화는 자신밖에 못하기때문에

보안을 이룰수있다.



4. 비대칭형 / 대칭형 믹스 


위의 공개키암호화는 상당히 안전하지만 , 공개키/개인키를 통한 암호화/복호화는 상당히 느리다.

따라서 처음에만 공개키로 하고 다음부터는 대칭키로 암호화/복호화를 하는 전략을 쓴다.

1 .A 는 자신의 공개키를 B에게 전달한다. 

2. B 는 A의 공개키로 자신의 비밀키를 암호화해서 A에게 전달한다.

3. A 는 자신의 개인키로 B 의 비밀키를 복호화한다.

4. 공유된 B 의 비밀키로 통신한다.


이 방법은 B 가 A 의 공개키를 어떻게 진짜 A 의 공개키인지 믿을수 있나? 라는 화두를 던진다.


5. 해쉬 함수 


"EWEFWFWEEFWFWEWFWE32R2FF2F" 이렇게 긴 문장을 

"F3F34"  요렇게 짧게 바꾸는 작업을 말한다. 

해쉬된 문장을 원래 문장으로 바꾸는건 불가능하다.

해쉬를 왜 하냐면 , 짧으면 무엇이 좋을까?  


1. 암호화, 복호화 할때 시간이 단축된다.

2. 원래 문장을 전송에 사용할 필요가 없어진다. 따라서 비밀번호같은것의 노출을 피할수있다.

   예를들어 비밀번호를 해쉬함수를 통해서 걸러진 문장을 DB 에 저장해두면,  진짜 비밀번호를 알수는

   없지만, 동일한 비밀번호로부터 나온 결과라는것은 알수있게된다.


MD5 / SHA1 같은것을 사용한다.


6. 전자 서명 


상대방의 신원을 확인하기 어려운 사이버 공간에서 서로를 알아볼 수 있도록 한 쌍의 전자서명키 (공개키+개인키)를 사용하여 자신을 증명하는 것이 전자서명의 원리입니다. 공인인증기관으로부터 인증서를 발급받아 전자계약서 등에 전자서명을 하여 계약내용을 증명합니다.단지 사이버 공간이라는 환경과 모든 처리가 전자적으로 처리된다는 것이 차이점입니다.


네이버어플리케이션 전자서명의 원리 : http://helloworld.naver.com/helloworld/textyle/744920


7. 인증서 


당신과 접속해있는 사람이나 웹 사이트가 믿을 수 있는지 어떻게 판단할 수 있을까? 한 웹사이트 관리자가 있다고 가정하자. 그 사람이 당신에게 이 사이트가 믿을만하다고 (심각할 정도로) 열심히 설명했다. 당신이 그 사이트의 인증서를 설치해 주기를 바라면서 말이다. 한두번도 아니고 매번 이렇게 해야한다면 귀찮지 않겠는가?


인증서는 여러 부분으로 이루어져있다. 아래는 인증서 속에 들어있는 정보의 종류를 나타낸 것이다.


인증서 소유자의 e-mail 주소

소유자의 이름

인증서의 용도

인증서 유효기간

발행 장소

Distinguished Name (DN)

- Common Name (CN)

- 인증서 정보에 대해 서명한 사람의 디지털 ID

Public Key

해쉬(Hash)


SSL의 기본 구조는 당신이 인증서를 서명한 사람을 신뢰한다면, 서명된 인증서도 신뢰할 수 있다는 것이다. 이것은 마치 트리(Tree)와 같은 구조를 이루면서 인증서끼리 서명하게 된다. 그러면 최상위 인증서는? 이 인증서를 발행한 기관을 Root Certification Authority(줄여서 Root CA)라고 부르며, 유명한 인증 기관(역주:Verisign, Thawte, Entrust, etc)의 Root CA 인증서는 웹브라우저에 기본적으로 설치되어 있다. 이러한 인증 기관은 자신들이 서명한 인증서들을 관리할 뿐만 아니라 철회 인증서(Revoked Certificate)들도 관리하고 있다. 그러면 Root CA의 인증서는 누가 서명을 했을까? 모든 Root CA 인증서는 자체 서명(Self Signed)되어 있다.



8. SSL


SSL 은 위의 4번과 동일하다. 다만 추가된것은 공개키를 인증해주는 과정이 추가되었다.

A의 공개키를 CA 기관에 전자서명을 받아서 인증서를 받은후에 (CA 에서는 자신의 개인키로 암호화함) B에게 보내준다. B는 자신의 컴퓨터에 미리 존재하는 (브라우저안에도 있음) CA의 

공개키로 인증서를 확인하여,  A의 공개키를 얻는다.

HTTPS 에 사용된다.



9. JCA  


가)    전자 서명과 메시지 다이제스트 같은 기능에 대한 일반적인 API 제공

 

나)    주요 클래스들

①     MessageDigest

②     Signature

③     KeyPaireGenerator

④     KeyFactory

⑤     CertificateFactory

⑥     KeyStore

⑦     AlgorithmParameters

⑧     AlgorithmParameterGenerator

⑨     SecureRandom

 

다)    암호 서비스 제공자 Sun Provider(Java 2 기준, sun.security.provider.Sun)

①     MD5 메시지 다이제스트

②     SHA-1 메시지 다이제스트

③     DSA 전자 서명 사인과 검증

④     DSA 키 쌍 생성

⑤     DSA 키 변환

⑥     X.509 인증서 생성

⑦     Proprietary keystore 구현

⑧     DSA 알고리즘 매개변수

⑨     DSA 알고리즘 매개변수 생성

 

라)    암호 서비스 제공자 RSAJAC provider(com.sun.rsajca.Provider)

①     RSA 키 쌍 생성

②     RSA 키 변환



10) JCE


가)    주요 클래스와 인터페이스

①     Cipher

②     KeyAgreement

③     KeyGenerator

④     Mac

⑤     SecretKey

⑥     SecretKeyFactory

 

나)    JCE 접근 

KeyGenerator keyGenerator = KeyGenerator.getInstance(“Blowfish”);

Key key = keyGenerator.generatorKey();

Cipher cipher = Cipher.getInstance(“Blowfish/ECB/PKCS5Padding”);

cipher.init(Cipher.ENCRYPT_MODE, key);

byte[] cipherText = cipher.doFinal(myData);

  

다)    JCE 설치

이름

BouncyCastle

URL

http://www.bouncycastle.org

 

 

12. JSSE



13. JAAS


http://docs.oracle.com/javase/7/docs/technotes/guides/security/jaas/JAASRefGuide.html


14. Java SASL


https://docs.oracle.com/javase/8/docs/technotes/guides/security/sasl/sasl-refguide.html


15. Java GSS-API


https://docs.oracle.com/javase/8/docs/technotes/guides/security/jgss/tutorials/index.html


16. 커버로스 


http://publib.boulder.ibm.com/html/as400/v5r1/ic2986/info/rzakh/rzakha06.htm


17. LDAP 


http://jabcholove.tistory.com/89


18. 스프링에서의 보안


http://www.slideshare.net/madvirus/ss-36809454


19. AngularJS  에서의 보안 


https://www.youtube.com/watch?v=18ifoT-Id54


20. 하둡에서의 보안 


http://www.slideshare.net/oom65/hadoop-security-architecture

'보안' 카테고리의 다른 글

SSH 인사이드  (0) 2016.06.09
HTTP 다이제스트 엑세스인증  (0) 2015.09.20
HTTP 기본인증 (Basic authentication)  (0) 2015.09.20
HTTP 세션을 이용한 인증  (0) 2015.06.04
HTTPS 설정 및 사설인증서 관련 글들  (0) 2015.06.04




이번주 토요일 발표된 하둡 쉘에 대하여 간단히 정리해봤습니다. 지저분한것들이 많지만..
핵심은 저것인데요.

0. 실행은 항상 마스터컴퓨터에서 합니다.
1. 먼저 start-dfs.sh 로 실행합니다.   ( 하둡의 HDFS 를 실행시키기 위함) 
2. start-dfs.sh 은 내부에서 hdadoop-config.sh 을 가져와서 실행합니다. config 에는 별거 없습니다.
3. hadoop-config.sh 내부에서는 다시  hadoop-env.sh 을 가져옵니다. 위에 보시다시피 각종 환경변수들을 설정해줍니다.
4. 마지막으로 start-dfs.sh 은  hadoop-daemon.sh 와 hadoop-daemons.sh 를 실행합니다. 
5. 단수인것은 네임노드만 실행하는것이구요 ( 네임노드는 마스터에 존재)
6. 복수인것은 for 문 돌면서 원격컴퓨터의 hadoop-daemon.sh 을 실행합니다. 
7. 결국 hadoop-daemon.sh 을 실행합니다.
8. hadoop-daemon.sh 은 옵션에 따라서  namenode 와 datanode 를 실행합니다.
9. hadoop --config 컨피그 디렉토리  namenode/datanode    start/stop  이렇게 실행합니다.
10. 마지막으로 hadoop 파일을 보면  하둡 라이브러리들을 클래스패스에 등록한후에  결국 자바클래스를 호출합니다.

name 노드 실행은   exec JAVA  namenode 자바클래스  start 
data  노드 실행은   exec JAVA  datanode 자바클래스 start 
입니다.

HDFS 에 파일을 조작하는 명령어는  hadoop fs 이며 
하둡에서 맵리듀스를 실행하는 명령어는 hadoop jar  입니다.

결국 소스는 위에 있는 클래스소스들부터 분석시작하면 됩니다. 

주요 클래스

CLASS= org.apache.hadoop.hdfs.server.namenode.NameNode
CLASS= org.apache.hadoop.hdfs.server.datanode.DataNode
CLASS=org.apache.hadoop.fs.FsShell
CLASS=org.apache.hadoop.util.RunJar


hadoop-2.2.0 경우는 이클립스에서  maven  으로 import 하면 됩니다만


hadoop-1.2.1 은 이클립스에서 ant 로 import 할 경우 ivy-common (??)  에러나면서 안됩니다.

그럴 경우 아래 방법을  참고하셔서 import 하시면  됩니다.

Basic Environment Description:

     

OS:

     

Ubuntu 12.04

JAVA version:

     

jdk 1.6.0_27

Eclipse version:

     

Service Release 1


  Part 1: Building Hadoop Developing Environment

           

1) download hadoop source code.

  • For Hadoop release, download it here. I used Hadoop 1.2.0;
  • For Newest versions, it can be downloaded from Hadoop Version Control System;
  • Assume HADOOP_HOME is the directory where the source code is.
2) Install tools.
  • Run command: sudo apt-get install ant ivy automake libtool to install "ant ivy automake libtool".
  • Download forrest-0.8 and unzip it. Assume its directory is FORREST_HOME 
    Note that forrest-0.8 is not the latest version, Using the latest version may confront with some problems. In order to run forrest-0.8, we need java-1.5. Because we only need java-1.5 for forrest-0.8, we put java-1.5 in a separate directory. Assume it is JV5_HOME. The version I used is jdk1.5.0_22
3) Change configuration file of Hadoop
  • Open config file: $HADOOP_HOME/src/c++/task-controller/configure.ac 
    Find a line which contains AC_SYS_LARGEFILE, delete it.
  • Open config file: $HADOOP_HOME/build.xml 
    Find a line contains < target name="ant-eclipse-download", there is a line closely below, start with < get src= which indicates the source location of file "ant-eclipse.jar". The location in my version does not exist, change it to https://ant-eclipse.googlecode.com/files/ant-eclipse-1.0.bin.tar.bz2.
4) Build hadoop.
  • cd $HADOOP_HOME
  • ant compile
  • ant clean package -Djava5.home=$JV5_HOME -Dforrest.home=$FORREST_HOME
5) Create eclipse project files.
  • ant eclipse
6) Configure eclipse and import hadoop project.
  • Open eclipse and add class path variable. 
    The operation is "Window->preferences->java->Buiid path-> Classpath Variables" 
    Create new variable ANT_HOME=/user/share/ant
  • Import Hadoop project. 
    The operation is "File->Import->General->Existing Project Into Workspace". Goto $HADOOP_HOME and import the project
  • Configure Hadoop project. 
    Because Hadoop need to find classes for webapp but these .Class files are not included in project configuration, it cause runtime errors. 
    A simple solution is adding path "$HADOOP_HOME/build/classes" to CLASS_PATH of the project. The operation is "File->Properties->Java Build Path->Libraries->Add Class Folder"

7) Test your environment

  • There are many JUnit test examples in the project. You can choose one to test your environment. 
    If debugging Junit testcases add following arguments to your "VM Argument" in your "Debug configuration". 
    -Dhadoop.log.dir=build/test/logs -Dtest.build.data=build/test/data 
    -Dhadoop.log.file=hadoop.log 
    -Djava.net.preferIPv4Stack=true






java 5 sdk 설치방법 (1)

ubuntu의 기본 update repository에서는 sun-1.5-jdk를 제공하지 않으므로, 다음의 방법으로 추가 repository를 추가한다.

sudo add-apt-repository "deb http://us.archive.ubuntu.com/ubuntu/ hardy multiverse"
sudo add-apt-repository "deb http://us.archive.ubuntu.com/ubuntu/ hardy-updates multiverse"

aapt-get update

sudo apt-get install sun-java5-jdk



java 5 sdk 설치방법 (2) 

oracle 에서 5버전 bin 다운받은후 terminal 창에서 
sudo chmod +x jdk-1_5_0_22-linux-amd64.bin   (엔터)
sudo ./jdk-1_5_0_22-linux-amd64.bin   (엔터) 


1편 언어에서 강력함 과 대중성  그리고 스칼라 

이글은 스칼라에 대한 전문적인글이 아니며 ,  스칼라의 모든부분을 말하는글은 아닙니다.
스칼라의 함수자(Functor) , 컬렉션 및 유틸리티 에 대해서 한정되있으며,  
먼저 스칼라말고 다른 여러가지 언어들에 대해서 말하고도 있습니다. 제목에 일반적이라고 붙힌이유는 , 스칼라의 다른 기능들 , 소위 Active Object &  Actor Pattern 기반의 동시성 구현라이브러리로 알려진 아카라든지,콤비네이터를 이용한 인터프리터 개발같은것들을 대중적이라고 보기엔 무리라고 판단하였기에 그런것들을 제외한 극소수의 내용들로 이루어져있기때문입니다. 그리고 아래 글에서 예시로 보여지는 (슈도)코드는 문법적으로 정확치 않은 예 이며, 글에 쓰여진 모든 지식은 저의 것이아니라 다른 사람으로부터 나온것입니다. (저도 틈틈히 공부중이며,  이번 사태를 계기로 수준높은 글들이 앞으로 많이 나올것을 기대합니다.)

강력함 과 대중적이라는것은 전혀 별개의 말입니다. 
강력하다고 해서 대중적으로 선택받지 못함은 역사를 통해 다들 잘 알고있을터입니다. 


본글에서는 단지 간단한 예제들을 나열함으로서  글을 읽는 여러분들이 알아서 생각하게끔 구성하였습니다.  
자 아래에는 어떤 목록에서 원하는 값을 찾는 과정에 대한  코드입니다.

예제1 in C++ ) 


string guy = null;

for(  Groups::iterator i = Groups.iterator();  i != Groups.end() ; ++i){
	
	player p = *i;
	
	if(p.id == 1){
		guy =  p.name;
	}
}

return guy;

위의 코드는 그룹에서 ID 1 번 선수의 이름을 찾아서 리턴해주는 로직입니다. 보는바와 같이 길게 늘어써져 있습니다.   코드를 읽어야지만 멀하자는건지 알수있습니다.

아래의 코드를 보면 지원되는 함수 util 을 이용하여 조금 짧게 이루어져있습니다. 

예제2 in JAVA )


player = Groups.findbyId(1);
if( player ){
	return player.name;
}
elsle{
	return null;
}

findbyId 함수를 이용하여  보다 간략하게 코딩하였음을 알수있습니다. 멀하자는건지 금방 알수있습니다.
 저 코드를 짜려면 findbyId 라는 함수를 알고있어야겠지요. 

에제3  in Haskell )   * 헤스켈 ( 순수 함수형 언어)  


fmap (getName)  (findId 1) 

( findId 1  <-- 이 부분을 functor 라고 합니다.)  findId 1 에서  값을 찾으면  getName 이 호출되어 , 원하는 선수 이름을 얻을수있으며 값을 못찾으면 Nothing 이 되면서, 아무것도 하질 않습니다. 
1줄로 줄어들었으며, Null 참조 및  잘못된 배열참조에 대한 불안감으로부터 해방되었습니다.
가독성 좋아졌는지는 잘모르겠고 , 코드가 강건해졌다고 볼수있습니다. 

스칼라를 사용하면, 추상정도를 높히는 코드를 C++,자바보다 더 자유롭게 표현할수 있게됩니다. 
추상정도를 높히면 강력합니다! 더 정확히 말하면 강력하게 변신하기 쉬워집니다. 

자 다들 저렇게 쓰도록 바꾸자!!! 고요 ?  저는 잘모르겠습니다. 독자분들마다 생각이 다를 것입니다.
대중들은 단순히 람다표현식 혹은 함수자 유틸의 사용으로 라인수 줄어든것만으로 굳이 공부하거나 바꿀 필요성을 느끼지 못할거 같습니다. 대중이 판단할때  현재 '진짜'  강력해야 선택받습니다.


제가 그렇게 생각하는  이유를 전에 쓴 글에서 가져오고 제 경험을 말씀드리면 
 c++ 은 10년전부터 다른언어의 특성들을  차용하며 소위 Modern C++ 이라는 이름으로 나름 발전을 이루어왔습니다. 주로 c++ 언어특성으로 발전을 하여온것은 아니고 STL 이름의 라이브러리등을  통하여 발전하였습니다. 언어 창조자의 철학이, 먼가가 필요하면 굳이 언어를 변경하는것보다는 라이브러리 차원으로 제공하는게 낫다라는 입장입니다. 아마도 자바 또한 그러한 보수적인 관점에서 신기능의 추가 (제너릭,병렬, 함수형등) 에 조심성을 가지고 발전한거겠지요. 

c++ 에 다양한 함수자유틸들, 템플릿특화,  일반화함수자, 타입추론 , 템플릿메타프로그래밍등 많은 개념들이 10년도 훨씬 전에 생겨났지만, 대중은 그닥 관심이 없습니다. (물론 몇몇 게임등 쓰이는곳도 있습니다)
알필요도 없지요. 안쓰고 개발해도 충분했으니깐요.  라이브러리 제작자한테나 필수일까...

실제 사례로, 제가 이전프로젝트에서의 c++ 코드라인이 300백만줄이 넘고, 10년동안  8백만라인이상의 코드를 접해왔는데  저런것을 쓴 사례가 거의 없습니다.
너와 니네팀이 무식해서 그렇다고요? 네 반은 맞습니다. 대부분은 비판하는 분과 다르게 저 수준입니다.
(반은 C++ 의 함수형스타일이 그닥 어렵게 공부해서 적용할 필요성이 없었습니다.)
 
정말 간단한  C++ 예를 이전 글에서 가져와서 보여 들어드리자면 

bool is_cool (const Thing& x) { ... }
find_if(begin, end, not1(ptr_fun(is_cool)));

위의 예제는  어떤 컬렉션을 순회하며 , 쿨하지 않은것을 찾아서 리턴해주는 표준함수,부정자 및 바인더라고불리는 것들입니다.  굉장히 단순해 보여서 많이 쓰일거 같지요?  저는 적극적으로 사용했습니다만, 대부분 저런식의 코딩을 하지 않습니다. 그냥 대부분 for 문으로 순회하면서 내용에 비지니스 로직 적습니다. not1 이 부담스러워요, ptr_fun 이 부담스럽습니다. 대중화에 실패했습니다.  
먼가 부족했습니다.
덜 강력했습니다. 먼가 조금 아쉽습니다..
저거에 openMP나 PPL  같은 병렬을 쉽게 해주는 수준까지 지원됬다면 , 더 나아가 GPGPU 까지 흠흠...

스칼라 혹은 함수형패러다임언어가  대중화에 성공 할수있을까요?  
단순히 케이스클래스, 람다, 함수형들을 써서 간단,간편,쉽게하자라는 이유라면  No 라고 생각합니다.
대신 높은 추상화를 가진 표현을 통해, 응용개발자들이 신경쓰지 않아도 라이브러리혹은 언어내부에서의 업그레이드로 손쉽게 강력함을 얻을수있다는 보장이 되는것에 공감대를 얻게된다면  Yes 일거라고 봅니다. (자바,C++ 도 저런측면에서 강력함을 어필하기 위해 노력하고있습니다.  치열한 싸움이 예상됩니다. )


자 이제 다음 예제로 넘어가 볼까요? 

예제1 in SQL )

select * from player  where id = 1  

우와 엄청 간단합니다. 강력합니다. 실수할 여지가 별로 없습니다. 추상의 끝판왕입니다. 
SQL 은 대중적으로 성공했습니다. ^^

저것을 풀어쓰면 

예제 in JAVA)


public Table select (Selector where){

	Table resultTable = new ConcreteTable(null, columnNames.clone()};
	Results currentRow = (Results) rows();
	Cursor[] envelop = new Cursor[] { currentRow };

	while(currentRow.advance(){
		if(where.approve(envelope))
			resultTable.insert( currentRow.cloneRow());
	}
	
	return new UnmodifiableTable(resultTable);
}

더 풀어쓰면

예제 in C )

struct table {
	....
}

table t [][];
              ....


for(int i = 0 ; i <  rows ; i++){
	for( int j = 0 ; j < cols; j++){
		...
	}
}

한 몇백라인되겠지요. 
SQL 은 성공했습니다. !!!


자 이제 JQuery  를 보겠습니다.

$("div")     모든 div 태그를 가져오는것을 한방에 합니다.
document.getElementByTag("div")      조금 길어졌네요.

$("div.reply")   모든  div 중에서 reply 클래스들만 가져옵니다.
JQuery 안쓰면 ?  헬입니다.

$("div.reply:hidden").show()    우와 함수까지 한방에 해결입니다.

다른 값을 감싸고 한방에 해결해줍니다. (모나드라는 특성의 일부분을  가지고 있습니다)
JQuery 성공했습니다.!!!


자 이제 마지막으로 스칼라 (위에 썼듯이 스칼라의 일부분) 를 보겠습니다. 다양한 스칼라 예제를 살펴보고
과연 이것도 성공할수있을지 실제 코딩에서 사용할지...여러분이 알아서 생각하세요.
여러분이 선택하는게 정답입니다.  대중이 선택한것이 대부분 정답이니깐요. ( 물론 이부분은 상황에 따라서 시각이 다를수있겠습니다) 



2편. 여러언어를 통해본 함수형 스타일 ( 함수포인터,함수자,람다 )


본글은 멀 주장하거나  전문적인 글이 아니라, 정보를 공유하기위한 모음글입니다. 
시간이 될때 관심이 있으면 편하게 읽어보시면 될듯합니다. 같이 익숙해져가는게 목표입니다.
먼가를 주장하는글은 1편 언어에서의 강력함과 대중성 그리고 스칼라 http://okky.kr/article/275634  에 있습니다. 참고로  함수형이라는 말에는 다양한 개념들이 있으나 이 글에서는 함수를 매개변수로 넘기는 스타일들부터 익숙해지자는것에 한정되있습니다.



함수 포인터의 정의는  전달가능한 행위의 시작위치! 라고 말할수있으며 사용이유로는  
아래와 같이 3가지정도를 말할수있을듯 합니다.  1번이 가장 핵심이구요 .
람다도 마찬가지로 지연호출이 핵심중 하나입니다.

1. 호출시점 유연화.
2.  struct 에서 행위를 포함할수있게함
3. 디펜던시 문제 해결.  
4. 코딩 단순화로 인한 사고 단순화 


예제  in C ) 

#include <stdio.h>

void apple(void)
{
  printf("apple");
};


int main()
{
  void (*fptr)(void);          // 선언
  fptr = apple;                // 대입
  fptr();                      // 호출
  someFunction(fptr);          // 매개변수로 넘김.
}
 fptr 는 행위 (애플이라고 프린팅하는) 를  가르키고있습니다.  
행위를  함수의 매개변수로 넘겨서 다른곳(someFunction)에서 그 행위에 대한 책임을 지도록  합니다.

예제  in C++ ) 

#include <iostream>
using namespace std;
class fruit
{
	public:
		void apple()
		{
			cout << "apple" << endl;
		}
		void berry()
		{
			cout << "berry" << endl;
		}
};

int main()
{
	fruit x, *y;
	void (fruit::*f)(void);    //  선언 
	
	f = &fruit::apple;         //  대입
	(x.*f)();                  //  호출

	f = &fruit::berry;
        y = new fruit;
	(y->*f)();

	delete y;
} 

C 에는 없는 클래스라는 개념을 가지고있는 C++ 
클래스의 멤버함수를 가르키는 방법입니다. 객체를 가르키는 방법에 따라 다르기때문에  복잡합니다.
헤깔리기때문에 보통 사용할때 구글링으로 확인합니다.


예제 in C++ with boost) 

class CHello
{
	void Say()
	{ 
		print(“hello”); 
        }
};

Void func1()
{
	CHello hello;                                             // CHello 의 객체 선언 
	boost::function< void ( void ) > memfunc;                 // boost::function 객체 선언
	memfunc = boost::bind( &CHello::DebugOut, hello, _1 );    // DebugOut 함수를 func 에 바인딩

        func2(memfunc);     
}     
                                                                      
Void func2(boost::function< void ( void ) >   _func)
{

    _func( 5 );     // CHello::Say() 가 호출!!!  	1.  행위를 하게함. ( observer 패턴)  
                    //                          2.  호출위치를 조절할수도 있고 
                    //                          3.  다른모듈에서 CHello를 호출.(디펜던시문제)

} 

위에는 클래스에 대한 멤버함수를 포인팅하는 방법을 좀더 쉽게 하기위해서
비표준 라이브러리에서  function과 bind가 사용되었습니다.근데 진짜 저게 더 쉬워진걸까?
역시 쓸때마다 구글링합니다. 

예제 in C++ 11) 

#include <iostream>
#include <functional>

using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    auto f = bind(add, 1, 2);
    cout << f() << "\n";
}

///////////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <functional>

using namespace std;
using namespace std:placeholders;

int add(int a, int b) {
    return a + b;
}

int main() {
    auto add1 = bind(add, 1, _1);
    cout << add1(100) << "\n";
}


자. c++11  에 와서 boost  라는 비표준라이브러리를 안쓰고 표준라이브러리를 사용하면서
조금 깔끔해졌습니다. auto의 등장 



예제  in C++  11 이전 ) 

boost::asio::ip::tcp::acceptor m_acceptor;
 
void handle_accept(Session* pSession, const boost::system::error_code& error)
{
  if (!error)
  {     
       std::cout << "클라이언트 접속 성공" << std::endl;
                   
       pSession->PostReceive();
  }
}
 
m_acceptor.async_accept( m_pSession->Socket(),
                         boost::bind(&TCP_Server::handle_accept,  // 함수 객체를 넘긴다. (함수포인터넘겨도됨)
                         this,
                         m_pSession,
                         boost::asio::placeholders::error)
                    );

예제 in C++ 11 이후) 


boost::asio::ip::tcp::acceptor m_acceptor;
 
 
m_acceptor.async_accept( m_pSession->Socket(),
                     [this](boost::system::error_code error)   // 람다식을 넘김!!
                    {
                       if (!error)
                       {  
                          std::cout << "클라이언트 접속 성공" << std::endl;
                          m_pSession->PostReceive();
                       }
                      
                       StartAccept();
                    }
                    );

C++도 11 버전 (2011년)  이전에는  함수자 or 서술자를 쓸때 그것들의 위치가 실제 사용되는 함수와 
거리가 멀어지는 문제점때문에 쓰기 꺼렸었지만, 11 버전 이후에 람다가 표준이 되면서 좀 더 많이 사용될것이라고 생각됩니다.


예제 in JAVA)    

Public void foo()
{
        final int n = 1;
	Class  ActionListener() 
	{
		Pubic void actionPerformed(Action e)
		{
			System.out.pringln(“Clicked! N =+n);
		}
	}
      ActionListener ac = new ActionListener() 
      
      
      Jbutton button = new Jbutton(“Click me”);
      button.addActionListener(ac);
}

자바 내부클래스입니다.
final int n = 1; 여기서 n 이 내부클래스에서 참조되는게 중요한데..
저게 바로 클로저라고 하죠. 내부에서 외부의 존재를 참조하는것!! 
다만 final 이 꼭 붙어야해서 클로저가 아니다라는 말도 합니다.


예제 in JAVA)   익명클래스 

Public void foo()
{
	final int n = 1;
      Jbutton button = new Jbutton(“Click me”);
      button.addActionListener(new ActionListener() 
				{
					Pubic void actionPerformed(Action e)
					{
						System.out.pringln(“Clicked! N =+n);
					}
			});
}




ActionListener 클래스가 함수매개변수로 바로 만들어져 삽입된다.


예제 in JAVA 8 with lamda ) 


Public void foo()
{
      final int n = 1;
      Jbutton button = new Jbutton(“Click me”);
      button.addActionListener(  ()-> System.out.pringln(“Clicked! N =+n);
			      );
} 

자바는 함수를 따로 만드는게 언어철학차원에서 지원하지 않기때문에 C++보다 내부 구현에 복잡해질 여지가 많아보입니다. 어설프게 타언어 장점 가져올바에는 일관성을 지키는게 훨씬 낫겠지요.

예제 in C# with delegate) 

          //대리자 선언
          public delegate void SayHandler(string mag);
                
            [1]익명메서드 : Say 함수를 작성하지 않고 익명메서드로 작성
            SayHandler hi = delegate(string msg)
            {
                Console.WriteLine(msg);
            };
            hi("익명메서드");

            //[2]익명메서드 : Button의 click event를 익명 메서드로 작성
            Button button = new Button();
            button.Click += delegate(string msg) 
            {
                Console.WriteLine(msg);
            };
            button.OnClick("이벤트 익명메서드");


            //[3]람다표현식 : Button의 click event를 Ramda 표현식으로 작성
            Button button1 = new Button();
            button1.Click += (string msg) => Console.WriteLine(msg);
 
            button.OnClick("람다 표현식");
           
            

 C# 도 1버전부터 4버전까지 계속 대리자(함수포인터 , 람다) 가 발전되어져 왔다.

예제 in Scala  with lamda) 

스칼라에서 함수표현 

def max(m: Int, n: Int): Int = if(m > n) m else n

스칼라에서의 람다식

1) 보통 

def bubbleSort(arr: Array[Int], order: (Int, Int) => Boolean): Unit {
    ...
    val o: Boolean = order(a, b)
    ...
}

val arr: Array[Int] = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (a: Int, b: Int) => a > b)

2) 짧게 
val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (a, b) => a > b)


3) 더 짧게
val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (_: Int) > (_: Int))

4) 가장 짧게
val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, _ > _)


 예제  in Scala vs JAVA8   with lamda) 


JAVA8  ) 
List names = Arrays.asList("1", "2", "3");
Stream lengths = names.stream().map(name -> name.length());

Scala ) 
val names = List("1", "2", "3")
val lengths = names.map(name => name.length)



JAVA8  )
List<Photo> photos = Arrays.asList(...)
List<Photo> output = photos.filter(p -> p.getSizeInKb() < 10)

Scala ) 
val photos = List(...)
val output = photos.filter(p => p.sizeKb < 10)


예제 in javascript)


function applyOperation(a, b, operation) 
{
  return operation(a,b);
}

function add(a,b) { return a+ b; }

applyOperation(1,2, add);


// anonymous inline function
applyOperation(4,7, function(a,b) {return a * b})


예제 in Ruby with lamda)

class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

array = [1, 2, 3, 4]

array.iterate!(lambda { |n| n ** 2 })

puts array.inspect

# => [1, 4, 9, 16]


예제  in JAVA )   12보다 큰 단어의 워드카운팅.


int count = 0;
for(String w : words){
   if (w.length > 12 ) count++;
}
머하는지 일단 읽어봐야하고 혹시 index 증감으로 배열참조하다보면 오류생길가능성도 조금 더 있겠지만 
깔끔하고 너무나 익숙하다.  


예제  in JAVA 8 with lamda )  

long count = words.stream().filter( w -> w.length() > 12 ).count();
람다를 이용하여 한줄에 작성되었다. 이거 좋다고 이렇게 쓰자고 난리다. 
다양한 알고리즘도 미리 제공해주고 있다. 흠 근데 아직 잘 모르겠다. 복잡한거 같다. 패스~


예제 in JAVA  8 with lamda and parallel )

long count = words.parallelStream().filter ( w -> w.length() > 12).count()

어라, 병렬을 한방에 해주네??  이 정도면 끌리는데? 어려워도 배워볼만하네.
이게 추상화의 장점이구나!! 기능추가의 자유도가 쉽게 높아지는군!

만약 저걸 하둡의 맵 or 리듀스공정에 사용하면 손쉽게 분산병렬과 쓰레드레벨 병렬의 시너지가
날듯합니다.( 여기에 추가적으로 GPU레벨 믹스도 추상화가 높아지면 쉬워지겠네요. )

참고로 c++ 은 저렇게 언어 차원에서 저런식의 병렬을 지원하지 않으며 openMP 나 PPL 같은
외부 라이브러리를 통해  지원합니다.




순서

1) 하둡 RPC
2) 하둡 스트리밍
3) 하둡 & 보안
4) 하둡 HDFS 읽기
5) 하둡 HDFS 쓰기
6) 하둡 IO  (Writable / Avro)
7) 하둡 & 가용성  (Zookeeper) 
8) 하둡 쉘 스크립트 및 환경


하둡은 HDFS 라는 분산파일시스템과 맵리듀스라는 그것을 이용하여 계산을 하는 도구를 가지고있습니다.  (YARN 이전) 계산에는 간단한 배치성 작업이 주를 이루며 다양한 머신러닝 알고리즘 (머하웃 라이브러리) 을 실행할수도 있습니다.계산복잡도,알고리즘형태에 따라서  지라프,하마같은 다른 도구를 사용할수도 있으며 , 하둡 YARN 과 함께 다양한 빅데이터 솔루션들이 하모니를 이루고 있습니다. Storm-yarn 같은 도구를 사용하여 실시간 분석을 용이하게 할수도있으며, 메모리를 적극적으로 활용한 Spark 라는 제품도 각광을 받고 있습니다.



HDFS 라는 분산파일 시스템은 여러대의 컴퓨터에 파일을 분산하여 놓고 읽기/쓰기를 지원해주는 단순한 기능을 합니다.
NameNode 는 분산된 파일의 위치/크기등의 정보를 가지고있으며
DataNode 는 실제 데이터를 가지고있습니다.

하둡 클라이언트에서 파일을 가져오기 위해서는

0. KDC (케버로스센터) 에 네임노드 / 데이터노드 / 클라이언트는 각각 인증합니다.
1. 네임노드와 데이터노드간에 서로 비밀키를 교환합니다.
2. 클라이언트는 KDC 에 TGT 를 요청합니다.
3. 클라이언트는 KDC 에 네임노드 서비스 티켓을 요청합니다.
4. NameNode 에게 내가 원하는 파일의 위치 (블럭ID) 를 알려달라고 요청합니다. (티켓과 함께)
5. 1번에서 획득한 블럭ID  및 블록 접근 토큰을 가지고 해당 DataNode 에게 데이터를 달라고 요청합니다.

위에

4번에서 NameNode 의 특정 함수를 호출하여 원하는바를 이루는데. 그때 하둡RPC 가 사용됩니다.
5번에서 데이타를 가져올때는 하둡 스트리밍 (TCP/IP) 를 이용합니다.
나머지 0~3번은 보안과 관련된 이슈이므로 다음 연재때 자세히 다뤄보도록 하겠습니다.


하둡 RPC 를 이해하기위해서는 자바기술에 대한 몇가지 선행학습이 필요합니다. (하둡은 자바로 만들어짐) 


1. 자바 리플렉션 ( http://socurites.com/60 )

public class TestServer implements RPCProtocol {

Class  cls  = Class.forName(“RPCProtocol“);

Class paramTypes[] = new Class[2];
pramTypes[0] = String.TYPE;
pramTypes[2] = Integer.TYPE;

Method meth = cls.getMethod(“heartbeat”, paramTypes);

Object arglist[] = new Object[2];
arglist[0] = new String(“hello world”)
arglist[1] = new Integer(2);


RPCProtocol rp  =  new  TestServer ();

Object retObj = meth.invoke(rp, arglist);

Integer retval = (Integer) retObj;

중요포인트 ( 필요사항 ) :    실제 객체 /  클래스타입 /  메소드이름 /  인자타입 / 실제 인자 /    

Hadoop  에서 각각에 대한 변수명 

                     instance (실제객체) 
                     protocol   (클래스타입) 
                     Invoocation  ( 메소드이름, 인자클래스, 실제 인자값)


2. 자바 Dynamic Proxy (http://javacan.tistory.com/entry/115 

public class MyDebugInvocationHandler   implements java.lang.reflect.InvocationHandler {    
        private Object target = null;
          public void setTarget(Object target_) {
            this.target = target_;
        }
        
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("Going to execute method : " + method.getName);
                Object retObject = method.invoke(target, args);
                System.out.println("After execute method : " + method.getName());
                return retObject;
        }
    }


-----------


 IMyBusinessObject bo = new MyBusinessObject(); // 실제 비즈니스 객체
  MyDebugInvocationHandler aMyDebugInvocationHandler = new MyDebugInvocationHandler();
  aMyDebugInvocationHandler.setTarget(bo);
    
 IMyBusinessObject proxyObject =    (IMyBusinessObject) Proxy.newProxyInstance  // 프록시 객체 생성
          (IMyBusinessObject.class.getClassLoader(),
          new Class[] { IMyBusinessObject.class }, // 프록시할 인터페이스
          aMyDebugInvocationHandler); // InvocationHandler
    
    // 프록시 객체 사용
    System.out.println(proxyObject.doExecute("Hello World"));


3. 자바 IO /NIO ( http://javacan.tistory.com/entry/87 

* InputStream / OutputStream
* Selctor
* Channel
* ByteBuffer
* Socket


이제 하둡 RPC 코드를 보도록 합시다.

먼저 그림을 통해 NIO 서버 흐름도를 살펴보도록 하겠습니다.




* Listener 를 통해서 대기하고있다가 Reader 쓰레드중 하나가 응답을 받아서 큐에 메세지를 넣음.
* Handler 에서 실제 함수를 리플렉션을 이용해 호출해주고 리턴값을 받아서 Response 함.


다음은 패킷의 구성을 살펴보겠습니다.


*  헤더를 통해서 인가 과정을 거치고 Invocation 객체가 직렬화되어 전달되어 집니다.


클라이언트 와 서버간의 인터렉션을 나열해 보면

클라이언트

1. 호출할 함수를 선언한 NameNode (ClientProtocol)  인터페이스로  다이나믹 프록시를 생성
2. 생성된 프록시로 함수호출 
3. 내부에서 클라이언트 객체 하나를 풀에서 꺼내어 서버와 connection 을 맺음
4. 서버로 인터페이스 타입, 메소드 이름/타입, 메소드인자타입/ 값등 을 보내줌
5. 응답이 올때까지 대기
6. 응답이 오면 connection 을 끊고 리턴값을 완전한 writable 객체로 리턴함.  
7. writable 객체에서 값을 꺼내어 리턴.  끝.


서버 

1. 실제 구현이 있는 함수를 가진 인터페이스를 구현한 NameNode 객체에서 서버객체 생성
2. 서버객체는 Listenning 을 하며 대기.
3. 클라이이언트과 connection  을 맺고 패킷을 받은후 Read 객체는 그것을 큐에 삽
4. Handler 객체는 큐에서 패킷을 꺼내어 자바리플렉션기술을 이용하여 호출할 함수와 
    자신이 가지고있는  NameNode 객체를 이용하여 NameNode 의 함수를 호출함.
5. 함수결과값을 Response 객체를 통해서 클라이언트에 전송  



이제  DFSClient   (클라이언트) ------ RPC ------>  NameNode (서버)  의 코드를 짧게 살펴보겠습니다.


DFSClient (클라이언트 측) 

DFSClient

(ClientProtocol)RPC.getProxy(ClientProtocol.class,
        ClientProtocol.versionID, nameNodeAddr, ugi, conf,
        NetUtils.getSocketFactory(conf, ClientProtocol.class), 0,..)

 this.namenode = createNamenode(this.rpcNamenode, conf);
 
 namenode.getBlockLocations(src, start, length);

DFSClient 클래스에서 원격에 ClientProtocol  인터페이스를 구현한 객체가 가지고있는 함수 (getBlockLocations) 
하나를  호출하기위해  Proxy 를 만듭니다. 저 Proxy 는 원격함수를 호출하는 공통코드를 가지고있습니다. 
보통 Proxy or Decorator  는 웹개발에서 트랜잭션설정, 로그삽입, 필터기능등을 위하여 사용됩니다. 

RPC

1)
   final Invoker invoker = new Invoker(protocol, addr, ticket, conf, factory,
        rpcTimeout, connectionRetryPolicy);
    VersionedProtocol proxy = (VersionedProtocol)Proxy.newProxyInstance(
        protocol.getClassLoader(), new Class[]{protocol}, invoker);

2)

  private static class Invoker implements InvocationHandler {
    private Client.ConnectionId remoteId;
    private Client client;
   
    private Invoker(Class<? extends VersionedProtocol> protocol,
                     InetSocketAddress address, 
                     UserGroupInformation ticket,
                     Configuration conf, 
                     SocketFactory factory,
                     int rpcTimeout, 
                     RetryPolicy connectionRetryPolicy) throws IOException {
   
       this.remoteId = Client.ConnectionId.getConnectionId(address, protocol,
          ticket, rpcTimeout, connectionRetryPolicy, conf);
       this.client = CLIENTS.getClient(conf, factory);
    }

    public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    
      ObjectWritable value = (ObjectWritable)
        client.call(new Invocation(method, args), remoteId);
     
      return value.get();
    }

1) 번은  Dynamic proxy 를 만드는 코드입니다.  

2) 번은 InvocationHandler 구현클래스의 내용입니다. 클라이언트객체풀에서 하나 가져와서 
    클래스 타입, 보안관련, 메소드이름, 인자, 인자타입등을 Invocation 클래스에 담아서 서버로 보내줍니다. 


Client


public Writable call(Writable param, ConnectionId remoteId)  
                       throws InterruptedException, IOException {
    Call call = new Call(param);
    Connection connection = getConnection(remoteId, call);
    connection.sendParam(call);                 // send the parameter

 하둡에서는 자바의 직렬화 및 RPC 를 사용하지 않습니다. 범용적으로 구현되있기때문에 성능에
 문제를 가져오기때문입니다.  따라서 Writble 이라는 인터페이스를 통해 아주 간단히 직렬화를 합니다. 
 Invocation 클래스는 Writble 을 상속받았음을 알수있습니다. 




NameNode (서버측) 

NameNode 

public class NameNode implements ClientProtocol, DatanodeProtocol,

   this.server = RPC.getServer(this, socAddr.getHostName(),
        socAddr.getPort(), handlerCount, false, conf, namesystem
        .getDelegationTokenSecretManager());

실제 분산노드에 저장되어있는 파일을 관리하고있는 클래스입니다.  ClientProtocol 를 구현했다는것을 알수있습니다.
클라이언트측에서 ClientProtocol  를 이용하여 Proxy 를 만든것을 보았을것입니다.  
NameNode 객체는 RPC 서버를 만드는데 포인트는 첫번째 인자로 자신을 넣어준다는것입니다. 
자바리플렉션에서 함수호출할경우 첫번째 인자로 호출하려면 함수를 가지고있는 객체를 받기때문입니다. 나머지는 소켓서버를 생성하기위한 포트 및 환경설정등입니다) 


RPC

public static Server getServer(...){    
      return new Server(instance, conf, bindAddress, port, numHandlers, verbose, secretManager);
  }

 public static class Server extends org.apache.hadoop.ipc.Server {

  public Writable call(Class<?> protocol, Writable param, long receivedTime) 
        Invocation call = (Invocation)param;
        if (verbose) log("Call: " + call);

        Method method =
          protocol.getMethod(call.getMethodName(),
                                   call.getParameterClasses());
        method.setAccessible(true);

        long startTime = System.currentTimeMillis();
        Object value = method.invoke(instance, call.getParameters());

추상클래스 Server 를 상속받아서 자신의 Server 클래스를 만듭니다. call 메소드를 오버라이드 한것을 볼수있습니다.
Server 에서  클라이언트 측에서 보낸 Invocation 객체를 해석하고 호출해주는 코드가 담겨져있습니다.

Server
 Server
   listener = new Listener();
  
 Listener 
      void doAccept(SelectionKey key) 

  Reader 
    void doRead(SelectionKey key) 

  Connection 
         public int readAndProcess() 
         private void processData(byte[] buf) 
   
  Call 
  call = new Call(id, param, this); 
  callQueue.put(call); // queue the call; 

  Handler 
    final Call call = callQueue.take(); // pop the queue; 
    value = call(call.connection.protocol, call.param, call.timestamp); 

 Response
서버 클래스입니다.

일반적인 서버측 행위를 수행합니다. 클라이언트를 기다리고 , 받아드리고 , 보낸 데이터를 읽어서 처리합니다. 
processData 함수에서 큐에 넣고 핸들러에서 pop 해서 call 합니다. call 에서는 리플렉션기능

 Invocation call = (Invocation)param;
        
 Method method = protocol.getMethod(call.getMethodName(),call.getParameterClasses());
      
 Object value = method.invoke(instance, call.getParameters());
으로  함수를 호출해주고
리턴객체를 클라이언트로 보내줍니다. 리턴객체또한 Writble 로 직렬화 됩니다.


이상 마치고  다음에는 하둡 스트리밍에 대해서 살펴보겠습니다.
(시간 날때마다 코드조각들을 더 넣고 설명도 더 추가하겠습니다. 조금 짧은감이있군요 -.-a) 



관련 클래스 모음 



+ Recent posts