반응형
반응형

사내에 국내 / 해외 모니터링을 하는 중앙 서버가 하나 있고, 해당 중앙 서버를 Web 환경에서 컨트롤 할 수 있는 Spring Boot로 개발한 어플리케이션을 하나 운영중인데, 중앙 서버가 모니터링 하는 서버들의 상태를 특정 주기마다 불러와서 보여주는 역할을 하고 있다.


해당 역할을 하는 중앙 서버의 모니터링 시스템이 어플리케이션이 아니라, 그냥 데몬이기 때문에 어플리케이션 단에서는 ssh 통신을 해서 Input Stream을 통해 콘솔에 나타나는 정보를 가져와야 한다.


ssh 통신 간 사용한 라이브러리는 jsch (http://www.jcraft.com/jsch/)를 이용하였고, In / Output Stream을 편리하게 메소드 형태로 가져올 수 있게 구현이 되어 있다.


아무런 생각없이, 아래와 같이 구현하고 몇 개월동안 별 문제 없이 운영을 했었음. (ssh 연결 과정은 생략하며, Stream 처리 부분만 명시함.)

...
// stdout이 jsch에서 가져온 InputStream임..
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
StringBuffer sb = new StringBuffer();
String line;
while((line = reader.readLine()) != null) {
    sb.append(line + "\n");
}
...


정상적인 상황에서는 문제가 없지만, 가끔 중앙 서버와 통신하는 서버의 IP가 잘못 입력된 경우나, 없어진 서버가 생기면 시스템에 과부하가 오는 현상이 발견되었다.


디버깅을 하던 과정 중 위 코드의 치명적인 문제를 발견함.


1. 일단, BufferedReader의 readLine method가 blocking method이기 때문에, 읽을 수 있을 때까지 무한정 대기하며, 서버내의 자원을 점유한다. (이런 류의 요청이 쌓이면, OOM 등 시스템에 문제가 전파됨.)


2. 무한정 대기하다가 결국 못읽는다고 판단하면, Exception을 발생시키는데, 이 시점을 코드 레벨에서 컨트롤할 수 없음.

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

최종적으로 아래와 같이 해결함.


1. BufferedReader에서 제공하는 ready method를 이용해, 읽을 수 있는 지 먼저 검사하는 방식으로 시스템이 blocking되지 않도록 변경


2. Guava(https://github.com/google/guava)에서 제공하는 SimpleTimeLimitter를 이용하여, 코더가 Timeout을 컨트롤 할 수 있도록 구조를 변경

BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
StringBuffer sb = new StringBuffer();
int timeoutSec = 3;
SimpleTimeLimiter limiter = new SimpleTimeLimiter();
    try {
	limiter.callWithTimeout(() -> {
            String line;
            boolean isReady = false;
            while(!isReady) {
                isReady = reader.ready();
            }
            while((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
	}, timeoutSec, TimeUnit.Seconds, false);
    }
...


이제 중앙 서버가 IP가 잘못 등록되었거나 존재하지 않는 원격 서버와의 통신을 시도해도, 3초 이상 기다리지 않고 요청을 끊어버리므로 위에서 발견된 문제는 모두 해결이 되었다.

반응형
,
반응형

Oracle Jdk 1.8버전을 받아서 내부 시스템에서 활용을 하고 있었는데, SNS나 여기저기 커뮤니티에서 Oracle이 야금야금 라이센스를 걸고 넘어지기 시작했다는 소문을 접수하고, OpenJdk로 전부 다 갈아버리기도 마음을 먹었다.


Oracle Jdk 1.8은 Binary 형태로 압축을 풀어서 환경변수만 맞춰서 사용했었는데, OpenJdk 1.8 버전은 아직 Binary로 제공하진 않는 것처럼 보여서, 패키지 설치로 진행하기로 했다.


일반적으로 다 잘 동작했는데, 서버 간 Https 통신하는 부분에서 아래와 같은 에러가 발생함.

...
java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
...

구글링을 좀 해보니, java 인증서가 설치되어 있지 않은 이슈였다.

실제로 /etc/ssl/certs/java/ 경로에 가보니 아무것도 없어서, SSL 통신이 안됐던 거였음.


아래와 같은 명령어로 기존에 있던 인증서를 정리한 후, 다시 설치 해주면 해결 된다. (Ubuntu 14.04 기준)

$ sudo dpkg --purge --force-depends ca-certificates-java
$ sudo apt-get install ca-certificates-java


어플리케이션 종류에 따라, 어플리케이션 재실행을 해야 될 수도 있음.

반응형
,
반응형

JDK9가 드디어 정식 릴리즈 됨.

http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.html


다운받아서 설치하고 환경변수를 잡은 뒤 STS를 실행했으나, 아래와 같은 에러가 발생함.

eclipse.buildId=3.9.0.201707061823-RELEASE-e47
java.version=9
java.vendor=Oracle Corporation
BootLoader constants: OS=win32, ARCH=x86_64, WS=win32, NL=ko_KR
Framework arguments:  -product org.springsource.sts.ide
Command-line arguments:  -os win32 -ws win32 -arch x86_64 -product org.springsource.sts.ide

!ENTRY org.eclipse.osgi 4 0 2017-09-22 09:53:56.501
!MESSAGE Application error
!STACK 1
org.eclipse.e4.core.di.InjectionException: java.lang.NoClassDefFoundError: javax/annotation/PostConstruct
	at org.eclipse.e4.core.internal.di.InjectorImpl.internalMake(InjectorImpl.java:410)
	at org.eclipse.e4.core.internal.di.InjectorImpl.make(InjectorImpl.java:318)
	at org.eclipse.e4.core.contexts.ContextInjectionFactory.make(ContextInjectionFactory.java:162)
	at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createDefaultHeadlessContext(E4Application.java:491)
	at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createDefaultContext(E4Application.java:505)
	at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createE4Workbench(E4Application.java:204)
	at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:614)
	at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:336)
	at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:594)
	at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:148)
	at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:151)
	at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
	at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:134)
	at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104)
	at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:388)
	at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:243)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:653)
	at org.eclipse.equinox.launcher.Main.basicRun(Main.java:590)
	at org.eclipse.equinox.launcher.Main.run(Main.java:1499)
