2013년 4월 30일 화요일

ThreadContext 별 저장소를 활용한 Spring Scope 확장

제가 닷넷으로 개발할 때, IoC/DI framework 으로 Castle.Windsor 를 사용했습니다. NInject, Spring.NET 등도 있었지만... Castle.Windsor 가 가장 앞서 나갔고, Fluent 방식으로 Component 를 등록하는 것도 지원했습니다.

자바에서는 Spring Framework 이 거의 표준이라서 별 생각없이(?) 잘 쓰고 있습니다. 다만 한가지 아쉬운 점은 같은 Thread Context 별로 다른 Bean (Component) 를 사용하고 싶은데... 할 수 있는 것이라곤...
scope 가 singleton 이거나 prototype 이란 거... 즉 프로세스에 오직 하나의 인스턴스만 있거나, 요청 시 매번 새로운 인스턴스를 준다는 것이지요. 물론 웹에서는 PerSession 등이 있습니다만, 내부 라이브러리에서는 Thread Context 별로 Bean을 제공하는 방식이 추가되었으면 좋겠다 싶었습니다.

Windsor Castle 에는 thread 라고 지정해주면 Thread Context 별로 독립된 인스턴스를 제공해 주거든요^^

그래서 만들어 봤습니다.

우선 Bean 을 정의하는데, 일반적으로 단순 생성해서 주는 게 아니라. TheadLocal을 이용하여, 생성된 인스턴스를 보관하여, 같은 ThreadContext 에서 Bean을 요청하는 경우, 기존 인스턴스를 제공하는 것입니다.

ThreadLocal 에 초기 값을 지정해주고, 필요 시마다 ThreadLocal 의 인스턴스를 제공해 주는 것입니다.
이렇게 하면, Thread Context 별로 다른 인스턴스를 사용하여, 독립적인 작업을 수행할 수 있고,   같은 Thread Context에서는 반복적인 조회에도 같은 인스턴스를 제공할 수 있습니다.

Thread 별로 제공해야 하는 Bean 이 많은 경우에는 위와 같은 작업이 번거로울 수 있습니다. 이럴 때에는 thread 별로 hash map 을 제공하는 것이 좋습니다.

위와 같이 같은 Thread 에서는 하나의 hash map 을 공유하게 되므로, 여러 Class 들간에 인자 전달 방식이 아니더라도, thread 단위로 값을 공유할 수 있습니다.

이렇게 Local 을 이용하여 기존 ThreadLocal 방식을 다시 구현하면


과 같습니다. Bean 마다 ThreadLocal 을 따로 만들지 않아 좋은 장점이 있습니다.

2013년 4월 29일 월요일

NIO2 AsynchronousFileChannel 사용 예제

작은 크기의 파일을 읽을 때에는 JDK 1.7 의 Files.readAllLines() 를 사용하면 땡입니다. 하지만 무지 큰 파일을 읽기, 쓰기를 할 경우에는 Disk IO 작업만으로 많은 시간이 걸려, 다른 작업을 못하게 됩니다. 이럴땐 무조건 비동기 작업으로 변환해야 합니다.

왜? 뭐하러? 비동기 작업이 만들기도 어렵고, 버그 발생 시 처리하기도 힘든데...
맞는 말이기도 하지만, 메모리와 CPU에서 처리하는 작업도 비동기 작업으로 처리하는데, 네트웍이나 DISK IO라면 수 천배, 수 만배 느린데, 이것을 비동기로 처리해서, 다른 작업을 처리해 줄 수 있다면 시스템 전체의 작업 효율이 엄청 높아질 것입니다.

Redis 가 초기에 Master/Slave replication 방식을 동기식으로만 지원하다가 비동기 방식도 지원하게 된 이유도 같은 이치라 보시면 됩니다.

자 이제 본격적으로 비동기 방식의 파일 읽기/쓰기를 해보기로 합시다. JDK 1.7 NIO 2 에 보면 기존 NIO 보다 확장되고 향상된 기능이 많습니다.
아직도 JDK 1.7을 쓰지 않는 시스템이나 NIO2 를 쓰지 않는 시스템을 보면 아쉬움이 크네요.
다들 얼른 빨리 업그래이드해서 고성능 기능을 활용하면 좋을텐데 하는 맘입니다.

우선 NIO 2 를 이용하는 방식 중 작은 파일을 다룰 때에는 버퍼링되는 스트림을 사용하는 것이  가장 쉽고, 편합니다.

BufferedReader, BufferedWriter 사용 법은 Files 클래스를 사용하면 끝납니다.


Files.newBufferedWriter, Files.readAllLines 등 내부적으로 BufferedReader, BufferedWriter를 사용합니다. 버퍼링을 통해, DiskIO 작업 횟수를 적게, 한꺼번에 처리하도록합니다.

