2011년 11월 16일 수요일

Android Native 공유 라이브러리 연동

[개발환경]
1. Cygwin 설치
  가. http://www.cygwin.com 에서 다운받는다.
  나. Select package 화면에서 gcc-core, g++, make 패키지를 검색해서 설치한다.


2. NDK 설치
  가. http://developer.android.com/sdk/ndk/index.html 에서 다운받아 압축을 푼다.
  나. 시스템 변수 path영역에 압축푼 경로를 추가한다.
       ex) D:\AndroidNDK\android-ndk-r6b


3. 테스트
  가. cygwin을 실행해서 >ndk-build 를 실행하여 관련 메시지가 나타나는지 확인한다.




[개발 테스트]
1. Android  프로젝트를 생성한다.
  가. 프로젝트 생성 : NDKExam
// =start=======================================================

package org.example.ndk;


import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;


public class NDKExam extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        TextView tv = new TextView(this);
        int x = 1000;
        int y = 42;
        
        System.loadLibrary("ndk-exam");
        
        int z = add(x, y);
        
        tv.setText("The sum of " + x + " and " + y + " is " + z);
        setContentView(tv);
    }
    
    public native int add(int x, int y);
}

// = end =======================================================




2. 헤더 생성은 링크 클릭 => JNI 네이티브 함수 헤더 생성하기




3. JNI 네이티브 함수 구현하기
  가. eclipse 패키지에서 "jni" 폴더 생성한다.
  나. second.c 파일을 생성한다.
// =start=======================================================

#include "first.h"
#include <jni.h>


jint Java_org_example_ndk_NDKExam_add(JNIEnv* env,
 jobject this,  jint x,
 jint y)
{
return first(x, y);
}

// = end =======================================================




  다. first.c 파일을 생성한다.
// =start=======================================================

// first.c
#include "first.h"


int first(int x, int y)
{
return x + y;
}

// = end =======================================================


  라. first.h 파일을 생성한다.
// =start=======================================================

// first.h
#ifndef FIRST_H
#define FIRST_H


extern int first(int x, int y);


#endif /* FIRST_H */

// = end =======================================================


  마. jni 폴더에 Android.mk 파일을 생성한다.
// =start=======================================================

# 소스 파일 위치
LOCAL_PATH:= $(call my-dir)


# Make 관련 환경 변수를 초기화
include $(CLEAR_VARS)


# 라이브러리를 빌드하기 위한 정보 생성(라이브러리 이름, 소스 코드등)
LOCAL_MODULE := ndk-exam
LOCAL_SRC_FILES := first.c second.c


# 공유 라이브러리 생성
include $(BUILD_SHARED_LIBRARY)

// = end =======================================================


※ Android.mk 작성 문법 관련해서는 <NDK_HOME>\docs 파일 참조




  바. <PROJECT-ROOT>ndk-build 실행


  사. eclipse에서 android run하여 에뮬이나 디바이스에서 결과 확인.

JNI 네이티브 함수 헤더 생성하기

오랜 인고의 이틀간 삽질 끝에 드디어 파일이 생성 되었다.. ㅠㅠ;;


개발환경
OS : Windows 7 pro
Eclipse : Indigo Service Release 1 Build id: 20110916-0149
Java : jdk7


왜 개발환경을 쓴고하니... 인터넷에 찾아보니깐 갖가지 방법들이 나왔는데
내 환경에서는 되지 않았다. 클래스와 android.jar사이 구분자가 ";"로 하니 되는데
어느 블로그에는 ":"로 했더니 되더란다.. 환경은 리눅스환경인듯.. 아무튼
윈도우 환경에서는 아래 코드를 넣고 실행하니 헤더가 생성되었다~~ 아싸...ㅡㅡ;;


D:\project\NDKExam>javah -classpath bin/classes;D:\MyDevelopment\Android\android-sdk\platforms\android-8\android.jar org.example.ndk.NDKExam




아.. 참고로 책에는 이렇게 나와있었다.. 윈도우환경이었다.
H:\project\NDKExam>javah -classpath bin org.example.ndk.NDKExam

2011년 11월 11일 금요일

[JNI] C에서 Java호출하기

