2021년 7월 15일 목요일

ObjectMapper로 List Map 타입을 List Dto 타입으로 변환 방법

 

ObjectMapper mapper = new ObjectMapper();
List<Map<String, Object>> listMap = new ArrayList<>();
...
List<MyDto> testDtoList =
mapper.convertValue(listMap, TypeFactory.defaultInstance().constructCollectionType(List.class, MyDto.class));


2021년 5월 4일 화요일

maven resource 파일 지정.

 legacy 프로젝트를 maven으로 바꾸면서 properties 파일이나 mybatis xml 파일을 찾지 못하는 현상이 있었다.


Caused by: java.io.FileNotFoundException: class path resource [applicationResources.properties] cannot be opened because it does not exist


기존 프로젝트는 resourse 디렉토리가 따로 있는 게 아니라, 여기 저기 중구난방으로 되어 있다.

maven에서 아래의 설정을 추가하면 된다.

        <resources>
            <resource>
                <directory>src</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>


2021년 3월 29일 월요일

Java로 무한 loop 쉘스크립트 실행

 java에서 쉘스크립트를 실행하는 기능을 추가 했다.

문제는 자바 프로그램이 종료되면 쉘스크립트 역시 1~2초 후에 멈춰 버린다.

쉘스크립트는 while true로 무한루프로 실행이 되는 스크립트였다.


자바에서 실행 명렁어를 nohup, &, sh -c 등 이것 저것 다 해 보았지만 쉘스크립트는 멈췄다.

결국 찾은 방법은 두가지이다. 두가지 방법 다 정석적인 해결책은 아니고 trick으로 볼수 있는 방법이다.


하나는 중간 launch 스크립트를 두는 방식이다.

실제 내가 실행해야 할 스크립트가 target.sh 이면 launcher.sh를 둬서 자바에서는 launcher.sh를 실행하는 방법이다.

launcher.sh에는 아래와 같은 명령을 넣는다.


#/bin/sh

nohup ./target.sh 1> /dev/null 2>&1 &



다른 하나는 trap을 사용한 방법이다. 

trap 명령어는 특정 시그널이 들어올 때 어떤 일을 할 지 적용할 수 있다.


실제 적용한 방법은 두번째 방법으로 trap명령어를 통한 방법이다.

적용 방법은 아래와 같다.


trap "method_name" 0


0은 EXIT 인 경우 이다. 자바 프로세스 종료시 EXIT 시그널이 오는 데, 그 때 loop를 사용하는 method_name를 다시 한번 사용하게 하였다.

종료는 9(SIGKILL) 시그널을 발생시켜서 종료하므로 문제 없이 stop을 할 수 있다.

2021년 3월 20일 토요일

netstat말고 ss명령을 사용합시다.


tcp 파일을 이용해서 현재 접속 현황을 가지고 오는 자바로 만들어진 프로그램이 있다.
이 프로그램에서 cpu 사용량이 15% 이상을 치는 문제가 발생했다.

리눅스에서 tcp와 udp의 소켓 정보는 /proc/net/tcp, tcp5, udp, udp6 파일에서 확인 할 수 있다.
문제의 프로그램은 위 파일을 읽고 파싱하여 데이터를 가져온다.
커넥션이 적을 때는 문제가 없지만, 커넥션이 많아지면서 문제가 발생한 것이다. 

커넥션 수가 20000개가 넘어가면 읽는 시간은 느려지지 않지만, cpu사용율이 15% 가까이 발생한다. 
커넥션 수가 50000개가 넘어가면 전체 데이터를 읽어오는 시간은 20초 가까이 걸리며 cpu사용율을 100% 가까이 사용한다.

tcp, udp 파일에서 하나의 라인은 하나의 커넥션이다.
처음에는 5만 라인이 굉장히 많은 줄 알았다. 그러나 /proc/net/tcp 파일을 복사해서 그 복사한 파일을 읽었더니 굉장히 빨랐다. 
/proc/net/tcp 파일 자체가 문제였던 거다. 구글링으로 문제에 대해 검색을 시작했다. 

