여러 나라에서 사용하는 전화번호 처리를 위한 라이브러리가 있다.

http://code.google.com/p/libphonenumber/

Posted by '김용환'
,

 

블룸필터의 의미와 카산드라에서 블룸필터를 어떻게 구현했는지 체크한다.

 

1. 블룸 필터 (Bloom Filter)

블룸 필터는 data 파일의 키값을 모아, 주어진 정보가 있는지 없는지 알려주는 확률 기반의 필터이다. 블룸필터 를 통해서 data가 있는지 빨리 확인할 수 있다. 상식선에서 잘 생각했을 때, 데이터가 없는데 있다는 것는 괜찮지만,  데이터가 있다고 해놓고서는 없는 것은 없다 라는 개념으로 보면 된다. 전자는 false positives, 후자는 false negatives 이다. 아래 내용이 바로 그런 예라고 보면 된다.
(false positives : 참이 아닌데, 실제로는 참이다,  false negatives : 참인데, 실제로는 거짓이다.)

image

(http://en.wikipedia.org/wiki/Bloom_filter 참조)

블룸 필터를 사용하면 일종의 키가 있는지 없는지에 대한 빠른 확인이 가능하다. (전문용어로 to save IO when performing a key lookup) 이렇게 함으로서 disk access를 조금이나다 적게 해서 속도를 높일 수 있다. 캐쉬는 아니지만, 캐쉬 처럼 비슷한 개념이 있다고 말할 수 있다.

블룸 필터는 두가지를 제공한다. 하나는 추가하는 add(), 하나는 존재하는지에 대한 isExist() 이다.  add() 할 때, key를 hash 알고리즘을 이용해서 여러 개의 hash 값을 나누고, 버킷이라는 저장장소에 저장한다. 그래서 isExist할때 그 버킷를 활용해서 있는지를 확인한다. 확률상 없는데, 있다고 나오지 않도록 적절하게 잡는 알고리즘이 최상의 알고리즘이라 할 수 있을 것 이다.

image

(http://en.wikipedia.org/wiki/Bloom_filter 참조)

 

자세한 내용은 아래 내용을 참조하면 쉽게 이해할 수 있다.

Programming Game Gen 의 블룸필터 챕터
http://mindori.egloos.com/1699170
http://blog.naver.com/PostView.nhn?blogId=cra2yboy&logNo=90122287288

 

 

2. 카산드라의 실제 소스 확인

 

카산드라 1.0.5 에서 bloom filter 를 사용하는 곳 (모두 org.apache.cassandra.io.sstable 패키지)은 다음과 같다.

- SSTable 생성시 (Index 용으로 활용) (SSTableWriter의 inner class IndexWriter)

public final BloomFilter bf;
bf = BloomFilter.getFilter(keyCount, 15);

- SSTable을 읽을 때 (SSTableReader)

private Filter bf;
bf = LegacyBloomFilter.getFilter(estimatedKeys, 15);

false positive 확률을 파라미터는 15 정도로 해서 잘 넘기고 있다.

BloomFilter의 getFilter() 메서드의 원형은 다음과 같다.

/**
* @return The smallest BloomFilter that can provide the given false positive
* probability rate for the given number of elements.
*
* Asserts that the given probability can be satisfied using this filter.
*/
public static BloomFilter getFilter(long numElements, double maxFalsePosProbability)
{
    assert maxFalsePosProbability <= 1.0 : "Invalid probability";
    int bucketsPerElement = BloomCalculations.maxBucketsPerElement(numElements);
    BloomCalculations.BloomSpecification spec = BloomCalculations.computeBloomSpec(bucketsPerElement, maxFalsePosProbability);
    return new BloomFilter(spec.K, bucketsFor(numElements, spec.bucketsPerElement));
}

 

# 저장

Filter.db 파일에 블룸필터를 저장한다.  (Componen , SSTable 클래스 참조)

bloom filter를 저장할 때는 serialization하게 저장하게 되어 있다. (org.apache.cassandra.utils.BloomFilterSerializer.java)

bloom filter 의 hashcount 를 integer type으로 저장하고 그 다음에는 워드 개수(word number, bit 길이)를 저장한다. 그 다음에 page size만큼 page를 읽어와 bit 값을 저장한다.

public class BloomFilterSerializer implements ISerializer<BloomFilter>
{
public void serialize(BloomFilter bf, DataOutput dos) throws IOException
{
int bitLength = bf.bitset.getNumWords();
int pageSize = bf.bitset.getPageSize();
int pageCount = bf.bitset.getPageCount();

dos.writeInt(bf.getHashCount());
dos.writeInt(bitLength);

for (int p = 0; p < pageCount; p++)
{
long[] bits = bf.bitset.getPage(p);
for (int i = 0; i < pageSize && bitLength-- > 0; i++)
dos.writeLong(bits[i]);
}
}

}

 

 

# Filter

Bloom Filter는 org.apache.cassandra.utils.Filter 추상 클래스를 상속받았다.

package org.apache.cassandra.utils;

public abstract class Filter
{
int hashCount;

int getHashCount()
{
return hashCount;
}

public abstract void add(ByteBuffer key);

public abstract boolean isPresent(ByteBuffer key);
}

 

# BloomFilter

org.apache.cassandra.utils.BloomFilter.java 이다.

abstract 메서드를 상속받은 두개의 메소드의 구현은 아래와 같다. 
제일 중요한 것은 확률적으로 잘 분산시킬 수 있는 알고리즘인데, 일반적으로는 SHA 기반의 알고리즘을 사용하여 난수를 만들어내는 데, 카산드라는 “Less Hashing, Same Performance: Building a Better Bloom Filter” (http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf) 과 MurMurHash(http://murmurhash.googlepages.com/)를 이용하고 있다. 

SHA보다 빠르고, 적당하게 잘 분산시킬 수(good collision resistance)  있다. hashcount와 주어진  key, OpenBit 길이(디폴트는 20개)로 만들어진 세트(hash bucket)에서 long [] 타입의 정보를 읽는다.

add 일  경우에는 fastGet를

public void add(ByteBuffer key)
{
    for (long bucketIndex : getHashBuckets(key))
    {
    bitset.fastSet(bucketIndex);
    }
}

public boolean isPresent(ByteBuffer key)
{
    for (long bucketIndex : getHashBuckets(key))
    {
        if (!bitset.fastGet(bucketIndex))
        {
            return false;
        }
    }
     return true;
}

private long[] getHashBuckets(ByteBuffer key)
{
    return BloomFilter.getHashBuckets(key, hashCount, bitset.size());
}

    // Murmur is faster than an SHA-based approach and provides as-good collision
    // resistance.  The combinatorial generation approach described in
    // http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
    // does prove to work in actual tests, and is obviously faster
    // than performing further iterations of murmur.
    static long[] getHashBuckets(ByteBuffer b, int hashCount, long max)
    {
        long[] result = new long[hashCount];
        long hash1 = MurmurHash.hash64(b, b.position(), b.remaining(), 0L);
        long hash2 = MurmurHash.hash64(b, b.position(), b.remaining(), hash1);
        for (int i = 0; i < hashCount; ++i)
        {
            result[i] = Math.abs((hash1 + (long)i * hash2) % max);
        }
        return result;
    }


/** Sets the bit at the specified index.
* The index should be less than the OpenBitSet size.
*/
public void fastSet(long index) {
   int wordNum = (int)(index >> 6);
   int bit = (int)index & 0x3f;
   long bitmask = 1L << bit;
   bits[ wordNum / PAGE_SIZE ][ wordNum % PAGE_SIZE ] |= bitmask;
}


/** Returns true or false for the specified bit index.
  * The index should be less than the OpenBitSet size.
  */
public boolean fastGet(long index) {
   int i = (int)(index >> 6);               // div 64
   int bit = (int)index & 0x3f;           // mod 64
   long bitmask = 1L << bit;
   // TODO perfectionist one can implement this using bit operations
   return (bits[i / PAGE_SIZE][i % PAGE_SIZE ] & bitmask) != 0;
}

 

MurmurHash 클래스를 살펴본다. 이 클래스를 살펴보기 전에 성능을 봐야 하는데. intel core 2 duo 2.4 서버를 기준으로 했을 때, 초당 처리율을 최고를 자랑한다. 

Excellent performance - measured on an Intel Core 2 Duo @ 2.4 ghz

OneAtATime - 354.163715 mb/sec
FNV - 443.668038 mb/sec
SuperFastHash - 985.335173 mb/sec
lookup3 - 988.080652 mb/sec
MurmurHash 1.0 - 1363.293480 mb/sec
MurmurHash 2.0 - 2056.885653 mb/sec

 

/**
* This is a very fast, non-cryptographic hash suitable for general hash-based
* lookup. See http://murmurhash.googlepages.com/ for more details.
*
* <p>
* The C version of MurmurHash 2.0 found at that site was ported to Java by
* Andrzej Bialecki (ab at getopt org).
* </p>
*/
public class MurmurHash {


    public static long hash64(ByteBuffer key, int offset, int length, long seed)
    {
        long m64 = 0xc6a4a7935bd1e995L;
        int r64 = 47;

        long h64 = (seed & 0xffffffffL) ^ (m64 * length);

        int lenLongs = length >> 3;

        for (int i = 0; i < lenLongs; ++i)
        {
            int i_8 = i << 3;

            long k64 =  ((long)  key.get(offset+i_8+0) & 0xff)      + (((long) key.get(offset+i_8+1) & 0xff)<<8)  +
                        (((long) key.get(offset+i_8+2) & 0xff)<<16) + (((long) key.get(offset+i_8+3) & 0xff)<<24) +
                        (((long) key.get(offset+i_8+4) & 0xff)<<32) + (((long) key.get(offset+i_8+5) & 0xff)<<40) +
                        (((long) key.get(offset+i_8+6) & 0xff)<<48) + (((long) key.get(offset+i_8+7) & 0xff)<<56);
          
            k64 *= m64;
            k64 ^= k64 >>> r64;
            k64 *= m64;

            h64 ^= k64;
            h64 *= m64;
        }

        int rem = length & 0x7;

        switch (rem)
        {
        case 0:
            break;
        case 7:
            h64 ^= (long) key.get(offset + length - rem + 6) << 48;
        case 6:
            h64 ^= (long) key.get(offset + length - rem + 5) << 40;
        case 5:
            h64 ^= (long) key.get(offset + length - rem + 4) << 32;
        case 4:
            h64 ^= (long) key.get(offset + length - rem + 3) << 24;
        case 3:
            h64 ^= (long) key.get(offset + length - rem + 2) << 16;
        case 2:
            h64 ^= (long) key.get(offset + length - rem + 1) << 8;
        case 1:
            h64 ^= (long) key.get(offset + length - rem);
            h64 *= m64;
        }

        h64 ^= h64 >>> r64;
        h64 *= m64;
        h64 ^= h64 >>> r64;

        return h64;
    }


}

Posted by '김용환'
,

 

java에서 FileOutputStream 과 FileOutputStream을 감싸는 FilterOutputStream를 이용해서 파일을 저장할 때가 있을 때 보통 FilterOutputStream만 flush와 close() 메서드 콜만 하고 끝나는 경우의 코드들이 있다.

그러나 좀 더 확실히 하기 위해서는 FileOutputStream 의 getFD() 메소드를 호출하여 FileDescriptor 를 꺼집어 내서 sync() 메서드 콜을 해야 함으로 안정하게 sync 해야 physical device에 데이터가 저장된다.

flush는 그냥 버퍼를 비우는 것 뿐이고, sync 는 파일시스템 내부의 캐쉬를 모두 physical device로 가는 것이다. 확실히 저장시키는 습관이 필요하다. 

 

1. 일반 IO의 경우는 아래와 같이 사용한다.

FileOutputStream fos = new FileOutputStream(“file.db”);
DataOutputStream stream = new DataOutputStream(fos);
stream.write(1); 
stream.flush();
fos.getFD().sync();
stream.close();

-------------------------------

2. NIO의 경우는 아래와 같이 사용한다.

FileOutputStream s = new FileOutputStream(filename) ;
Channel c = s.getChannel() ;
c.write(buffer);
c.force(true) ;
s.getFD().sync() ;
c.close() ;

Posted by '김용환'
,



java에서 jvm으로 넘어오면 파라미터를 체크하려면 좀 귀찮은 작업을 해야 하는데. Apache common cli를 쓰면 좀 편리하다.  properties 체크도 할 때 하려면 귀찮아서 추후를 위해서 만들어본다.


Test.java

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;


public class Test {
 public static final String DEFAULT_CONFIG = "a.prop";
 
 public static void main(String[] args) throws Exception {
  Map<String, String> propMap = readProperties();
  Map<String, String> argMap = readOption(args);
  if (argMap == null) {
   return;
  }
  
  //...
  System.out.println("property..");
  for (Map.Entry<String,String> entry : propMap.entrySet()) {
   System.out.println("key : " + entry.getKey() + ", value : " + entry.getValue());
  }
  
  System.out.println("argument..");
  for (Map.Entry<String,String> entry : argMap.entrySet()) {
   System.out.println("key : " + entry.getKey() + ", value : " + entry.getValue());
  }
 }

 private static Map<String, String> readOption(String[] args) throws ParseException {
  Options options = new Options();
  options.addOption("m", "mode", true, "mode data");
  options.addOption("n", "num", true, "number data");

  CommandLineParser parser = new PosixParser();
  CommandLine cmd = parser.parse(options, args);

  if (!cmd.hasOption('n') || !cmd.hasOption('m')) {
   HelpFormatter formatter = new HelpFormatter();
   formatter.printHelp("Help ....", options);
   return null;
  }
  
  Map<String, String> map = new HashMap<String, String>();
  String number = cmd.getOptionValue('n');
  String mode = cmd.getOptionValue('m');
  map.put("number", number);
  map.put("mode", mode);
  return map;
 }

 private static Map<String, String> readProperties() throws IOException {
  InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULT_CONFIG);
  if (in == null) {
   throw new IOException("Not found config file " + DEFAULT_CONFIG);
  }

  Properties properties = new Properties();
  properties.load(in);
  
  Map<String, String> map = new HashMap<String, String>();
  for (Map.Entry<Object,Object> entry : properties.entrySet()) {
   map.put((String) entry.getKey(), (String)entry.getValue());
  }
  return map;
 }
}




a.prop


a=1
b=2


그냥 실행시에는 에러가 발생


usage: Help ....
 -m,--mode <arg>   mode data
 -n,--num <arg>    number data


파라미터를 넘기면(-n 5 -m run) 다음과 같은 결과 나옴

property..
key : b, value : 2
key : a, value : 1
argument..
key : number, value : 5
key : mode, value : run




pom.xml의 추가할 depdendency 추가.



  <dependency>
   <groupId>commons-cli</groupId>
   <artifactId>commons-cli</artifactId>
   <version>1.1</version>
  </dependency>
Posted by '김용환'
,

 

jconsole은 업데이트가 잘 안되어서, 거의 버려진 반면 (그래도 jconsole이 내게는 편하다..)  jvisualvm은 계속 업데이트되고 있다. plugin이 짱이고, 이젠 대세로 되어가고 있다. 좋은 툴이다.

 

$java-home/bin 디렉토리에 있는 jconsole.exe 파일을 실행한다.

image

 

메뉴의 Tools->Plugins를 실행한다.

image

 

내가 원하는 플러그인을 체크해서 설치한다.  (주로 볼 것은 MBeans이다.)

image

 

설치하는 화면이 계속 보인다.

image

 

어그리먼트에 어그리해준다.

image

 

다시 jvisualvm을 실행하고 나서, jmx connection을 하나 연다.

image

 

탭이 여러 개 생긴 것을 확인할 수 있다.

image

 

mbean의 com.sun.management의 HotSpotDiagnostic 을 선택한다.

image

 

jconsole과 크게 다르지 않다.

image

 

heap dump를 날려준다~

image

 

 

그리고, jconsole 을 썼던 아쉬운 점도 해결할 수 있다.  플러그인 설치할 때, Options로 한다.

 

Tools->Options를 선택한다.

image

jdk 의 demo/management에 있는 jar 를 추가한다.

image

 

 

점점 jvisualvm은 좋아지고 있다.

http://visualvm.java.net/ 을 참조하면 떢고물이 나온다. 참 예전에는 힘들게 했는데.. 세상이 좋아지고 있다.

image

Posted by '김용환'
,

 

아래 블로그에 작성한 JAXB HttpClient 코드를 Spring OXM으로 수정해서 돌려보았다. 비교해볼만함

http://knight76.tistory.com/entry/JAXB-Http-Client-Example샘플

 

 

특정 URL을 호출하면 사원 정보가 나온다고 가정한다.

 

<ROOT>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

</ROOT>

 

# ROOT.java

package spring;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="ROOT")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
   
    @XmlElement(name="EMPLOYEE")
    private List<Employee> employees;

    public Root() {
        employees = new ArrayList<Employee>();
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

}

 

# Employee.java

package test;

public class Employee {

private String id;
private String nm;

….

… // set, get

}

 

# XEmployee.java

 

public class XEmployee {

    private static List<String> employeeList;
    public static List<String> getEmployeeList() {
        return employeeList;
    }
    static {
        employeeList = new ArrayList<String>();

        employeeList.add("aaa");
         employeeList.add("bbb");
         employeeList.add("ccc");

    }
}

여기까지는 바뀌는 것이 없다.  Spring 연동 코드가 바뀌었다. 연동코드는 형광펜으로 칠해놨다.

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/oxm
           http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

    <oxm:jaxb2-marshaller id="jaxb2Marshaller">
        <oxm:class-to-be-bound name="spring.Root" />
    </oxm:jaxb2-marshaller>
   
    <!-- oxm:jaxb2-marshaller 에서 사용했던 것과 동일한 효과(예전 방식) 
    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>spring.Root</value>
            </list>
        </property>
    </bean>
    -->

    <bean id="application" class="spring.CheckEmp">
        <property name="marshaller" ref="jaxb2Marshaller" />
        <property name="unmarshaller" ref="jaxb2Marshaller" />
    </bean>

</beans>

 

main 메서드

package spring;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import javax.annotation.Resource;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.transform.stream.StreamSource;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class CheckEmp {
    @Resource(name = "jaxb2Marshaller")
    Unmarshaller unmarshaller;

    @Resource(name = "jaxb2Marshaller")
    Marshaller marshaller;
   
    public void setMarshaller(Marshaller marshaller) {
        this.marshaller = marshaller;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }
   
    public static final boolean isSaved = false;
    public static final String URL = "http://aaaaaaaaa";
    public static JAXBContext jaxbContext;
    static {
        try {
            jaxbContext = JAXBContext
                    .newInstance(new Class[] { test.Root.class });
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }

       public static void main(String[] args) throws Exception {
        ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        CheckEmp application = (CheckEmp) appContext.getBean("application");
        application.start();
    }

    private void start() throws Exception {
        BufferedReader reader = connectURL();
        if (isSaved) {
            saveFile(reader);
        }

        Root root = getRoot(reader);

         for (String retiredEmpID : XEmployee.getEmployeeList()) {
            boolean found = false;
            for (Employee emp : root.getEmployees()) {
                if (emp.getID().trim().equalsIgnoreCase(retiredEmpID)) {
                      found = true;
                }
            }
            if (found == false) {
                System.out.println(retiredEmpID);
            }
        }
    }

    private Root getRoot(BufferedReader reader) throws Exception {
        StreamSource source = new StreamSource(reader);
        Root root = (Root) unmarshaller.unmarshal(source);
        return root;
    }

    private BufferedReader connectURL() throws IOException,
            ClientProtocolException, UnsupportedEncodingException {
        HttpGet httpget = new HttpGet(URL);
        HttpClient httpclient = new DefaultHttpClient();
        httpclient.getParams().setParameter("http.socket.timeout", 999000);

        HttpResponse response = httpclient.execute(httpget);
        HttpEntity entity = response.getEntity();
        if (entity == null) {
            throw new RuntimeException("url error");
        }
        InputStream instream = entity.getContent();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                instream, "utf-8"));
        return reader;
    }

    private static void saveFile(BufferedReader reader) throws IOException {
        Writer fstream = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream("aaa.txt"), "utf-8"));
        BufferedWriter out = new BufferedWriter(fstream);
        char[] a = new char[1024];
        while (reader.read(a) > 0) {
            out.write(a);
        }
        out.flush();
        System.out.println("File created successfully.");
    }

}

 