// JniTest.java
public class JniTest{
public static void main(String[] args){
System.out.print("Hello World " + args[0]);
}
}

// 빌드는 아래와 같이 하면 JniTest.class 파일이 생성된다.
>javac JniTest.java



// JniCall.c

#include

#define PATH_SEPARATOR ';'
#define USER_CLASSPATH "."

void main(){
JNIEnv *env;
JavaVM *jvm;
jint res;
jclass cls;
jmethodID mid;
jstring jstr;
jclass stringClass;
jobjectArray args;

JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString = "-Djava.class.path=" USER_CLASSPATH;
vm_args.version = 0x00010002; // 버전정보를 입력한다.
vm_args.options = options; // 옵션할당
vm_args.nOptions = 1; // 옵션의 아규먼트 갯수를 지정한다. 하나이므로 1
vm_args.ignoreUnrecognized = JNI_TRUE; // 자바가상머신이 옵션값이 잘못되어도 무시하고 진행.. 반대는 JNI_FALSE

/* Create the Java VM */
// 자바 가상머신 생성.. env주소를 넘기는건 여기 담아주셔요~~라고하는 것
// env에는 JNI 인터페이스 포인터 주소가 저장된다.
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (res < 0) {
fprintf(stderr, "Can't create Java VM\n");
exit(1);
}

// JniTest 클래스를 로드한다.
cls= (*env)->FindClass(env, "JniTest"); // 빌드된 JniTest.class가 있어야 겠죠.
if (cls == 0) {
fprintf(stderr, "Can't find JniTest class\n");
exit(1);
}
// 자바 소스에서 main함수 ID를 가져온다.
mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");

// String jstr = "from C!"; 이녀석이랑 똑같음.
jstr = (*env)->NewStringUTF(env, "from C!");

// String 클래스를 가져온다.
stringClass = (*env)->FindClass(env, "java/lang/String");

// 자바 메인함수가 main(String[] args) 이렇게 구성되어있으니..
// 똑같이 배열로 오브젝트를 구성한다.
args = (*env)->NewObjectArray(env, 1, stringClass, jstr);

// main함수를 호출합니다!
(*env)->CallStaticVoidMethod(env, cls, mid, args);

// 예외처리
if((*env)->ExceptionOccurred(env)){
(*env)->ExceptionDescribe(env);
}

(*jvm)->DestroyJavaVM(jvm);
}


// 빌드는 반드시 Visual Studio Command Prompt를 실행하여 아래 명령어를 실행한다.
cl -I"D:\MyDevelopment\Android\Java\jdk1.7.0\include" -I"D:\MyDevelopment\Android\Java\jdk1.7.0\include\win32" invocationApi.c -link "D:\MyDevelopment\Android\Java\jdk1.7.0\lib\jvm.lib"

// invocationApi.exe 파일이 생성된다.



// 빌드가 완료되었으니 실행해보자!
>invocationApi.exe
Hello World from C!

// 위와 같이 출력되면 OK!


[JNI] Java에서 C 호출하기

// HelloJNI.java
class HelloJNI
{
//네이티브 메서드 선언
native void printHello();
native void printString(String str);

// library loading
static { System.loadLibrary("hellojni"); }

public static void main(String args[])
{
HelloJNI myJNI = new HelloJNI();
myJNI.printHello();
myJNI.printString("Hello World from printString fun");
}
}
// 빌드는 아래와 같이 진행
>javac HelloJNI.java
// HelloJNI.class 파일이 생성된다.



// HelloJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: printHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_printHello
(JNIEnv *, jobject);

