- list나 배열은 repeated 로 표현


- map은 존재하지 않아서 따로 선언

message Pair {

optional string key = 1;

optional string value = 2;

}


message Content {

repeated Pair pairs = 1;

}

required Content content  = 7;




Posted by '김용환'
,

 

Bean을 json으로 전달하는 과정에서 properties에 null이 있으면 수신 측에서는 귀찮은 null 체크 작업을 해야 한다. 이를 줄이기 위해서 송신 측에서 예쁘게 null을 빼고 주는 것이 좋다.

 

관련 자료를 찾아보니 아래 내용과 같다. Bean에 해당 값을 넣어주니 더 이상 null인 값은 전달하지 않는다. 발생하지 않는다.

 

http://wiki.fasterxml.com/JacksonAnnotationSerializeNulls

 

ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
// no more null-valued properties

@JsonSerialize(include=JsonSerialize.Inclusion.NON_DEFAULT)
  public class MyBean {
    // ... only serialize properties with values other than what they default to
  }
 
 
Posted by '김용환'
,

 

쿼리 로그 잘 남기기

 

1) Mybatis에 있는 것 활용

 

<bean id="adminDataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">

<property name="driverProperties">
    <map>
        <entry key="logger" value="com.mysql.jdbc.log.Slf4JLogger" />
        <entry key="logSlowQueries" value="true" />
        <entry key="useUsageAdvisor" value="false" />
        <entry key="profileSql" value="false" />
        <entry key="autoGenerateTestcaseScript" value="true" />
    </map>
</property>

 

 

2) log4jdbc

 

  <property name="driverClassName" value="net.sf.log4jdbc.DriverSpy"/>

  <property name="url" value="jdbc:log4jdbc:derby://localhost:1527//db-derby-10.2.2.0-bin/databases/MyDatabase” />

 

참고 자료

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

http://www.mimul.com/pebble/default/2008/10/24/1224847200000.html

 

 

나중에 이슈를 잡기 위해서 log4jdbc를 활용하는 것은 좋은 방법일듯.

Posted by '김용환'
,

 

mybatis xml 설정에서 다음 에러가 발생했다.

The content of elements must consist of well-formed character data or markup.

 

등호 근처 혹은 쿼리문 바깥에 넣어준다.

 

예제 1) – 내가 선호하는 스타일

<select id="getItems" parameterType="map"  resultType="hashmap">
<![CDATA[
select data
from item
where id = #{channelInstanceId} 
    and revision < #{lastRevision}
]]>
</select>

 

예제 2) 

<select id="getItems" parameterType="map"  resultType="hashmap">
select data
from item
where id = #{channelInstanceId} 
    and revision

<![CDATA[

<

]]>

#{lastRevision}


</select>

Posted by '김용환'
,


Mybatis에 여러개의 파라미터를 넘겨줘서 객체나 파라미터를 변환하는 작업을 하지 않고 간단하게 map으로 작업할 수 있다. 


http://code.google.com/p/mybatis/wiki/HowToSelectMultipleParams


즉, 2개의 파라미터를 Mybatis로 넘길려면, 객체를 만들거나 하는 작업없이 

 User selectUser(@Param("username") String usrename, @Param("hashedPassword") String hashedPassword);


paramterType을 map으로 설정할 수 있다. 


<select id=”selectUser” parameterType=”map” resultType=”User”>
  select id, username, hashedPassword
  from some_table
  where username = #{username}
  and hashedPassword = #{hashedPassword}
</sql>

Posted by '김용환'
,

 

 

mybatis로 multiple DB에 접근할 일이 있는데. sqlSessionFactory를 여러 개를 두어서 관리하도록 에러 메시지가 나와서. MapperScannerConfigurer의 sqlSessionTemplateBeanName property를 추가하니 문제가 되었다.

properties 파일을 못읽는 부분, DAO를 못찾는 exception이 나온다.

 

* 사용 라이브러리

 

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.1.0</version>
        </dependency>


        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.1.0</version>
        </dependency>

 

 

 

* 사용 mybatis 연동 beans 설정

 

 

     <bean id="adminDataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url" value="${template.jdbc.url.master}" />
        <property name="username" value="${template.jdbc.username}" />
        <property name="password" value="${template.jdbc.password}" />
        ....
    </bean>

    <bean id="adminSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="adminDataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
    </bean>

    <bean id="adminTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="adminDataSource" />
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean id="adminMapper"  class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="google.talk.admin.repository" />
        <property name="sqlSessionFactory" ref="adminSqlSessionFactory" />
    </bean>