삽질 방지를 위해서 spring debug log가 출력되게 한다.

log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Threshold" value="DEBUG"/>
        <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%F:%L) %m%n"/>
        </layout>
    </appender>
    <appender name="general" class="org.apache.log4j.ConsoleAppender">
       <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%F:%L) %m%n"/>
       </layout>
    </appender>

    <logger name="com" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="general"/>
    </logger>

    <logger name="org.springframework" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="general"/>
    </logger>

    <root>
        <level value="WARN"/>
        <appender-ref ref="STDOUT"/>
    </root>
</log4j:configuration>

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test1123</groupId>
    <artifactId>teswt1</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <spring-version>3.1.0.RELEASE</spring-version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-xjc</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>jsr250-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>

</project>

 

 

# 참조한 내용

Spring OXM 에 대한 자료 (Marshalling XML using O/X Mappers)

http://static.springsource.org/spring-ws/site/reference/html/oxm.html

 

 

# 기타

 

@Resource  annotation은 javax.annotation으로 플랫폼 독립적이며, jsr250에서 DI 의 리더들이 모여서 결정했다. jsr250-api.jar 를 반드시 pom.xml에 넣어야 한다.

<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>

name속성에 자동으로 연결될 빈객체의 이름을 입력한다
@Resource(name="marshaller")
Marshaller marshaller;




