The Simple Magic of Consistent Hashing (http://java.dzone.com/articles/simple-magic-consistent) 자료를 참조했다. 좋은 말로 정리가 잘 된 것 같아서..

Consistent Hashing

1. 부하 집중 부분을 피하기 쉽다.

2, 파티셔닝(partitioning)을 가능하게 한다. 슬라이스 피차처럼~

3. 스케일(scale) up/down이 가능하다. 예측이 가능하다.

4. 복제가 가능하다. 또한 부하 집중 부분의 부하를 경감할 수 있다.

Posted by '김용환'
,

 

자바 서버개발자보다는 자바 임베디드 개발자 내부 캐쉬 용도 용도로 쓰기 위해서 java.lang.ref.Reference클래스를 사용하기도 한다. 잘만 쓰면 메모릭 릭을 잘 방지하면서 사용할 수 있다.

 

* SoftReference(SR)

풍부하게 메모리를 넉넉히 쓰지만, 최초의 GC가 돌아간 후, JVM은 메모리 공간이 없으면 SoftRefernece 인스턴스를 gc 대상으로 잡는다. OOME 가 발생할 수 있는 상황에서 softly-reachable objects가 있다면, 이 objects를 모두 메모리에서 정리한다. 언제 gc될지는 모른다.

안드로이드 API(http://developer.android.com)에 따르면, as late as possible이다.

 

실제 구현 예)  Tomcat의 jdbc 쪽 SoftReferenceObjectPool.java을 참조하면 좋음

간단하게 new SoftReference(객체인스턴스)로 해서 만든다.

http://www.docjar.com/html/api/org/apache/commons/pool/impl/SoftReferenceObjectPool.java.html

package org.apache.commons.pool.impl;

import java.lang.ref.SoftReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import org.apache.commons.pool.BaseObjectPool;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.PoolUtils;


public class SoftReferenceObjectPool extends BaseObjectPool implements ObjectPool {
….



public synchronized void addObject() throws Exception {
        assertOpen();
        if (_factory == null) {
            throw new IllegalStateException("Cannot add objects without a factory.");
        }
        Object obj = _factory.makeObject();

        boolean success = true;
        if(!_factory.validateObject(obj)) {
            success = false;
        } else {
            _factory.passivateObject(obj);
        }

        boolean shouldDestroy = !success;
        if(success) {
           _pool.add(new SoftReference(obj, refQueue));
            notifyAll(); // _numActive has changed
        }

        if(shouldDestroy) {
            try {
                _factory.destroyObject(obj);
            } catch(Exception e) {
                // ignored
            }
        }
    }



* WeakReference(WR) , WeakHashMap

객체가 사용 중이기는 하지만, 더 이상 사용하지 않을 것 같으면 컨테이너에서 삭제한다.

쉽게 말해서 일종의 잠깐 동안의 캐쉬로서 gc가 실행되면 메모리에서 정리한다.

아래 코드를 실행하면, 설명은 그다지 필요하지 않다.


import java.lang.ref.WeakReference;

public class ReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        Student s1 = new Student(1);
        System.out.println("1: " + s1);
        WeakReference<Student> ws = new WeakReference<Student>(s1);
        System.out.println("2: " + ws.get());
        s1 = null;
        System.gc();
        Thread.sleep(1000);
        System.out.println("3: " + ws.get());
    }
}

class Student {
    int id;
    public Student(int id) {
        this.id = id;
    }
    public String toString() {
        return "[id=" + id + "]";
    }
}

결과는 다음과 같다. gc이후에 WeakReference 객체는 모두 메모리에서 정리된다. 

1: [id=1]
2: [id=1]
3: null

 

톰캣 컨테이너에서 사용하고 있는 ConcurrentCache.java를 예로 든다.

package org.apache.el.util;

import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

public final class ConcurrentCache<K,V> {

    private final int size;

    private final Map<K,V> eden;

    private final Map<K,V> longterm;