Caused by: java.lang.NoClassDefFoundError: javax/annotation/PostConstruct
	at org.eclipse.e4.core.internal.di.InjectorImpl.inject(InjectorImpl.java:124)
	at org.eclipse.e4.core.internal.di.InjectorImpl.internalMake(InjectorImpl.java:399)
	... 22 more
Caused by: java.lang.ClassNotFoundException: javax.annotation.PostConstruct cannot be found by org.eclipse.e4.core.di_1.6.100.v20170421-1418
	at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:433)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:395)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:387)
	at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:150)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
	... 24 more

!ENTRY org.eclipse.e4.ui.workbench 4 0 2017-09-22 09:53:56.515
!MESSAGE FrameworkEvent ERROR
!STACK 0
java.lang.NoClassDefFoundError: javax/annotation/PreDestroy
	at org.eclipse.e4.core.internal.di.InjectorImpl.disposed(InjectorImpl.java:450)
	at org.eclipse.e4.core.internal.di.Requestor.disposed(Requestor.java:156)
	at org.eclipse.e4.core.internal.contexts.ContextObjectSupplier$ContextInjectionListener.update(ContextObjectSupplier.java:78)
	at org.eclipse.e4.core.internal.contexts.TrackableComputationExt.update(TrackableComputationExt.java:111)
	at org.eclipse.e4.core.internal.contexts.TrackableComputationExt.handleInvalid(TrackableComputationExt.java:74)
	at org.eclipse.e4.core.internal.contexts.EclipseContext.dispose(EclipseContext.java:178)
	at org.eclipse.e4.core.internal.contexts.osgi.EclipseContextOSGi.dispose(EclipseContextOSGi.java:99)
	at org.eclipse.e4.core.internal.contexts.osgi.EclipseContextOSGi.bundleChanged(EclipseContextOSGi.java:141)
	at org.eclipse.osgi.internal.framework.BundleContextImpl.dispatchEvent(BundleContextImpl.java:908)
	at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)
	at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:148)
	at org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEventPrivileged(EquinoxEventPublisher.java:213)
	at org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEvent(EquinoxEventPublisher.java:120)
	at org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEvent(EquinoxEventPublisher.java:112)
	at org.eclipse.osgi.internal.framework.EquinoxContainerAdaptor.publishModuleEvent(EquinoxContainerAdaptor.java:168)
	at org.eclipse.osgi.container.Module.publishEvent(Module.java:476)
	at org.eclipse.osgi.container.Module.doStop(Module.java:634)
	at org.eclipse.osgi.container.Module.stop(Module.java:498)
	at org.eclipse.osgi.container.SystemModule.stop(SystemModule.java:202)
	at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle$EquinoxSystemModule$1.run(EquinoxBundle.java:165)
	at java.base/java.lang.Thread.run(Thread.java:844)