구글링 후 알게 된 하나는 netstat명령과 ss 명령의 차이점이었다. 둘 다 네트워크의 상태를 보는 명령이지만 구조 자체가 다르다. 
netstat은 /proc/net/tcp 파일을 읽어들인다. 그렇기 때문에 커넥션이 많을 때는 netstat 명령 역시 느려진다. 
반대로 ss 명령은 커넥션이 많을 때도 굉장히 빠르다. 


AF_NETLINK를 이용하기 위해서는 C 언어를 이용해야 했다. C 언어 자체는 잘 모르지만 예제 샘플을 이용해서 원하는 형태의 프로그램은 만들 수 있을 것 같았다. 
만들어진 프로그램을 JNI을 이용해서 가지고 오면 될것으로 봤다.

여기에는 몇가지 문제가 있었다. kernel 과 직접 통신하기 때문에 커널 버전에 맞는 각각의 실행파일을 따로 준비해야 한다. (문제가 있었던 프로그램은 여러 서버에서 돌아가야 했다.)
커스터마이징을 한다고 해도 c 코드 자체를 수정하기가 쉽지 않다. 관리포인트가 늘어나는 문제도 있다.

고민을 거듭하다 방향을 바꿔서 그냥 ss 명령을 사용하는 방법을 생각했다.
ss 명령은 iproute 패키지의 일부분이다. iproute 패키지는 소스가 공개되어있다.
문제가 되었던 프로그램이 돌아가던 운영체제는 대부분이 centos나 redhat이었다.
centos 4,5,6,7 버전의 minimal 설치 패키지를 조사하여 iproute 패키지가 있는 지 확인하였다. 전부 있는 것으로 확인하였다.

ss 커맨드를 이용한 방법으로 개발을 진행하겠다고 컨펌을 받고 개발을 진행하였고 잘 마무리 되었다.

나중에 알게된 솔라윈즈에서 작성한 글이 딱 내가 겪었던 일을 잘 설명해 주어서 링크한다.

2021년 1월 27일 수요일

단락 연산자(short-circuit operator)

 리눅스에서 명령어를 연속 해서 사용 할 때가 있다.

보통 || 와 &&을 사용한다. 이를 단락 연산자(short-circuit operator)라고 한다.

첫번째 명령어를 실행하고 곧이어 두번째 명령어를 실행하는 역할이다.


예제는 다음과 같다.

[root@localhost ~]# true || echo 'ok'
[root@localhost ~]# false || echo ok
ok
[root@localhost ~]#

||는 앞의 명령어 성공하면 뒤의 echo 'ok'는 실행하지 않는다.
앞의 명령이 실패하면 뒤의 echo 'ok' 명령를 실행한다.

&&은 ||과 반대이다. 앞의 명령이 성공하면 뒤의 명령을 실행한다.
앞의 명령이 실패하면 뒤의 명령을 실행하지 않는다.

[root@localhost ~]# true && echo 'ok'
ok
[root@localhost ~]# false && echo 'ok'
[root@localhost ~]#

앞 명령어와 상관없이 실행하고자 할 때는 ;를 쓴다.
[root@localhost ~]# echo '1ok'; echo '2ok'
1ok
2ok
[root@localhost ~]#

2020년 12월 17일 목요일

modelmapper memory leak

최근 추가한 코드를 테스트하는 개발서버에서 아래의 에러 메시지가 발생했다.