그래서, 문서를 잘 보니. 아래와 같은 좋은 문구가 있었다.

 

http://www.mybatis.org/spring/mappers.html#MapperFactoryBean

 

Notice that there is no need to specify a SqlSessionFactory or SqlSessionTemplate because the MapperScannerConfigurer will create MapperFactoryBeans that can be autowired. But if you are using more than one DataSource autowire may not work for you. In this case you can use the sqlSessionFactoryBeanName or sqlSessionTemplateBeanName properties to set the right bean name to use. Note that bean names are required, not bean references, thus the value attribute is used instead of the usual ref:

 

<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />



아래와 mapper의 property의 내용과 mybatis-spring을 업그레이드하니 문제가 없었다.

 

1) property 수정 (ref –> value)

   <bean id="adminMapper"  class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="google.talk.admin.repository" /> 

        <property name="sqlSessionFactoryBeanName" value="adminSqlSessionFactory" />
    </bean>

 

 

2) 1.1.1 최신버전으로 변경 (1.1.0 –> 1.1.1)

       <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.1.1</version>
        </dependency>


Posted by '김용환'
,



설치 순서


# add-apt-repository ppa:ferramroberto/java

# apt-get update

# apt-get install sun-java6-jdk libboost-dev libevent-dev python-dev automake pkg-config libtool flex bison 


# ./configure  JAVAC=/usr/bin --prefix=/usr/local/thrift

# make

# make install


확인

# /usr/local/thrift/bin# ./thrift 

# /usr/local/thrift/bin/thrift --gen java test.thrift


Posted by '김용환'
,

 

java 7부터 BitSet의 valueOf()가 추가되었다. 이를 이용해서 Redis의 BitSet 으로 받아 테스트해본다.

이 것을 이용해서 성능을 향상시킨 블로그(http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/ )를 참조로 만들었다.