Caused by: java.lang.ClassNotFoundException: javax.annotation.PreDestroy cannot be found by org.eclipse.e4.core.di_1.6.100.v20170421-1418
	at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:433)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:395)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:387)
	at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:150)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
	... 21 more


해결책은 eclipse.ini 또는 sts.ini 파일을 열어서 -vmargs 하위에 아래와 같은 argument를 넣어주면 된다.

-vmargs
--add-modules=java.se.ee


이렇게까지 했는데도 동일하게 에러가 발생한다면, IDE자체에서 JDK9를 싫어하는 것이므로, Eclipse Oxygen 이상의 버전으로 올려야 한다.


그런데... IDE 정상 구동까지는 잘되지만, IDE 내부에서 Compiler 지원을 JDK8까지밖에 하지 않기 때문에, Eclipse Marketplace에 가서 별도로 Java 9 Support (BETA) for Oxygen 4.7 플러그인을 설치해서 사용해야 한다. 


본인은 BETA!!라고 써있는 것을 보고, 억지로 맞춰서 쓰는 느낌이라 그냥 새로운 IDE 배포를 기다리기로 했다; 

반응형
,
반응형

프로그래밍을 처음 배울때 if문을 배우면서 숫자 비교 같은 것을 배우게 됩니다. 

그러다가 응용 단계로 가면 문자열 비교를 배우게 되죠.

아마 대부분 아래와 같이 쓰지 않았었나 생각됩니다.. (물론 저도 포함 ^^;;) 

 

String str = "abc";
String input = "abc";
 
if(str == input) {
   system.out,println("true");
} else {
   system.out,println("false");
}


 

결과는, 당연히 false가 찍히겠죠. false가 찍히는 원인은 문자열 변수마다 가지고 있는 hashCode가 다르기 때문입니다.

따라서, String 비교시에는 아래와 같이 equals를 사용하면 원하는 결과인 true를 얻을 수 있습니다.

 