다음으로는 대용량의 데이터를 파일로 처리할 경우 비동기 방식으로 처리해주는 AsynchronousFileChannel 의 사용법에 대해 보겠습니다. 이름에서 보시다시피 비동기 방식으로 파일의 데이터를 처리합니다.

대용량 데이터를 쓰는 예



데이터를 쓰는 경우도 많은 데이터를 쓰는 동안 다른 일을 처리할 수 있으므로, Future.isDone() 을 검사하여 다른 작업을 처리할 수도 있고, 다른 작업을 처리한 후 Future.get() 을 통해 작업 결과를 알 수 있습니다.

대용량 데이터를 읽는 예

데이터를 읽을 때, isDone() 은 UI 처리 등을 수행할 수 있고, 단순히 get() 으로 기다릴 수도 있습니다.
주의사항 : 테스트 코드라  읽고 난 후 파일을 삭제하게 해 놨으니, 실전에서는 필요에 따라 옵션을 지정해 주세요.

마지막으로 byte[]로 읽어드린 내용이 텍스트라면,  List 수형으로 변경해주는 것이 좋겠죠?
라인 처리는 OS 시스템마다 다 다른데, BufferedReader 가 잘 되어 있더군요 ㅋ

자 이렇게 BufferedReader 를 이용하여 작업을 처리했습니다.

2013년 4월 18일 목요일

Apache Avro IDL protocol 을 비동기 방식으로 통신하기

연속해서 Avro 에 대해 글을 쓰게 되네요. 이번 프로젝트에 Avro를 사용할 것이고, 팀원들이 쉽게 쓰게 하기 위해서 이 글을 씁니다. ㅋ

Netty 덕분에 Avro 의 통신이 믿을만하고, 속도도 빠르다고 확신합니다만, 개발자 분들도 비동기 방식을 사용하여 더 효과적인 통신이 되도록 하면 좋을 것입니다.

CalculatorServer.java ( 이전 글의 예제를 비동기 방식도 가능하도록 수정)

입니다. 이전 코드에서 변경된 부분은 다음과 같습니다.

변경된 부분만!!!


비동기 방식 통신을 위해서는 Client 를 얻을 때 Calculator.class 를 쓰지 않고, Calculator.Callback.class 를 사용합니다.  이 interface 는 이미 Avro에 의해 생성된 코드입니다.

이를 바탕으로 Callback을 직접 정의해도 되고,  CallFuture 를 사용해도 됩니다. 

Apache Avro IDL 을 이용한 RPC 구현

어제는 JSON 포맷의 Avro Protocol 파일을 정의하여, RPC 를 구현해 봤습니만 (Avro Protocol 방식),
오늘은 IDL을 이용하여 구현해 보도록 하겠습니다. IDL 이 json 포맷보다 훨씬 가독성이 좋아서, 저는 앞으로는 IDL로 구현해야겠네요.

오늘은 어제보다 좀 더 복잡한 코드를 만들어 보겠습니다. Avro IDL 관련 설명을 읽고 10분만에 구현했으니, 이 글을 보시는 분들도 단박에 이해 되실겁니다.

구현한 예제는 검색 서비스를 흉내낸 것인데,  데이터의 저장, 검색 함수를 지원합니다.

searchService.avdl (Avro IDL 포맷의 확장자는 avdl 입니다)


SearchService 에는 Entity, SearchResult 라는 자료구조 (java에서는 class) 를 정의하고, 아래에는 rpc 메소드들을 정의했습니다.
JSON 포맷보다 훨씬 보기 좋죠?

이전 글에 설명되었듯이 idl-protocol 에 대해 source-generation 을 수행하고, 아래 코드를 작성하면, Search Service 를 제작할 수 있습니다.

SearchServer.java

Client, Server 구현은 이전글의 예제와 거의 유사합니다.
아주 초간단하게 작성했지만 될 건 다 됩니다^^

이제 Avro 의 좀 더 깊은 영역을 살펴봐야겠습니다.

2013년 4월 17일 수요일

apache avro 의 rpc 예제 by java

검색 서비스 제작 중에 외부 서비스를 위한 RESTful 말고, 내부적으로 사용할 고속의 통신 방식을 제공하는 방법을 찾아봤습니다. 일차적으로 유명한 Apache Thrift 를 사용하려다가, Avro 가 맘에 들어, 공부 삼아 예제를 테스트 해 봤습니다.

흠 뭐랄까? 개발하는 입장에서는 Thrift 라 크게 다를 게 없는데... 규격을 정의하는 코드는 차라리 Thrift 가 눈에 더 익어 보이네요^^
다만, Avro 측에서 주장하는 Dynamic 함과 Schema 를 가지고 있다는 것이 향후 확장이라던가, 유연성 측면에서 좋을 것 같더군요...

개인적으로 이거 저거 상세하게 따지지 않고, 맘에 가는 걸 선택합니다... 
향후에 둘 다 지원하면 되니까요^^ 