java.lang.OutOfMemoryError: Direct buffer memory
        at java.nio.Bits.reserveMemory(Bits.java:694)
        at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123)
        at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
        at io.netty.buffer.UnpooledUnsafeDirectByteBuf.allocateDirect(UnpooledUnsafeDirectByteBuf.java:111)
        at io.netty.buffer.UnpooledUnsafeDirectByteBuf.(UnpooledUnsafeDirectByteBuf.java:68)
        at io.netty.buffer.UnsafeByteBufUtil.newUnsafeDirectByteBuf(UnsafeByteBufUtil.java:626)
        at io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:65)
        at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:179)
        at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:170)
        at io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:131)
        at io.netty.channel.DefaultMaxMessagesRecvByteBufAllocator$MaxMessageHandle.allocate(DefaultMaxMessagesRecvByteBufAllocator.java:73)
        at io.netty.channel.socket.nio.NioDatagramChannel.doReadMessages(NioDatagramChannel.java:243)
        at io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:75)
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:642)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:565)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:479)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:441)
        at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
        at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
        at java.lang.Thread.run(Thread.java:748)



추가한 부분은 udp 클라이언트 용도의 프로그램이었다. 


소스를 분석해 봤지만 문제되는 부분을 알 수 없었다.


visulavm을 이용해 모니터링을 해봤더니, classes의 Total loaded 개수가 계속 증가하면서, Metaspace 영역이 계속 증가하는 현상을 확인했다.

dump를 받아서 mat 프로그램으로 분석했다. 잘 사용할 줄은 몰랐지만 대충 보다 보니 modelmapper가 보인다. 뭐지...


'modelmapper 성능 이슈'로 검색해 봤더니, 이런 글을 찾았다. 


설마 하는 생각으로 modelmapper 부분을 제외했더니, 정상적으로 돌아왔다.


modelmapper는 예전부터 web 프로젝트에서 많이 사용했었기 때문에 이런 문제가 발생할 줄 몰랐다.


'modelmapper memory leak'으로 검색해서 더 알게 된 정보는 modelmapper는싱글톤으로 구성하여 사용하라는 것이다.


modelmapper를 많이 사용하지 않았기 때문에 전부 걷어내고 직접 지정하는 방식으로 변경했다.


이런 문제가 한 번 생기고 나니 modelmapper를 다시 사용하기 꺼려진다.


modelmapper memory leak으로 찾아 본 글

https://www.programmersought.com/article/1444628366/
https://better-dev.netlify.app/java/2020/10/26/compare_objectmapper/
http://modelmapper.org/user-manual/faq/
https://github.com/modelmapper/modelmapper/issues/375

2020년 12월 5일 토요일

오라클 접속이 되다 안되다 하는 현상.

스프링 기반으로 만든 소스에서 오라클 접속이 로컬에서 개발 할 때는 잘 되었다. 
문제는 개발서버에서는 연결 자체가 안됐다. 아예 안 되는 건 아니고, 가끔 연결이 될 때도 있었다. 
오류메시지가 연결되지 않은 것에 대한 메시지만 나오니 이 에러메시지로는 해결책을 찾기가 어려웠다.
이것저것 여러가지를 적용해 봤지만, 해결이 안 됬다. 다음날 다시 검색 시작. 해결법을 찾았다.

해결법은 이거였다.
-Djava.security.egd=file:/dev/./urandom
리눅스에서 오라클 jdbc 드라이버는 기본적으로 random 을 사용하는 데, random은 서버의 엔트로피를 사용하여 random 값을 생성한다고 한다. 여기서 엔트로피란 서버의 노이즈라고 한다.
노이즈는 서버의 디스크 읽기, 키보드 입력, 네트워크 패킷등이라고 한다.
random은 이런 엔트로피가 일정조건까지 채워져야 값을 생성해 준다고 한다. 이 기다림이 문제 였다. 반면 urandom 일정조건을 채우지 않고도 바로 값을 준다고 한다.

잘 이해가 안 가서 검색을 하니, 잘 정리된 블로그가 있었다.

역시 검색을 하려고 해도 뭘 알아야 검색을 할 수 있다.

결국 옵션 하나로 해결한 문제였지만 문제의 원인을 알 수 없을 때는 꽤 골치 아픈 문제였다.