    public ConcurrentCache(int size) {
        this.size = size;
        this.eden = new ConcurrentHashMap<K,V>(size);
        this.longterm = new WeakHashMap<K,V>(size);
    }

    public V get(K k) {
        V v = this.eden.get(k);
        if (v == null) {
            synchronized (longterm) {
                v = this.longterm.get(k);
            }
            if (v != null) {
                this.eden.put(k, v);
            }
        }
        return v;
    }

    public void put(K k, V v) {
        if (this.eden.size() >= size) {
            synchronized (longterm) {
                this.longterm.putAll(this.eden);
            }
            this.eden.clear();
        }
        this.eden.put(k, v);
    }
}

java의 ThreadLocal.ThreadLocalMap에서도 WeakReference를 사용하고 있다.

static class ThreadLocalMap {

 
       static class Entry extends WeakReference<ThreadLocal> {
           /** The value associated with this ThreadLocal. */
           Object value;

           Entry(ThreadLocal k, Object v) {
               super(k);
               value = v;
           }
       }

       private static final int INITIAL_CAPACITY = 16;

 
       private Entry[] table;

    
       private int size = 0;

    
       private int threshold; // Default to 0


       private void setThreshold(int len) {
           threshold = len * 2 / 3;
       }

     
       private static int nextIndex(int i, int len) {
           return ((i + 1 < len) ? i + 1 : 0);
       }

 
       private static int prevIndex(int i, int len) {
           return ((i - 1 >= 0) ? i - 1 : len - 1);
       }

     
       ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
           table = new Entry[INITIAL_CAPACITY];
           int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
           table[i] = new Entry(firstKey, firstValue);
           size = 1;
           setThreshold(INITIAL_CAPACITY);
       }

   
       private ThreadLocalMap(ThreadLocalMap parentMap) {
           Entry[] parentTable = parentMap.table;
           int len = parentTable.length;
           setThreshold(len);
           table = new Entry[len];

           for (int j = 0; j < len; j++) {
               Entry e = parentTable[j];
               if (e != null) {
                   ThreadLocal key = e.get();
                   if (key != null) {
                       Object value = key.childValue(e.value);
                       Entry c = new Entry(key, value);
                       int h = key.threadLocalHashCode & (len - 1);
                       while (table[h] != null)
                           h = nextIndex(h, len);
                       table[h] = c;
                       size++;
                   }
               }
           }
       }

   …..

 