/*
* Class: HelloJNI
* Method: printString
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloJNI_printString
(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif



// hellojni.c
#include "HelloJNI.h"
#include

JNIEXPORT void JNICALL Java_HelloJNI_printHello (JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
return;
}

JNIEXPORT void JNICALL Java_HelloJNI_printString (JNIEnv *env, jobject obj, jstring string)
{
const char *str = (*env)->GetStringUTFChars(env, string, 0);
printf("%s!\n", str);
return;
}


// 빌드는 아래와 같이 진행
>cl -I"D:\MyDevelopment\Android\Java\jdk1.7.0\include" -I"D:\MyDevelopment\Android\Java\jdk1.7.0\include\win32" -LD hellojni.c -Fehellojni.dll
// Fehellojni.dll 파일이 생성된다.




// 빌드가 완료되었으니 이제 실행해보자!
>java HelloJNI
Hello World!
Hello World from printString fun!

// 위 처럼 출력결과가 나타난다.(실행된 코드 그대로 올렸으니 소스상 문제는 없을것이다.)

[JNI] 개발 환경 설정

[개발환경]
1. http://www.microsoft.com/express/download/ 경로에서
Visual Studio 2010 Express Professional 버전을 설치한다.
반드시 2010일 필요는 없음

2. 설치가 완료되면 Visual Studio Command prompt 실행

JNI 소스 빌드시 2번에서 실행한 커맨드 창에서 명령어 입력해서 빌드해야한다.

2011년 5월 26일 목요일

eclipse 설치

ubuntu JDK6 및 thrift설치

ubuntu 10.10에서 JDK6 설치하는 방법

# 수정시 아래 경로 파일
apt sources path : /etc/apt/sources.list

$ sudo apt-get install python-software-properties
$ sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"
$ sudo apt-get update
$ sudo apt-get install sun-java6-jdk

// 설치된 자바 목록을 확인
$ sudo update-java-alternatives -l
java-1.5.0-sun 53 /usr/lib/jvm/java-1.5.0-sun
java-6-sun 63 /usr/lib/jvm/java-6-sun

// 사용하려는 자바 버젼으로 변경한다.
$ sudo update-java-alternatives -s java-6-sun

// 자바 버젼을 확인한다.
$ java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) Server VM (build 16.3-b01, mixed mode)

thrift 설치
1. 기타 시스템 요구사항 설치
$ sudo apt-get install libboost-dev automake libtool flex bison pkg-config g++

// When need a package.
$ sudo apt-get install php5-dev python-dev

2. http://incubator.apache.org/thrift 에서 thrift 다운로드. 경로는 "/usr/local/thrift"
$ tar xvzf thrift-0.5.0.tar.gz
$ cd thrift-0.5.0
$ sudo ./configure JAVAC=/usr/lib/jvm/java-6-sun --prefix=/usr/local/thrift
$ sudo make
$ sudo make install






// Add path
vi ~/.bashrc
// Append to last of line
export PATH=/usr/local/thrift/bin:$PATH
// apply to PATH
$ source ~/.bashrc
// test
$ thrift

$ cd /usr/local/thrift-0.5.0/lib/java

// If no install ant
$ sudo apt-get install ant

// java class compile
$ sudo ant

sar 명령어

ubuntu에서 sar를 사용하기.

Ubuntu에서 기본적으로 SAR를 제공하지 않는다.
그래서 설정하는 방법을 정리 해보았다.
먼저 설치를 해야 하는데.
$sudo apt-get install sysstat
이렇게 하면 한참 시간이 걸리면서 설치가 완료가 된다.

이제 설정 부분이다.
변경을 해야하는 파일은 /etc/init.d/sysstat, /etc/cron.d/sysstat, /etc/cron.daily/sysstat, /etc/default/sysstat 이다.
ENABLE="false" --> ENABLE="true"
라고 모두 변경한다.

$sudo /etc/init.d/sysstat start
이러면 서비스가 가동된다.

새로운 ubuntu버전에서는
/etc/default/sysstat에서만 변경을 하고
/etc/init.d/sysstat start만 하면 됩니다.

2011년 5월 1일 일요일

ZooKeeper 데이터모델

일반 파일 시스템은 파일에만 데이터를 저장할 수 있지만 주키퍼는 모든 노드에 데이터를 저장할 수 있다. 모든 디렉토리 구조(/dir1, /dir2)등에도 데이터를 저장한다. (무슨 차이지? ㅡㅡa)

파일 시스템은 로컬에 저장돼 있거나 마운트돼 있는 파일과 디렉토리에 대한 접근을 수행하지만 주키퍼는 클라이언트 라이브러리를 이용해 네임스페이스에 대한 조회나 노드에 저장된 데이터를 원격 클라이언트에서도 접근할 수 있는 분산 시스템이다.

주키퍼의 모든 데이터는 서버의 메모리에 존재하기 때문에 주키퍼에 저장할 수 있는 전체 데이터의 용량은 주키퍼 데본 서버가 할당받은 힙 메모리 영역을 넘지 못한다. 따라서 주키퍼에서 저장되는 데이터는 일반 파일과 같은 데이터가 아닌 시스템 관리, 모니터링, 락 관리 등에 필요한 메타 정보만 저장하는 것이 일반적이다. 데이터는 메모리에 존재하지만 트랜잭션 로그와 스냅샷 파일은 로컬 디스크에 저장돼 주키퍼 서버를 재시작 했을때 에도 데이터는 영속적으로 존재한다.

* 패스
: 노드를 식별하는 식별자는 파일 시스템의 디렉토리 구조와 동일하게 '/'로 구분된다.

패스 문자 규약
- null 문자(0x000)
- \u001 ~ \u0019, \u007F ~ \u009F
- \ud800 ~ \uF8FFF, \uFFF0 ~ \uFFFF, \uXFFFE ~ \uXFFFF, \uF0000 ~ \uFFFFF
- zookeeper : 시스템에서 사용하는 예약어이기 때문에 사용할 수 없음.
- 상대 경로를 나타내는 '.', '..' 는 파일시스템 처럼 현재 또는 상위 디렉토리 의미가 아닌 문자자체로 인식한다.

* 시간
: 주키퍼는 하나 이상의 서버에서 수행되기 때문에 클라이언트의 처리 요청에 따른 버전정보나 시간정보등에 대해 모든 주키퍼 서버가 공유해야한다. 아래와 같은 방법들로 시간 버전정보를 관리한다.

- 트랜잭션 아이디(Zxid) : 주키퍼에 저장된 노드 상태변경에 부여된 트랜잭션ID로 모든 변경 요청에 대해서 순서적으로 부여되고 zxid1은 zxid2보다 먼저 요청되었음을 보장한다.
- 버전 번호 : 노드 데이터 변경시 마다 버전값이 증가한다. 버전종류로는 z노드 데이터 변경, 자식노드 데이터 변경, ACL변경에 대해 각각 다른 버전이 부여된다.
- 경과시간(Tick time) : 세션 타임아웃, 커넥션 타임아웃등과같은 이벤트의 경과 시간을 의미.
- 시스템 시간 : 대부분 사용하지 않으나 노드 생성, 수정 일시에 대한 정보는 시스템 시간이용.

* Z 노드
: 계층적인 네임스페이스에서 각 노드를 z노드라고 한다. '/', '/dir1', '/dir2/a' 모두 z노드다. 파일시스템의 디렉토리나 파일에 해당하지만 다음과 같은 특징이있다.
1. 부모 노드에도 데이터를 저장할 수 있다.
2. 크기가 작은 데이터 위주로 저장 : 서버의 상태, 락 정보, 환경설정 정보등과 같은 크기가 작은 메타 데이터를 주로 저장한다.
3. 버전 : z노드에 저장되는 모든 데이터는 버전을 갖고 있다.
4. 접근권한 : z노드 단위로 권한(ACL)을 관리할 수 있다.

주키퍼 클라이언트 라이브러리
: 대부분 z노드 연산과 관련돼 있으며, 단순기능의 API만제공한다. 자바, C, 파이썬, 펄등과 같은 다양한 언어를 제공한다. 아래는 라이브러리 API에 대한 설명이다.

- exists : 특정 노드 존재 여부 확인. 리턴타입은 boolean이 아닌 Stat객체반환하고 값이 null이면 존재하지 않음을 의미한다.
- create : 노드를 생성한다. 생성시 노드 데이터도 같이 입력할 수 있다. 최대 1MB까지 가능하고 이미 존재하는 노드면 KeeperException.NodeExistsException을 발생.
- delete : 특정노드를 삭제한다. 존재하지 않으면 KeeperException.NoNodeException을 발생.
- getData : 노드 데이터를 가져고 존재하지 않으면 KeeperException.NoNodeException을 발생.
- setData : 노드 데이터를 설정하고 버전이 맞지 않으면 Exception이 발생하며 버전값이 -1이면 버전에 상관없이 값을 설정한다.
- getChildren : 노드의 자식 노드 목록을 가져온다.
- sync : 주키퍼는 모든 서버에 복제본이 저장될 때까지 기다린다.

* 주키퍼 서버목록 : 주키퍼 서버가 여러 대 이면 ','로 구분해 입력한다.(예:server1:2181, server2:2181) 서버정보에 루트 패스를 지정할 수도 있다. 'server1:2181, server2:2181/app/myapp1' 같이 정보를 주면 server2에서는 app/myapp1을 루트 노드로 인식한다.
* 세션 타임아웃 : 주키퍼 서버와 클라이언트 연결에 대한 타임아웃 시간이고 밀리초단위.
* 와처 : 이벤트 리스너를 등록한다.


주키퍼의 용도
: 클라이언트 애플리케이션은 주키퍼 객체를 생성한 후 주키퍼 클래스에서 제공하는 메소드를 이용해 노드를 관리한다. 노드에 저장하는 정보에 따라 용도가 달라진다. 노드에 서버정보를 저장하면 클러스터 멤버십 관리 용도가 될 수 있고, 메시지를 저장하면 메시지 큐로 활용할 수 있다.

API의 동기 및 비동기
: 비동기 API를 사용하면 클라이언트는 주키퍼 서버로 요청만 보내고 반환 값을 기다리지 않고 바로 다음 처리를 진행한다. 서버로 부터의 응답은 백그라운드 스레드에 의해 요청 시 등록된 콜백 메소드를 통해 받는다. 비동기 API는 병렬로 주키퍼 서버에 많은 작업을 처리하는 클라이언트에서 활용할 수 있다. 비동기 요청에 대해서 서버로 부터의 응답은 요청한 순서에 따라 받는다.
비동기 호출을 하더라도 처리되는 순서는 주키퍼 서버에 의해 보장된다.
서버측 결과를 기다리지않고 바로 다음 코드를 실행할 수 있고 문제 발생시 콜백 메소드에서 받아서 처리할 수 있기 때문에 주키퍼에 많은 요청을 보내야하는 클라이언트의 경우 성능을 최대로 높일 수 있다.
비동기 호출에서 클라이언트 라이브러리는 내부적으로 멀티 스레드로 동작한다. 비동기 호출뿐만 아니라 주키퍼 서버로의 heartbeat메시지 전송, 서버로부터의 이벤트 수신 등이 추가로 스레드를 생성해서 실행된다.

// 비동기 API 코드==========================================
package kr.co.jaso.zk.basic;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.AsyncCallback.StringCallback;
import org.apache.zookeeper.ZooDefs.Ids;

public class AsyncAPITest implements Watcher {
private ZooKeeper zk;
private Object monitor = new Object();
class CreateCallBack implements StringCallback {
@Override // create이후 "do something"이 실행되고 난뒤에 processResult가 실행됨.
public void processResult(int rc, String path, Object ctx, String name) {
if (rc == 0) {
System.out.println("콜백 수신 성공");
} else {
System.out.println("콜백 수신 실패");
}
//callback 받으면 테스트 프로그램을 종료시키기 위해 main 흐름으로 알려준다.
synchronized (monitor) {
monitor.notifyAll();
}
}
}

public void start() {
try {
zk = new ZooKeeper("127.0.0.1:2181", 5000, this);

//callback 함수와 같이 호출하 async 호출이 된다.
zk.create("/async_test101", "test".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new CreateCallBack(), null);

//이 부분은 create 호출 후 서버로부터 응답이 오지 않아도 바로 실행된다.
System.out.println("do something");

//아래 코드가 없으면 테스트 프로그램이 바로 종료되기 때문에
// callback이 실행되기 전에는 종료 안되게 하기 위한 코드
synchronized (monitor) {
monitor.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void process(WatchedEvent event) {
}

public static void main(String[] args) {
(new AsyncAPITest()).start();
}
}

//==================================================


* 비동기 호출을 위한 콜백 클래스를 정의해야한다. 메소드별 콜백 클래스는 다음과 같다.
- create : StringCallback
- delete : VoidCallback
- exists : StatCallback
- getACL : ACLCallback
- getChildren :ChildrenCallback
- getData : DataCallback


* Z노드 속성
: 주키퍼가 분산 코디네이터의 역할을 수행할 수 있게 하는 z노드의 중요한 몇가지 속성이있다.
1. Stat
: z노드의 상태 정보를 저장한다. z노드 생성 시에는 byte[]정보만 전달하지만 주키퍼 내부적으로는 사용자가 전달한 데이터 이외에 부가적인 정보를 Stat객체로 저장한다.
-----------------------------------------------------------------
- czxid z노드의 생성 트랜잭션에서 사용된 zxid
- mzxid z노드의 마지막 수정 트랜잭션에서 사용된 zxid
- ctime z노드가 생성된 시스템 시간
- mtime z노드가 마지막 수정된 시스템 시간
- version z노드가 수정된 횟수
- cversion z노드의 자식의 갯수가 수정된 횟수
- aversion z노드의 ACL이 수정된 횟수
- ephemeralOwner 임시노드이면 z노드를 생성한클라이언트의 세션아이디이고, 임시노드아니면0
- dataLength 데이터 필드의 길이
- numChildren z노드의 자식수
-----------------------------------------------------------------
2. 와처(Watcher)
: 클라이언트는 모든 z노드에 모니터링 객체(callback object)를 등록할 수 있다. z노드에 데이터 수정, 삭제, 자식 노드 추가/삭제, ACL변경 등의 연산이 수행되면 z노드에 등록된 모니터링 객체의 콜백 메소드인 process()메소드를 호출해준다.
3. 원자적 데이터 처리(atomic)
: z노드에 대한 데이터의 조회와 저장은 원자성을 가진다.데이터의 일부만 저장 또는 조회되지않는다.
4. 영속성 노드(Persistent Node)
: 'Persistent'옵션으로 생성된 z노드는 주키퍼 서버의 로컬 디스크에 영구히 저장되고 클라이언트의 삭제 요청에 의해서만 삭제된다. 주키퍼 서버(클러스터)가 재시작 돼도 존재한다.
로컬 디스크에 저장된다는 말은 커밋 로그나 스냅샷 파일에 저장된다는 의미이며, 모든z노드는 생성 옵션에 상관없이 메모리에 존재한다. 따라서 주키퍼 서버에 저장할 수 있는 용량은 주키퍼 서버의 모모리 용량에 제한된다.(참조:서버/인프라를 지탱하는 기술 24시간 365일)
5. 임시노드(Ephemeral Node)
: 노드를 생성한 클라이언트와의 세션이 끊기면 해당 노드도 자동으로 삭제되는 노드다. 임시노드는 자식노드를 가질 수 없으며 임시노드의 부모는 반드시 영속성(Persistent로 생성된)노드여야됨
6. 시퀀스 노드(Sequence Node)
:프로그램 개발시 유일한 값이 필요한 경우가 있는데 싱글 프로세스나 동일 머신에서는 스레드 락이나 파일락을 이용할 수 있지만 분산시스템에서는 쉽지가않다. 가장 많이사용하는 방법은 DB테이블에서 자동으로 증가하는 칼럼을 이용하는 방법이다. 그러나 클라이언트가 많을시 서버에 많은 락을 유발함으로써 성능에 좋지않다. 시퀀스노드는 유일한 값을 쉽게 만들 수 있는데 자식노드의 이름값에 순차적으로 증가하는 번호를 부여하는 방식이다. 예를 들면 '/test'에 시퀀스 노드 옵션으로 생성하면 '/test0000000001'과 같이 만들어준다. 시퀀스 숫자의 범위는 '2147483647'이며 오버 되면 '-2147483647'로 된다.

ZooKeeper 설치

주키퍼 서버 자체는 자바로 개발됐으며 시스템 요구사항은 다음과 같다.
- 리눅스, 유닉스, 윈도우즈 등 자바를 설치할 수 있는 모든 운영체제
- JDK 1.6이상

http://hadoop.apache.org/zookeeper 에서 최신 버전을 다운로드한다.

# 설치 경로로 이동시킨다.
$ sudo mv zookeeper-x.y.z /usr/local/

# 압축을 푼다.
$ sudo tar -zxsf zookeeper-x.y.z.tar

$ cd /usr/local/zookeeper-3.3.3/

# 설정항목을 복사한다.
$ sudo cp conf/zoo_sample.cfg conf/zoo.cfg

# 필요한 설정들을 한다.
$ vi conf/zoo.cfg

# dataDir : 변경로그나 snapshot 파일을 저장할 로컬 디렉토리
# clientPort : 클라이언트 요청을 받을 포트 번호(기본 2181)

# 서버 시작
$ bin/zkServer.sh start

# 서버 중지
$ bin/zkServer.sh stop

# 아래 명령으로 정상 수행 되었는지 확인
$ bin/zkCli.sh

ZooKeeper

- 분산 코데네이터 서비스를 제공하는 아파치 오픈소스.

- 다양한 분산된 서버를 관리하는 기능수행.

하기 수행을 쉽게 구현할 수 있는 기능들을 제공한다.


  • 네임 서비스, 환경설정, 그룹 멤버쉽

  • Double Barriers

  • 우선순위큐

  • 공유 락 제어

  • 두단계 커밋

  • 리더 선출

위의 기능들을 쉽게 구현할수 있게 메커니즘을 제공하는것이다.
구글의 처비(Chubby)를 참고해 만들었다.


※ 주키퍼 시스템 구성
: 분산 환경을 쉽게 관리하는 기능을 제공하고 이 역시 여러대의 서버로 구성된 분산시스템이다.
N개의 서버와 클라이언트 API로 구성돼 있다.
- 파일 시스템과 비슷하게 계층적인 데이터를 저장하여 구성한다. 이 데이터들은 주키퍼 서버들
사이에 복제돼 일부 주키퍼 서버가 장애가 발생해도 서비스에 무리가 없도록 한다.






주키퍼 서버에 데이터의 추가, 삭제, 값 변동등의 상태가 변경이 되었을때 주키퍼 서버가 클라이언트로 변경사항을 전달하는 이벤트를 발생시킨다.


주키퍼 클라이언트는 라이브러리에서 단순한연산을 제공한다. 사용자 애플리케이션은 이라이브러리를 이용해 주키퍼 서버에 데이터를 저장, 삭제하는 연산을 수행하거나 이벤트를 등록한다.


분산시스템에서 가장고려해야할 SPOF(Single Point Of Failure)이다. SPOF는 분산시스템 구성에서 장애가 발생하면 전체 시스템에 장애가 발생할 수 있는 지점을 말한다. 이런 지점은 반드시 이중화 구성을 해서 SPOF를 제거해야 한다. 다음 예시를 보자.


예1) 특정 애플리케이션 서버는 장애가 발생하면 시스템 전체에 문제가 발생할 수 있는 서버로

마스터-스탠바이 형태로 이중화 구성을 했다.

예2) 모니터링 서버는 마스터 서버를 감시하고 있으며, 마스터 장애 발생 시 스탠바이 서버가

마스터 역할을 수행하게 명령을 내린다.


결국 마스터서버, 스탠바이 서버, 모니터링 서버 이렇게 3개가 구성되는데.. 모니터링 서버가 맛탱이가있을때 마스터도 맛탱이 가면 서버담당자는 잡코리아를 뒤져야 할까...??

이렇듯 위 와 구성에서 모니터링 서버가 SPOF가 된다는 것이다.


다른 예로 모든 서버가 공유해야할 중요한 데이터가 있고 이 데이터가 수시로 수정이된다. 그리고 이데이터에 접근할 수 없을 경우 시스템 전체에 문제가 발생한다고 하면 이데이터를 관리하는 서버가 SPOF가 된다. 결국 SPOF를 복제하여 분산 관리 되어야할 듯 하다.


주키퍼는 분산환경에서 절대 장애가 발생하지 않는 클러스터, 항상 접근 할 수있는 데이터를 보관할 수 있는 데이터 저장소의 역할을 수행함으로써 분산 아키텍처에서 SPOF를 쉽게 제거할 수 있는 방법을 제공한다. 위쪽의 그림에서와 같이 모든 데이터들은 주키퍼 서버사이에 동기화돼 관리되고 클러스터는 항상 3대 이상의 서버로 운영된다. 특정 주키퍼 서버에 장애가 발생하면 클라이언트 라이브러리에 의해 자동으로 다른 서버로 접속하며, 새로운 주키퍼 서버를 추가하면 데이터 동기화는 자동으로 맞춰진다. 이런 주키퍼의 특징으로 인해 분산환경의 가장 정점에서 분산 환경 전체를 관리하는 역할을 수행한다.


2011년 4월 27일 수요일

Avro

: 이기종 간 데이터 타입을 교환할 수 있는 체계를 제공하는 프레임워크.
데이터 직렬화(Serialization)를 기본 개념으로해 RPC호출을 이기종간에 가능하게 하는 개념.
JSON기반의 문장열을 이용해 데이터 타입에 대한 정보를 클라이언트와 서버 사이에 주고 받으면서 데이터 타입을 확인하는 과정을 거치는 형태를 사용한다.
이런방식을 이용하면 컴파일 시점이 아닌 실행 시점에서 IDL정보를 이용해 호추하는 방식도 지원할 수 있다.

1. 스키마
- 전송이나 저장시의 데이터 포맷에 대한 정의.
RPC 호출 : 전송할 데이터의 스키마 정보를 먼저 전송한 후 실제 데이터 전송.
파일저장 : 파일 앞부분에 스키마 정보 저장, JSON형태로 표현된다.

예) {"type":"타입명" ...속성...} 형식으로 정의. 사용 예{"type":"string"}
type은 예약어로 스키마 정의를 나타낸다.
type명의 두가지 종류.
1. 원시타입(Primitive type)
- 종류



  • null : 값 없음

  • boolean : true, false

  • int : 32비트 부호있는 정수

  • long : 64비트 부호있는 정수

  • float : 단일 정확도(32비트) IEEE 754부동 소수점

  • double : 이중 정확도(63비트) IEEE754 부동 소수점

  • bytes : 바이트 배열

  • string : 유니코드 문자열
2. 복합타입(Complex type)


  • 레코드 (record)

  • 열거형 (enum)

  • 배열 (array)

  • 맵 (map)

  • 유니온 (union)

  • 고정 길이 (fixed)
- 3가지 속성을 가지고 다음과 같다.
name : 레코드의 이름(클래스명 또는 구조체명)
doc : 스키마에대한 설명으로 필수항목은 아님
fields :여러 개의 필드를 정의. 필드는 name, doc, type, default 속성을 가진다.

2. 복합 타입
3. 객체 컨테이너 파일
4. 프로토콜
: RPC호출을 위한 프로토콜이다. RPC호출을 위해서 프로시저에 대한 인터페이스 정의와 송수신되는 데이터에 대한 타입정의가 필요하다. 프로토콜정의는 스키마와 동일하게 JSON포맷으로 정의한다. 속성은 아래와 같다.


  • protocol : 프로코콜의 이름, 코드로 생성됐을 때 인터페이스명에 해당한다.

  • namespace : 네임스페이스를 한정할 수 있는 문자열로, 코드로 생성됐을때 패키지에 해당한다(선택항목)

  • doc : 프로토콜에 대한 설명(선택항목)

  • types : 프로토콜에서 사용하는 타입을 정의한다. 스키마에 설명한 복합 타입중 레코드, 열거형 고정 길이형이 사용되면, 추가로 에러 타입을 정의할 수 있다. 에러타입과 동일하지만 타입을 나타내는 키워드로 'error'를 사용한다. types는 선택 항목이며 쓰리프트의 'struct'에 대응하는 개념이다.

  • messages : RPC메소드를 정의한다. 하나의 프로토몰 내에서는 여러 개의 메시지를 정의할 수 있다. 메시지는 쓰리프트의 'service'에 대응하는 개념이며 각 메시지는 이름과 'doc'메시지에 대한 설명, 'request' : 입력 파라미터 목록으로 'name', 'type'속성을 가진다., 'response'반환 타입을 정의한다., 'errors'예외타입 목록을 정의한다.