public class BitSetTest {

static Jedis jedis = new Jedis("192.168.159.133");

public static void main(String[] args) throws Exception {
  jedis.setbit("mon", 8, true);
  jedis.setbit("mon", 15, true);
  jedis.setbit("tue", 1, true);
  jedis.setbit("tue", 7, true);
  jedis.setbit("tue", 0, true);

  List<String> weekdays = new ArrayList<String>();
  weekdays.add("mon");
  weekdays.add("tue");

  BitSet all = new BitSet();
  for (String weekday : weekdays) {
     System.out.println(weekday + " : " + BitSet.valueOf(jedis.get(weekday.getBytes())));
     BitSet bitset = BitSet.valueOf(jedis.get(weekday.getBytes()));
     all.or(bitset);
  }
   System.out.println(all.cardinality());
}


결과는 예상하면, 약간 생뚱 맞다고 느껴질 수 있지만, 익숙해질 필요가 있다.

mon : {8, 15}
tue : {0, 6, 7}
5

 

그 이유는 아래 이미지 파일로 설명할 수 있다.

사진

 

redis의 biset 를 저장할 때, offset 0 이 의미하는 것은 바로 첫번째 1byte의 가장 최상위 bit를 의미한다. 따라서 0 offset true를 지정하고 get해서 BitSet으로 얻어오면 7로 얻어온다. 1 offset에 true를 저장하면 6으로 값을 얻어온다. 이런식으로 값들이 저장하도록 되어 있다.

만약 offset이 8인 값에 true를 저장해서 BitSet으로 얻어오면, 15로 얻어온다.

16 offset에 저장해서 BitSet으로 가져온다면, 23으로 가져온다..

Posted by '김용환'
,

 

redis의 bitset은 bit 단위의 값을 저장하고 읽을 때 유용하다. c나 c++ 쪽으로 개발했던 사람이라면 bit opersation은 일종의 flag 연산에 최고의 희열을 느꼈을 만하다.

http://redis.io/commands/setbit

사용법은 아주 간단하다.

setbit <key> <offset> <0 or 1>

 

image

 

jedis를 사용해서 테스트를 진행해본다.  잘 저장되고 잘 받을 수 있다.

private static void test1() throws Exception {
        // put
        List<Boolean> list = new ArrayList<Boolean>();
        Random rand = new Random();
        for (int i = 0 ; i < 100 ; i ++) {
            boolean bool = rand.nextBoolean();
            list.add(bool);
            jedis.setbit("key", i, bool);
        }
       
        // get
        int i = 0;
        Boolean getBoooleanValue;
        for (Boolean boool : list) {
            getBoooleanValue = jedis.getbit("key", i);
            if (boool.compareTo(getBoooleanValue) != 0) {
                throw new Exception("error !!");
            }
            i++;
        }
    }

 

내부를 잠깐 살펴보면..

 

jedis.setbit() 메소드는 key값, offset, boolean 을 넣도록 되어 있다.

public Long setbit(byte[] key, long offset, byte[] value)

public boolean setbit(String key, long offset, boolean value);

 

두 개의 메소드의 원형을 본다.

1. 2. key과 value가 byte[] 타입

BinaryJedis.java
public Long setbit(byte[] key, long offset, byte[] value) {
    client.setbit(key, offset, value);
    return client.getIntegerReply();
}

BinaryClient.java
public void setbit(byte[] key, long offset, byte[] value) {
    sendCommand(SETBIT, key, toByteArray(offset), value);
}

Connection.java
protected Connection sendCommand(final Command cmd, final byte[]... args) {
    connect();
    protocol.sendCommand(outputStream, cmd, args);
    pipelinedCommands++;
    return this;
}

Protocol.java
public void sendCommand(final RedisOutputStream os, final Command command,
        final byte[]... args) {
    sendCommand(os, command.raw, args);
}

private void sendCommand(final RedisOutputStream os, final byte[] command,
        final byte[]... args) {
    try {
        os.write(ASTERISK_BYTE);
        os.writeIntCrLf(args.length + 1);
        os.write(DOLLAR_BYTE);
        os.writeIntCrLf(command.length);
        os.write(command);
        os.writeCrLf();

        for (final byte[] arg : args) {
            os.write(DOLLAR_BYTE);
            os.writeIntCrLf(arg.length);
            os.write(arg);
            os.writeCrLf();
        }
    } catch (IOException e) {
        throw new JedisConnectionException(e);
    }
}

여기까지 write 하기..

read하는 부분이다.

Connection.java
public Long getIntegerReply() {
    flush();
    pipelinedCommands--;
    return (Long) protocol.read(inputStream);
}

Protoocol.java
public Object read(final RedisInputStream is) {
    return process(is);
}


private Object process(final RedisInputStream is) {
    try {
       byte b = is.readByte();
        if (b == MINUS_BYTE) {
            processError(is);
        } else if (b == ASTERISK_BYTE) {
            return processMultiBulkReply(is);
        } else if (b == COLON_BYTE) {
            return processInteger(is);
        } else if (b == DOLLAR_BYTE) {
            return processBulkReply(is);
        } else if (b == PLUS_BYTE) {
            return processStatusCodeReply(is);
        } else {
            throw new JedisConnectionException("Unknown reply: " + (char) b);
        }
    } catch (IOException e) {
        throw new JedisConnectionException(e);
    }
    return null;
}

 

2. key과 value가 각각 String, boolean 타입

public boolean setbit(String key, long offset, boolean value) {
client.setbit(key, offset, value);
return client.getIntegerReply() == 1;
}

Client.java
public void setbit(final String key, final long offset, final boolean value) {
    setbit(SafeEncoder.encode(key), offset, toByteArray(value ? 1 : 0));
}

public class SafeEncoder {
    public static byte[] encode(final String str) {
        try {
            if (str == null) {
                throw new JedisDataException(
                        "value sent to redis cannot be null");
            }
            return str.getBytes(Protocol.CHARSET);
        } catch (UnsupportedEncodingException e) {
            throw new JedisException(e);
        }
    }

public static final byte[] toByteArray(final int value) {
    return SafeEncoder.encode(String.valueOf(value));
}

 

잘 못 사용하는 경우에 “ERR bit is not an integer or out of range” 가 일어날 수 있다.

boolean 값으로 들어갈 0,1 을 제외한 다른 값이 들어오면 “ERR bit is not an integer or out of range”  Data Exception 발생이 된다. 서버에서 ‘–’ 마이너스 값을 던져준다.

jedis.setbit(new byte[] {0}, 1, toByteArray(3));
byte[] a = jedis.get(new byte[]{0});
System.out.println(a[0]);

Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: ERR bit is not an integer or out of range
    at redis.clients.jedis.Protocol.processError(Protocol.java:54)
    at redis.clients.jedis.Protocol.process(Protocol.java:61)
    at redis.clients.jedis.Protocol.read(Protocol.java:122)
    at redis.clients.jedis.Connection.getIntegerReply(Connection.java:178)
    at redis.clients.jedis.BinaryJedis.setbit(BinaryJedis.java:2965)
    at BitSetTest.main(BitSetTest.java:14)

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