'2012/03/29'에 해당되는 글 4건

  1. 2012.03.29 Consistent hashing의 장점
  2. 2012.03.29 SofreReference, WeakReference, PhantomReference
  3. 2012.03.29 웹서버 성능과 유지보수
  4. 2012.03.29 LinkedIn 아키텍처

 

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 '김용환'
,