# CheckEmp.java 코드를 Spring JUnit으로 만들기

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml"})
public class CheckEmp {

// 코드

 @Test
 public void testMarshaller() throws Exception {

 ...
}
}

Posted by '김용환'
,

 

apache commons의 http client와 jaxb로 어떻게 테스트할지에 대해서 작성한 글이다.

특정 URL을 호출하면 사원 정보가 나온다고 가정한다.

<ROOT>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

</ROOT>

 

객체는 Root-List<Employee>의 구조로 생각,

# Root.java 소스

package test;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "ROOT")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
   
    @XmlElement(name="EMPLOYEE")
    private List<Employee> employees;

    public Root() {
        employees = new ArrayList<Employee>();
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

}

 

# Employee.java

package test;

public class Employee {

private String id;
private String nm;

….

… // set, get

}

 

찾고 싶은 사람의 사번을 작성한다.

# XEmployee.java

public class XEmployee {

    private static List<String> employeeList;
   
    public static List<String> getEmployeeList() {
        return employeeList;
    }
   
    static {
        employeeList = new ArrayList<String>();

        employeeList.add("aaa");
         employeeList.add("bbb");
         employeeList.add("ccc");

    }
}

 

찾고 싶은 사람을 찾아서 출력한다.

 

# main 메서드