안드로이드 API(http://developer.android.com)에서는 구현체에 대해서 더욱 세밀하게 설명이 들어가 있다.

Implements a weak reference, which is the middle of the three types of references. Once the garbage collector decides that an object obj is is weakly-reachable, the following happens:

  • A set ref of references is determined. ref contains the following elements:
    • All weak references pointing to obj.
    • All weak references pointing to objects from which obj is either strongly or softly reachable.
  • All references in ref are atomically cleared.
  • All objects formerly being referenced by ref become eligible for finalization.
  • At some future point, all references in ref will be enqueued with their corresponding reference queues, if any.

 

 

* PhantomReference(PR)

soft, weak보다 엄청 약하다. gc가 돌기 전(즉, 이것은 gc 대상이야 라고 결정할 때, finalize() 호출 후 ) 메모리에서 정리된다. 아무래도 gc 되기 전에 cleanup 해야 할 때 사용한다. 내부적으로는 유지하고 있지만, 객체를 다시 꺼내오면 null이 된다.

아주 특수한 경우에 쓰인다. sun.misc.Cleaner  클래스가 PhantomReference를 사용하고 있다.

http://knight76.tistory.com/1548 

재미있는 블로그(http://weblogs.java.net/blog/kcpeppe/archive/2011/09/29/mysterious-phantom-reference)가 있는데, PhantomReference 썼다가 이상하게 (미스테리하게) 나와서, 조금 수정해서 해결했다는 내용이다.

package snippet;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;

public class ReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        test1();
        test2();
    }
   
    public static void test1() {
        ReferenceQueue<Foo> queue = new ReferenceQueue<Foo>();
        ArrayList<PhantomReference<Foo>> list = new ArrayList<PhantomReference<Foo>>();

        for (int i = 0; i < 10; i++) {
            Foo o = new Foo(Integer.toOctalString(i));
            list.add(new PhantomReference<Foo>(o, queue));
        }

        // make sure the garbage collector does it’s magic
        System.gc();

        // lets see what we’ve got
        Reference<? extends Foo> referenceFromQueue;

        for (PhantomReference<Foo> reference : list) {
            System.out.println("x: " + reference.isEnqueued());
        }
        while ((referenceFromQueue = queue.poll()) != null) {
            System.out.println("y: " + referenceFromQueue.get());
            referenceFromQueue.clear();
        }
    }

    public static void test2() {
        // initialize
        ReferenceQueue<Foo> queue = new ReferenceQueue<Foo>();
        ArrayList< FinalizeStuff<Foo>> list = new ArrayList<FinalizeStuff<Foo>>();
        ArrayList<Foo> foobar = new ArrayList<Foo>();
        
        for ( int i = 0; i < 10; i++) {
            Foo o = new Foo( Integer.toOctalString( i));
            foobar.add(o);
            list.add(new FinalizeStuff<Foo>(o, queue));
        }
        
        // release all references to Foo and make sure the garbage collector does it’s magic
        foobar = null;
        System.gc();
        
        // should be enqueued
        Reference<? extends Foo> referenceFromQueue;
        for ( PhantomReference<Foo> reference : list) {
            System.out.println(reference.isEnqueued());
        }
        
        // now we can call bar to do what ever it is we need done
        while ( (referenceFromQueue = queue.poll()) != null) {
            ((FinalizeStuff)referenceFromQueue).bar();
            referenceFromQueue.clear();
        }
    }
}


class Foo {
    private String bar;

    public Foo(String bar) {
        this.bar = bar;
    }

    public String get() {
        return bar;
    }
}


class FinalizeStuff<Foo> extends PhantomReference<Foo> {
    public FinalizeStuff(Foo foo, ReferenceQueue<? super Foo> queue) {
        super(foo, queue);
    }
 
    public void bar() {
        System.out.println("foobar is finalizing resources");
    }
}

테스트 결과는 다음과 같다.

x: true
x: true
x: true
x: true
x: true
x: true
x: true
x: true
x: true
x: true
y: null
y: null
y: null
y: null
y: null
y: null
y: null
y: null
y: null
y: null
true
true
true
true
true
true
true
true
true
true
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources

Posted by '김용환'
,

 

3.5년전 DDOS 공격을 잘 방어할 수 있는 방법을 찾고자 redirection 기능만 넣고 테스트한 적이 있다.

Nginx라는 녀석이 좋은 대안이 될 수도 있다는 생각이 들었다.

TC1

TC2

TC3

TC4

TC4(nolog)

Nginx

Base

Apache 2.2

Apache 2.2

Apache 2.2

Tux

Tux

Nginx

1분 부하값
(load avg)

220

22

220

0.3

0.3

0.5

테스트

Java script redirect

Apache redirect(302)

Java script

redirect

Java script redirect

Java script redirect

Redirect

(302)

MAX TPS

4358.56

5120.5

4523.25

10838.5

10903.1

10790.2

 

테스트 결과는 kernel 기반의 Tux 웹 서버와 Nginx의 성능은 비슷했다.

Tux의 장점은 Static content serving(DMA directly from page cache) 와 user/kernel간의 context switching도 적고  system call이 적어 cpu를 더 효율적으로 쓸 수 있다. 그러나 단점으로는 kernel기반이다 보니 쉽게 운영하기 어려운 단점이 있다.