if(str.equals(input) {
   system.out.println("true");
} else {
   system.out.println("false");
}


 

하지만, equalsIgnoreCase라는 것이 있습니다. equals같은 경우에는 대소문자 구분을 철저히 하지만,

equalsIgnoreCase 같은 경우 대소문자를 구분하지 않습니다. 클라이언트에서 입력받은 String을 비교할 때 유용하게 쓰이겠죠.

아래와 같이 말입니다.

 


 

결과는 true가 찍힙니다.

String str = "abc"
String input = "AbC";
 
if(str.equalsIgnoreCase(input)) {
   system.out.println("true");
} else {
   system.out.println("false");
}

 

보통은 대소문자 구분 처리할때 toLowerCase(모두 소문자 변환), toUpperCase(모두 대문자 변환) 등을 이용하여 사용하지만,

equalsIgnoreCase를 사용하면, 한방에 해결할 수 있어서 좋은 것 같습니다.

반응형
,
반응형

primitive type (int, long, double, char 등..)을 사용하는 경우,

따로 객체 생성을 하지 않고 처리되기 때문에, 메모리 관리가 자동으로 이루어짐.

 

하지만 Reference Type (Integer, Long, Double, String 등)은 Object를 상속받는 객체이기 때문에,

null로 처리하거나, GC가 이루어진 경우에만 메모리 정리가 됨.

 

개인적으로 내린 결론은, Reference Type 사용 시에는 사용 후에 반드시 null처리를 해주도록 하는 것이다.

GC가 하는 일을 줄여보기 위한....

혹시나 코딩을 잘못했을 경우에, 사용하지도 않는게 메모리 영역에 살아있을 수도 있으니, 귀찮더라도 null로 세팅해주도록 하자.

GC에게 모든걸 맡기면 언젠가 OOM (out of memory)를 보는 상황이 올지도 모른다. 

 

물론 SoftReference나 WeakReference를 사용하는 것도 좋은 방법 중 하나이다. 

 

이건 참고한 글.

 

http://lemonfish.egloos.com/viewer/5380639  

반응형
,

[Gson] TypeAdapter

개발/Java 2016. 6. 10. 23:08
반응형

기존까지 Json Parser는 Jackson을 사용하고 있었는데,

어딘가에서 Gson이 가장 퍼포먼스가 좋다는 이야기를 듣고, 작년부터 Gson을 도입하여 사용하기로 하였다.

 

근데.. 엄청난 문제가 하나 있었다.

Number Type의 경우 toJson이나 fromJson메소드를 사용 시에, 무조건 double형태로 내부적으로 변환을 해서 주는 것이었다.

 

물론.. Integer라던지, Long이라던지 제대로 명시를 해서 변환을 요청하면 제대로 바꿔준다.  

하지만, Map<String, Object> 형태를 사용하여, 변환을 요청하는 경우 위와 같은 문제가 발생. 

 

성능을 높이기 위해 모든 Number Type을 double로 바꿔주는 것인가...  

자세한 이유는 만든사람만 알겠지;;

 

처음에는 일일히 double로 받아서, int로 변환해서 사용을 하였었는데..

찾아보니 역시나 간단하게 해결할 수 있는 방법이 있었다.

 

gson객체에 registerTypeAdapter라는 설정 관련 메소드가 있었고,

이것을 통해, Double로 들어오는 것들을 값의 범위를 계산하여 

Integer범위보다 크면 Long으로.. 아니면 Integer로 변경하도록 구현해보았다.

 

참고로, toJson과 fromJson을 사용 시 구현해야 하는 인터페이스가 다르다.

 

toJson : JsonDeserializer<T>

fromJson : JsonSerializer<T>

 

현재 클라이언트에서 받는 객체는 Pojo로 타입이 확실하게 명시되어 있고,

Response Data의 경우 Map으로 받고 있어서, toJson(obj, HashMap.class)을 할 때,  

무조건 .0이 붙어서 나오는 문제가 있으니 JsonDeserializer를 구현해서 쓰도록 결정하였다.

 

 

@Override
public HashMap<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        Map<String, Object> map = Maps.newHashMap();
        JsonObject jsonObject = json.getAsJsonObject();
        for(Entry<String, JsonElement> entry : jsonObject.entrySet()) {
            JsonElement element = entry.getValue();
            if(element instanceof JsonPrimitive && element.getAsJsonPrimitive().isNumber()) {
                Number nValue = CommonUtils.getRemoveDot(element.getAsDouble());
                map.put(entry.getKey(), nValue);
                
            } else {
                map.put(entry.getKey(), context.deserialize(element, Object.class));
            }
        }
        return (HashMap<String, Object>) map;
}

서비스에도 적용되어 있는 상태인데, 특별한 문제는 없었다.

반응형
,
반응형

데이터베이스를 Scale Out으로 구성하는 경우, auto increment sequence값에 대해 고민을 하게 된다.

 

다음과 같은 시도를 해보았다.

 