package test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

public class CheckEmp {
    public static final boolean isSaved = false;
    public static final String URL = "http://xxxxxxxx";
    public static JAXBContext jaxbContext;
    static {
        try {
            jaxbContext = JAXBContext
                    .newInstance(new Class[] { test.Root.class });
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }

        public static void main(String[] args) throws Exception {

        // connect url
        BufferedReader reader = connectURL();

        // save file
        if (isSaved) {
            saveFile(reader);
        }
       

        // get object
        Root root = getRoot(reader);

        // find
        for (String retiredEmpID : XEmployee.getEmployeeList()) {
            boolean found = false;
            for(Employee emp : root.getEmployees()) {
                if (emp.getID().trim().equalsIgnoreCase(retiredEmpID)) {
                    found = true;
                }
            }
            if (found == false) {
                System.out.println(retiredEmpID);
            }
        }
    }

    private static Root getRoot(BufferedReader reader) throws JAXBException {
        Root root = (Root) jaxbContext.createUnmarshaller().unmarshal(reader);
        return root;
    }

    private static BufferedReader connectURL() throws IOException,
            ClientProtocolException, UnsupportedEncodingException {

        HttpGet httpget = new HttpGet(URL);
        HttpClient httpclient = new DefaultHttpClient();
        httpclient.getParams().setParameter("http.socket.timeout", 999000);
       
        HttpResponse response = httpclient.execute(httpget);
        HttpEntity entity = response.getEntity();
        if (entity == null) {
            throw new RuntimeException("url error");
        }
        InputStream instream = entity.getContent();
        BufferedReader reader = new BufferedReader(new InputStreamReader(instream, "utf-8"));
        return reader;
    }

    private static void saveFile(BufferedReader reader) throws IOException {
        Writer fstream = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("aaa.txt"), "utf-8"));
        BufferedWriter out = new BufferedWriter(fstream);
        char[] a = new char[1024] ;
        while (reader.read(a) > 0) {
            out.write(a);
        }
        out.flush();
        System.out.println("File created successfully.");
    }

}


# pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>test1123</groupId>
 <artifactId>teswt1</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <dependencies>
  <dependency>
   <groupId>org.apache.httpcomponents</groupId>
   <artifactId>httpclient</artifactId>
   <version>4.1.2</version>
  </dependency>
  <dependency>
   <groupId>javax.xml.bind</groupId>
   <artifactId>jaxb-api</artifactId>
   <version>2.2</version>
  </dependency>
  <dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-xjc</artifactId>
   <version>2.2</version>
  </dependency>
  <dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-impl</artifactId>
   <version>2.2</version>
  </dependency>
 </dependencies>
</project>
Posted by '김용환'
,

 

2009년 6월, 주키퍼(zookeeper)에서 cpu 튀는 현상이 발생했다는 버그가 리포트되었다.

ZooKeeper server unexpectedly high CPU utilisation
https://issues.apache.org/jira/browse/ZOOKEEPER-427

(현상)

5개 노드의 zookeeper를 돌리는데 cpu가 95%까지 튀는 현상이 발견되었다.

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6883 infact 22 0 725m 41m 4188 S 95 0.5 5671:54 java

(결론)

다들 jvm을 의심을 했지만. 드라마틱하게 문제는 zookeeper 자체 코드였다.

 

zookeeper의 org/apache/zookeeper/server/quorum/QuorumCnxManager.java 소스의 Thread 클래스를 상속받은 RecvWorker 내부 클래스의 run() 메서드에서 받아야 할 메시지를 처리를 잘 못하면서 cpu가 튀었다.