어쨌든 오늘은 avro 의 serialize/deserialize 예제 말고, 서버와의 통신 (RPC) 예제를 만들어 볼까합니다. (제가 서비스에 사용하기 위해 미리 연습삼아 제작한 것입니다)

예제는 클라이언트가 서버에게 계산을 요청하면, 값을 계산해서 반환하는 계산 서비스를 구현할 것입니다.

우선 준비 사항으로 avro 라이브러리가 필요합니다.

1. maven dependency 에 아래의 avro 관련 라이브러리를 추가합니다.

        avro 1.7.4 와 avro-ipc 1.7.4 를 사용했습니다.   

최신 버전이 1.7.4 더군요. avro-ipc 가 client/server 통신을 하기 위한 라이브러리 입니다.
내부적으로 Netty 를 사용하네요. Netty를 사용한다는 것은 속도면에서 보증받는 거라 보시면 됩니다. ㅋ

2. 다음으로 avro 파일을 java 코드로 생성해주는 avro-maven-plugin 을 정의해 줍니다.




avro-maven-plugin 버전도 1.7.4 더군요^^ 위의 sourceDirectory, outputDirectory 를 보시면, 표준 maven directory로 설정했습니다. 이부분은 각자 변경하시면 됩니다. 
avro 파일들을 찾아 java 클래스로 생성하여 outputDirectory 밑에 생성합니다.

자 이제 본격적으로 Calculator 를 제작해 봅시다.

calculator 를 먼저 정의합니다. avro 파일은 *.avro 가 표준입니다. 
하지만 protocol 인 경우는 *.avpr 을 써야 합니다.

3. calculator.avpr

{
    "namespace": "example.avro.rpc",
    "protocol": "Calculator",

    "types": [],

    "messages": {
        "add": {
                "request":[ { "name": "x", "type": "double"},
                            { "name": "y", "type": "double"} ],
                "response":"double"
            },

          "subtract": {
                "request":[ { "name": "x", "type": "double"},
                            { "name": "y", "type": "double"} ],
                "response":"double"
          }
    }
}

우선 namespace  를 정의하고, protocol 을 정의합니다. 이를 통해 rpc 통신을 한다는 것을 지정했습니다. types 는 사용자 정의 수형을 정의하는 영역입니다. 계산 서비스에서는 double 형만 쓰기 때문에 작업할 것이 없습니다.
다음으로 add, subtract 메소드를 정의했습니다. 
request 는 input parameter 이고, response 는 output parameter 입니다. output parameter 는 이름이 필요 없기 때문에 형식이 생략되었습니다.

이제 maven 에서 generate-sources 를 통해 java 코드를 생성합니다.

4. 생성된 Calculator.java




소스를 보시면 원하는대로 add, subtract 메소드가 정의되었음을 확인할 수 있습니다.
다음으로 실제 서버에서 서비스를 하고, client 에서 호출하여 결과를 볼 수 있는 예제를 제작해 봅시다.

5. CalculatorServer.java





아주 쉽죠^^

저도 처음에 avro가 rpc 를 위해 자체적인 서버가 없는 줄 알았습니다. netty 를 사용한다는 것을 알고는 thrift 보다 avro가 우선순위에 올라오게 되었지요^^

앞으로 내부 서버간의 통신 등에는 avro 를 적극적으로 활용해야겠습니다.

2013년 4월 7일 일요일

hibernate-redis 제작 (hibernate4 2nd cache using Redis) - part 1


hibernate-redis 제작 - part 1
hibernate-redis 제작 - part 2


작년부터 만들어보겠다고 맘만 먹고 있던 hibernate 4 2nd cache using Redis 를 제작해봤습니다.
hibernate 4, spring-data-redis 1.0.3, jedis 2.1 을 이용하여 작업했습니다.

Repository : https://github.com/debop/hibernate-redis

현재까지 테스트 시에는 제대로 작동하고, 성능 또한 만족스럽습니다.

한가지 마음에 걸리는 것은 spring-data-redis 의 RedisTemplate 를 사용하므로서, Spring 라이브러리에 의존하게 된다는 것이네요.

앞으로는 jedis 만 사용하도록 Upgrade 할 예정입니다.

흠... 그리고 검토한 것 중에 캐시 데이터 저장 시 압축을 지원하려고 했는데, Redis 는 기본적으로 제공한다고 하니, 굳이 할 필요가 없겠더라구요...
혹시 나중에 필요하면 옵션으로 넣어볼까 합니다.

NHibernate 2.x 에서는 2nd Cache Provider 제작이 엄청 쉬웠는데 ㅎㅎ...
hibernate 4 는 처음에는 분석하는데 좀 힘들었습니다...
hibernate-ehcache 소스를 보고 분석해보니 별거 아니더군요 ㅋ...