1. Unique idx 발급용 Redis를 별도로 두어, increment 명령어만 호출하여 사용한다.

  - 개발환경에서는 잘 사용하고 있었지만, SPOF 이슈가 있고, 별도 머신 세팅을 해야하므로 관리 이슈가 따름. (fail) 

 

2. java 내부에서 제공하는 UUID를 이용하여 randomString을 추출해낸다.

  - 겹칠 확률이 없다고 봐도 되고, 로직에서 처리하는 것이기 때문에 1번보다는 성능이 좋지만, 보통 DB에서 사용하는 unique idx는 long타입인데, 얘는 String타입으로 리턴해준다. 물론, long형태로 리턴해주는 메소드도 있어서 사용해봤고, 어느정보 겹칠 확률도 없었지만.. 좀 껄끄러워서 패스 (fail) 

 

3. 구글링을 하던 중 누군가 Github에 snowflake에서 unique idx발급 시 사용하는 알고리즘을 올려두었다. (트위터에서 올린건지는 모름;;)  

  - 전체적인 로직을 보진 않았지만, timestamp값을 가지고 or연산 등을 이용하여 생성해내는 방식인 것 같다. 이것도 unique idx를 generate하는 메소드는 string형태로 리턴이 되는 구조였지만, 내부적으로 Long으로 만든 후에 toString하는 방식이어서 toString만 없애고, 어느정도 맞게 수정해서 사용. 트위터에서 사용하고 있는 알고리즘이므로 믿고 사용하기로 결정.

링크 : https://github.com/Predictor/javasnowflake  

※ long id generate하는 부분 중에, hardWareAddress 가져오는 코드가 있는데, 이거 centOS에서 잘 안가져와 지는 것 같다. windows에서는 잘되는데, 왜 안되지 찾다가 보니 null을 반환했었다는.... 이럴 경우 getHardWareAddress()를 사용하면 안되고, Iterable한 형태로 리턴해주는 method를 이용하면 해결이 된다. (본인은 귀찮아서, null일 경우 로컬에서 한번 받아온 hardWareAddress를 세팅하도록 변경해서 사용중;; 어차피 중복 확률이 0%니깐 상관없을거 같아서..) 

 

nosql을 주 Storage로 사용하는 경우 2번의 방법도 괜찮다고 한다. 

반응형
,
반응형

https://github.com/guari/eclipse-ui-theme

 

검정 Base의 테마.

가독성도 나쁘지않고 맘에든다. 

반응형
,
반응형

windows - preferences - Install/Update - Available Sofrware에 가서,

The Eclipse Project Updates에 있는 url 가장 뒤에 / 를 붙여주고,

 

이클립스 p2 폴더에 있는 cache를 날려주고, 이클립스 재시작 후에 다시 시도하면 해결된다. 

반응형

'개발 > Java' 카테고리의 다른 글

[Gson] TypeAdapter  (0) 2016.06.10
[UUID] 분산 데이터베이스 환경에서의 UUID 발급  (0) 2016.06.10
[Eclipse] MoonRise Theme  (0) 2016.06.10
[Lambda] collect 함수 사용 시 주의점  (0) 2016.06.10
[Guava] EvictingQueue  (0) 2016.06.10
,
반응형

filter로 필터링 후에 collect로 새로운 Collection을 만들고, foreach를 돌리는 경우

필터링을 하고 새로운 Collection을 만들었기 때문에, 필터에서 걸러진 것들은 최종 결과물에서 안나올 거라고 생각을 했다.


그런데.. 필터링 되었던 항목이 그대로 포함이 되어 있었다.


이럴 경우에는 collect함수를 사용하면서 기존의 list에 덮어씌우는 작업 후에,

forEach를 돌리면.. 원하던대로 동작한다.



정리하면 아래 코드는 서로 다르게 동작한다는 거다.

체인 메소드 특성상, 내부적으로만 새로운 스트림을 만들기 때문인 것 같다.


list.stream().filter().collect(Collectors.toList()).forEach();


list.stream().filter().collect(Collectors.toList());
list.forEach();
반응형
,
반응형