처음 메시지를 받는 4byte를 읽으려고 하는데, 그냥 읽기만 했지 잘못된 메세지를

 

class RecvWorker extends Thread {

    Long sid;

    SocketChannel channel;

    volatile boolean running = true;

     final SendWorker sw;

 

    RecvWorker(SocketChannel channel, Long sid, SendWorker sw) {

        this.sid = sid;

        this.channel = channel;

        this.sw = sw;

    }

 

    public void run() {

        threadCnt.incrementAndGet();

        try {

        byte[] size = new byte[4];

        ByteBuffer msgLength = ByteBuffer.wrap(size);

        while (running && !shutdown && channel != null) {

            while (msgLength.hasRemaining()) {

-                channel.read(msgLength);  // 기존 코드

+                if (channel.read(msgLength) < 0) { // 수정된 코드

+                      throw new IOException("Channel eof");

+                }

       }

       msgLength.position(0);

      int length = msgLength.getInt();

      if(length <= 0) {

         throw new IOException("Invalid packet length:" + length);

      }

}

 

원인은 다음과 같다.


서버가 동작되고 (running=true,  shutdown=false), 채널은 연결되고 (channel instance not null), 4byte를 읽으려고 하는데, 다른 zookeeper의 인스턴스에서 stop하고 –1 을 리턴할 때 이에 대한 처리가 없었다. (channel.read(msgLength) = 1) 그래서 계속 while 문이 반복되면서 cpu ratio가 95%까지 올라갔다.

 

패치는 channel의 첫 바이트를 읽어 문제가 될 때는 IOException을 던져 개발자가 처리할 수 있게 했다.

 

통신프로그램에서는 (nio이든, io이든) read() 메서드에 대해서는 항상 체크하는 습관이 필요하다.

Posted by '김용환'
,

 

web.xml 에서 Dispatcher servlet 에 대한 url-pattern 에 오타를 냈더니. jsp로 redirect가 안된다. 하하~^^;;

org.springframework.web.servlet.DispatcherServlet noHandlerFound

 

오타내지 말아야지.

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

<servlet-mapping>
  <servlet-name>mvc-dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
  </servlet-mapping>

Posted by '김용환'
,

 

잠깐 동안이지만, Batch 업무를 오랜만에 다시 한다.  ibatis와 내부 클래스, Spring Batch를 이용해서 Spring Batch가 돌아갈 수 있게 하는 xml 설정에 대한 간단한 설명이다.

 


* Spring Batch 의 xml 설정하기 (기본 틀)

1. iBatis의 sqlMapConfig 설정 파일을 읽어 SqlMapClient 객체를 불러온다.

     <bean id="coreSqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
         <property name="configLocation" value="/sql-map-config.xml" />
     </bean>

 


2. FactoryBean을 이용해서 SqlMapClient 을 가지고 DataSource를 얻어온다.

    <bean id="dataSourceSupport" class="com.google.batch.core.dbpm.BatchCoreDataSourceSupport" >
        <property name="sqlMapClient" ref="coreSqlMapClient"></property>
    </bean>


    <bean id="coreDataSource" factory-bean="dataSourceSupport" factory-method="getDataSource" />

 


3. data source를 이용해서 transactionManager를 만든다.

    <bean id="coreTransactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        lazy-init="true">
        <property name="dataSource" ref="coreDataSource" />
    </bean>

 

4. SimpleStep을 정의한다.

    <bean id="simpleStep" class="org.springframework.batch.core.step.item.SimpleStepFactoryBean" abstract="true">
        <property name="transactionManager" ref="coreTransactionManager"></property>
        <property name="commitInterval" value="1"/>
    </bean>

 

5. SimpleJob을 정의한다.


    <bean id="simpleJob" class="org.springframework.batch.core.job.SimpleJob"
        abstract="true">
    </bean>

 

6. 실제 동작할 코드를 제공한다. SimpleJob을 상속한 Job과 SimpleStep을 상속한 step1을 정의한다.
    <job id="iambatch" parent="simpleJob" xmlns="http://www.springframework.org/schema/batch">
        <step id="step1" parent="simpleStep">
            <tasklet ref="myTasklet"/>
        </step>
    </job>

 

7. JobLauncher를 정의한다.

    <bean id="jobLauncher"
        class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
       
    </bean>

 

 

 

* job repository 추가하기


1. job repository 정의
     <batch:job-repository id="jobRepository" data-source="coreDataSource"
           transaction-manager="coreTransactionManager" isolation-level-for-create="SERIALIZABLE"   table-prefix="BATCH_iambatch_" />

2. job launcher, job, step에 추가한다.
   
    <bean id="simpleJob" class="org.springframework.batch.core.job.SimpleJob"
        abstract="true">
        <property name="jobRepository" ref="jobRepository" />
    </bean>


    <bean id="simpleStep" class="org.springframework.batch.core.step.item.SimpleStepFactoryBean" abstract="true">
        <property name="jobRepository" ref="jobRepository" />
        <property name="transactionManager" ref="coreTransactionManager"></property>
        <property name="commitInterval" value="1"/>
    </bean>


    <bean id="jobLauncher"
        class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
        <property name="jobRepository" ref="jobRepository" />
    </bean>

Posted by '김용환'
,