Ingo Molnár(http://en.wikipedia.org/wiki/Ingo_Moln%C3%A1r ) 라는 사람이 버려졌던 Tux를 리눅스 커널 2.6.0에 올렸다. 

 

Tux는 왜 점점 버려지고 있었을까?

1. 운영 측면에서 불편하다.

운영이 편리한(디버깅, 개발, 플러그인 추가) 어플을 사용하는 것이 편하다.

커널단을 건드려서 복잡한 단계를 진행하는 것보다 어플단에서 진행하는 게 백 번 낫다. 안정성 측면에서도 커널영역이 아닌 유저레벨에서 처리가 가능하다.  데몬 실행 할때만 root 권한이 필요하니. apache httpd나 nginx가 편리하다.

 

2. 메모리 사용 (sendfile)

어쩌면, sendfile이 대중화되면서 Tux의 장점이 사라졌던 것 같다. Apache http 서버는 sendfile directive가 추가되고 sendfile을 통해서 user space buffer copy 부분이 사라졌다. (zerocopy) Apache http 서버는 sendfile 기능을 넣어서 약 10%정도의 성능 효과를 발휘할 수 있다. (물론 nginx도.. ) 웬만한 웹서버는 sendfile 기능은 off이다.

f = open(file)

sendfile (socket, f, 0, fiesize)

굳이 커널레벨을 쓸 필요가 없다.

 

3. 성능

커널 단이라서 빠를 수 있지만, 오히려 처리 아키텍쳐가 더 큰 영향을 미친다.

 

4. 플러그인 개발

커널 모듈을 올렸나 내리는 작업이 결코 개발자에게는 좋지 않다.  바로 dynamic linking 하는 아파치 모듈이 더 편리하다. (현재까지는 nginx는 아직 dynamic linking을 지원하지 않는다. )

 

마치며..

과거에 Tux를 이용해서 먼가 해보려는 나의 시도는 굉장히 위험했던 것 같다. 언제나 생각해봐도 어플단에서 할 수 있는 것들을 최대한 살려서 쓰는 게 좋을 것 같다.

참고로. Tux 와 비슷하지만 성공했던 모델은 MS의 IIS (kernel + user) 이다. IIS 업데이트할 때마다 리스타트하는 상황을 보면. 참 거시기 하다. 여전히 어떻게 생존해 나갈지도 매우 궁금하기도 하구..

Posted by '김용환'
,
Posted by '김용환'
,

 

 

가상화 서버를 사용하게 되면서 테스트했을 때, 가장 큰 이슈가 cpu job이 아닌 io job들이었다. 물리 서버에서는 전혀 고민하지 않았던 이슈들을 고민해야 했다.

네트웍 IO(파일 업로드/다운로드, DB 연결 부하), 파일 IO 가 많을 때 에 엄청난 문제가 있다. 아마도 가상화 솔루션 내부에서 파일 IO 부분에 대한 바틀렉이 있는 것은 아닌지 의심하고 있다. 중형 서버를 이용해서 많은 가상화 솔루션을 두는 부분에 대해서 여전히 이슈가 있는 상태이다.

최근에 100mb 이상의 파일 copy시 에 엄청난 부하를 발생하여 어플리케이션에 영향을  줄 수 있다.. (log rotate 하는 것들, backup하는 것들)

베스트 Practice를 위한 노력이 계속 있어야 할 것 같다.

Posted by '김용환'
,
Posted by '김용환'
,

 

하나의 Batch 서버에 모든 Job을 수행할 수 없는 경우  여러 대의 Batch 서버를 Slave 로 추가하고, 다수의 Slave가 Job을 분산 수행하도록 설정해야 한다.

Hudson은 이와 같은 기능을 쉽게 사용할 수 있도록 지원한다. Hudson Master는 SSH, JNLP, Window service 등의 프로토콜을 사용하여 각 서버에 Slave Agent 를 설치하는 방법을 제공한다. 나는 리눅스 SSH를 선택하여 진행하였다.

 

<패스워드 없이 SSH 연결>

SSH 연결시 매번 패스워드 연결이 지저분할 수 있다. 따라서 공인키를 상대방 서버에 복사하게 하여 서로 접속이 편하도록 했다. 이런 스타일은 패스워드 없이 SSH 연결을 쉽게 할 수 있다.


1.master101(master) 로 root 로 접속

# rlogin -l root master101

 

2./etc/ssh/sshd_config 파일 수정
아래 코드가 #(주석화) 되어 있는데 주석을 풀고 수정

# sudo vi /etc/ssh/sshd_config


RSAAuthentication yes

PubkeyAuthentication yes

AuthorizedKeysFile .ssh/authorized_keys

sshd 데몬을 재시작하는지 잘 확인

# sudo /sbin/service sshd restart

netd 재시작 : [ OK ]

xinetd 재시작 : [ OK ]

 

3. ssh 연결하는 slave 를 저장하고 로그 아웃

# sudo vi /etc/hosts

1.1.1.3 slave103

1.1.1.4 yagbat104

# logout

 


4.www계정으로 재접속

# rlogin -l www master101

 

5.인증키 생성

# ssh-keygen -t rsa

Generating public/private rsa key pair.

Enter file in which to save the key (/home/www/.ssh/id_rsa): 엔터

Created directory '/home/www/.ssh'.

Enter passphrase (empty for no passphrase): 엔터

Enter same passphrase again: 엔터

Your identification has been saved in /home/www/.ssh/id_rsa.

Your public key has been saved in /home/www/.ssh/id_rsa.pub.

The key fingerprint is:

dd:75:8c:e8:f7:c3:07:4c:00:12:33:d0:08:c5:63:08 www@master101

 

6.생성된 인증키를 확인하고 해당 공개키를 사용할 수 있도록 authorized_keys 파일에 생성한 키값을 추가한다.

# cd ~/.ssh/

# cp -RfpP id_rsa.pub authorized_keys

그리고, 이 공개키(id_rsa.pub)를 상대서버에 복사만 하면 되는 구조이다.

# cat id_rsa.pub

ssh-rsa … www@master101

authorized_keys의 파일 권한 변경

# chmod 600 authorized_keys

 

7.root계정으로 slave103(slave #1)로 접속

rlogin -l root slave103

 

8./etc/ssh/sshd_config파일 수정 (2번 과정을 실행)하고..

# sudo vi /etc/ssh/sshd_config

RSAAuthentication yes

PubkeyAuthentication yes

AuthorizedKeysFile .ssh/authorized_keys

 

9. /etc/ssh/sshd_config 파일 timeout 설정
slave 에서 ssh timeout을 설정하여 slave의 dissconnect 이 가능하도록 설정

ClientAliveInterval 10

ClientAliveCountMax 12

 

10.sshd 데몬을 재시작

# sudo /sbin/service sshd restart

 

9. master101(master) 연결할 수 있도록 서버 설정 하고, log out

# sudo vi /etc/hosts

1.1.1.1 master101

# logout

 

11.www계정으로 slave103(slave) 접속

# rlogin -l www slave103

 

12.인증키 생성

# ssh-keygen -t rsa

Generating public/private rsa key pair.

Enter file in which to save the key (/home/www/.ssh/id_rsa): 엔터

Created directory '/home/www/.ssh'.

Enter passphrase (empty for no passphrase): 엔터

Enter same passphrase again: 엔터

Your identification has been saved in /home/www/.ssh/id_rsa.

Your public key has been saved in /home/www/.ssh/id_rsa.pub.

The key fingerprint is:

dd:75:8c:e8:f7:c3:07:4c:00:12:33:d0:08:c5:63:08 www@slave103

 

13.생성된 인증키를 확인하고 해당 공개키를 사용할 수 있도록 authorized_keys 파일에 생성한 키값을 추가한다.

# cd ~/.ssh/

# cp -RfpP id_rsa.pub authorized_keys

그리고, 역시 이 공개키(id_rsa.pub)를 master(master101)에 복사만 하면 되는 구조이다.

# cat id_rsa.pub

ssh-rsa … www@slave103

 

14. master101(master)에 생성한 공개키(id_rsa.pub)를 slave103(slave #1)의 www 계정의 ~/.ssh/authorized_keys에 추가

# cat authorized_keys

ssh-rsa  …. www@slave1031

ssh-rsa  … www@master101

authorized_keys의 파일 권한 변경

# chmod 600 authorized_keys

 

15. slave103(slave #1)에 생성한 공개키(id_rsa.pub)를 master101(master)의 www 계정의 ~/.ssh/authorized_keys에 추가

# cat authorized_keys

ssh-rsa … www@master101

ssh-rsa … www@slave103

 

16. 접속 테스트
master101(master)에서 slave103(slave #1)로 ssh 접속 확인

# ssh slave103

slave103(slave #1)에서 master101(master)로 ssh 접속 확인

# ssh master101

 

17. 위의 방법으로 yagbat104(slave #2)도 ssh 연결 셋팅

18. 테스트

완료

 

<hudson plugin 설치>

hudson의 안정적인 버전인 1.361 버전을 기준으로 했을 때. 잘 붙고 괜찮은 ssh slave plugin의 버전은 0.10이다.

1. http://hudson-ci.org/download/plugins/ssh-slaves/0.10/ssh-slaves.hpi 을 다운받아서 허드슨 플러그인 설치

2. 신규 노드 추가(ssh)

image

 

3. 테스트

Job 설정의 " Tie this project to a node" 에서 slave1을 지정하고, execute shell을 "hostname" 으로 셋팅하고 실행한다.
결과화면에서 slave1 (slave103)에서 hostname이 나오는 것을 확인할 수 있다.

Building remotely on slave1

[test-slave1] $ /bin/sh -xe /tmp/hudson8269255461472552301.sh

+ hostname

slave103

Finished: SUCCESS

 

4. 이런 식으로 확장하게 함

image

 

5. job 실행시 기본적으로 RR처럼 작동한다.

운영해보니 특정 노드(master, slave #, slave #2)에서 돌게 하는 것이 batch job 실행에 대한 안정성을 좀 더 확보하는 것 같다. job 설정에서 꼭 tie this porject to a node를 설정함

image

 

기타.

Spring Job Repository 생성해주는 job 하나 만들고 나면. 기본적인 셋팅은 끝..( 플러그인을 이용해서 notifiy, monitor 해주는 것이 필요하기는 함)

나머지는 Spring Job 만 잘 Scheduling해줄 필요가 있음.

Posted by '김용환'
,

 

애플이 아이폰의 식별자인 UUID에 접속하는 App 승인을 거부하고 있다는 뉴스

App 세계의 태풍이 임박할 듯..

http://news.inews24.com/php/news_view.php?g_serial=645402&g_menu=020600

'scribbling' 카테고리의 다른 글

Consistent hashing의 장점  (0) 2012.03.29
가상화 솔루션의 한계-I/O  (0) 2012.03.28
PP카드 활용-김포 국제공항  (0) 2012.03.22
로봇을 이용한 휠체어  (0) 2012.03.21
가상화 장비와 성능 간의 이슈들  (0) 2012.03.19
Posted by '김용환'
,

 

김포 국제공항에 PP카드로 이용할 수 있는 라운지가 생겼다. 6시 40분부터 8시까지.. 딱이다.

일본 동경 나리타 공항에는 아직 PP카드로 이용할 수 있는 라운지는 없지만.. 이게 어딘가..

김포-나리타 공항을 이용하는 일본 출장인에게 좋은 소식..

 

image

image

 

국제선 출국장 바로 위 “ 카페드 스페셀티스” 층에 있다고 한다.

네이버 검색하니 짠하고 나온다.

http://funnytravel.co.kr/377

Posted by '김용환'
,

로봇을 이용해서 휠체어를 새롭게 만든 것에 찬사를… 엄청나게 편리해 보인다.

이 동영상을 보면서.. 아주 어렸을 때 고모부가 루게릭병에 걸렸을 때, 돕고 싶었던 기억이 났다.

사람들을 돕는 꿈을 잃지 말아야겠다.. 언제가 내가 하고 있는 일이 좋은 일에 쓰일 수 있도록 열심히 해놓도록 해야겠다.

Posted by '김용환'
,