Spring Boot Toy 프로젝트 세션 클러스터링-1

JAVA/Spring|2019. 7. 4. 12:30

현재 라즈베리 파이에 올려놓은 Toy Project는 

앞에는 Nginx(web서버) 뒤에는 SpringBoot(WAS라 해야하나 WebApp이라고 해야하나..) 그리고 mysql로 구성되어있습니다.

 

nginx를 둔 이유는 정적 리소스 처리도 있지만, 버전 업을 위해 Boot를 배포해야하는 경우

Boot를 바로 내리기보다 먼저 신규 Boot를 배포하고 nginx의 설정을 기존 boot에서 신규 boot로 reload하여

DownTime을 제거하는 것이 가장 큰 이유였습니다.

 

현재 Toy프로젝트는 로그인 방식이 아니고, 쿠키를 활용하여 동작하기 때문에 위와같이 사용하더라도

사용자에게 문제가 생길 부분은 없지만, 추후 로그인을 위해 그리고 세션에 데이터를 활용하게 된다면

신규버전 배포시에 기존 boot에서 로그인 한 사용자들은 로그아웃이 되는 예상치 상황을 만날수 있음을 인지했습니다.

 

바로 적용을 할지는 의문이지만, 먼저 찾아보았을때 boot(톰캣)도 세션도 클러스터링이 가능하다는 내용들을 발견하여

추후 적용을 할때 빠르게 접근하기 위해 조금씩 내용을 정리할 예정입니다.

(필요할때는 왜 세션서버와 클러스터링이 생각이 떠오르지 않았는지.. 참..--;;)

 

일단 잠시 찾아봤을때

세션 클러스터링 방법

  1. 세션을 여러 서버들이 접근할 수 있는 곳에 저장한다(File, SQL Server, no SQL Server)
  2. 인스턴스 끼리 클러스터링 한다.
  3. 다른 인스턴스에 공유해둔다.

mysql을 사용중이니 mysql을 활용해도 좋을것 같고,

다른 분들을 봤을때 redis에도 저장해서 활용하시는것 같습니다(성능상 redis가 좋긴 하겠지요).

 

다음 글은 해당 내용 적용기를 작성하는걸로!

댓글()

[CompletableFuture][#1] 정리시작

JAVA|2019. 7. 3. 18:30

업무상 JDK7까지 밖에 쓸수없어 이후 나온것들을.. 다 파악하지는 못하지만

블로그 및 세미나를 보며 흥미로운 항목중 CompletableFuture이 있었습니다.

 

가장 흥미로웠던 부분은 병렬처리 flow를 쉽게 개발할수 있게 해줄수 있겠다는것!

 

여기서 사용할 예는

RunA async실행 후 RunB1 RunB2 병렬 수행, RunC 실행 되는 케이스를 해보려고 한다.

 

 

그전에 클래스를 조금 살펴보면

Future와 CompletationStatge 인터페이스를 구현하고 있다.

 

Future는 지금까지 이해하고 사용하는 부분으로는 caller 쓰레드가 async 한 작업을 위해 다른 쓰레드에 일을 넘길경우 결과값 및 실행 종료 여부를 파악할수 있도록 사용하는 클래스로 생각해왔으며

결과값을 얻거나 일정 시간동안 결과값이 set 될때까지 대기하는 get(), get(time) 과 

여부를 알수 있는 isDone() 등이 했다.

 

여기서 CompletationStatge는 처음 접한 인터페이스 인데 역시나 JDK8에 새로 추가된 인터페이스 였다.

선언 되어있는 메소드너무 많다..

thenApply(Function )

thenApplyAsync(Function )

thenAccept(Consumer)

thenAcceptAsync(Consumer)

thenRun(Runnable)

thenRunAsync(Runnable)

thenCombine(CompletaionStage, BiFunction)

thenCombineAsync(CompletaionStage, BiFunction)

thenAcceptBoth(CompletaionStage, BiConsumer)

thenAcceptBothAsync(CompletaionStage, BiConsumer)

...

handle(BiFunction)

handleAsync(BiFunction)

 

대부분 then** 인 만큼 앞에 작업을 수행후 수행할 작업들을 정의하는 것으로 파악 된다.

(다음에는 각 의미를 알아보고 정리할 예정)

 

CompletableFuture 는 다양한 static 메소드를 지원하여 다양한 방식을 지원

(하나의 runnable을 받아 async로 실행하거나, 특정 executor를 받는다던지, 여러 runnable을 받아 일괄로 실행한다던지)

allOf

anyOf

completedFuture

runAsync

supplyAsync

 

(다음에는 각 의미를 알아보고 정리할 예정)

 

 

댓글()

가끔 까먹는 비교 연산(IntegerCache, LongCache)

JAVA|2019. 7. 3. 10:04

아.. 순간 동작 방식을 헤깔렸버렸다...ㅠㅠ;;

 

대략적인 흐름은 

Integer val1=3;

Integer val2=3;

 

val1==val2// true일지 false 일지....

 

위에 val1=3 는 오토 박싱으로 기존 primitive가 Integer로 박싱된다.

디컴파일러로 보면 

Integer val1=Integer.valueOf(3); 으로 코드가 바뀐다.

 

그런데 실수는.. 아래 비교 부분이다

 

비교연산자에서

if(obj1==obj2) , 는 당연히 ref 비교

 

if(obj1.equals(obj2)), 는 당연히 equals() 내부 구현을 따라가는 비교..

(대부분 안에 값 비교)

때문에 String 비교는 ==가 아닌 equals 비교를 해야 원하는 동작이 된다..

 

착각한 부분은 

1.

==가 ref 비교가 아니라 hash비교로....... 착각 했다..

(하긴 hash 비교면 String도 equals를 할께 아니라 == 하면 되는건데..)

 

 

2.

이전에 블로그에서 봤던 부분인데 Integer는 오토박싱시에 캐싱을 한다는것..

범위가 대략 127까지 였던것 같은데.. 

애매하게 알았으니 착각한게 당연한지도..

 

그래서! Integer코드를 열어봤다

Integer의 valueOf(int) 위의 경우에는 이 부분이 불릴것이고.. 캐시동작..

 

 

주석에는 아주아주 친절하게, 오토박싱하는 값(-128~127) 까지는 값을 캐싱 해두도록 되어있었다..

그렇다면 해시값은 물론이고 객체 값도 당연히 같게 되겠지..

아 그리고 아래 로직을 보면 꼭 최대값이 127이 아니고, Property, java.lang.Integer.IntegerCache.high 값에 의해 결정되는걸 확인할 수 있었다.

 

결국 다시한번 부족함을 느끼고 지식이 늘음에 감사하는걸로..

 

추가로!

Double에는 없었고

Long도 열어보니 캐싱을 하고 있다.. 대신에 여기는 무조건 -128~127까지만!

Long의 valueOf()

 

 

요약

1. 오토박싱

2. 비교 연산자

3. IntegerCache, LongCache

4. 언제나 코드는 까보자.

5. 기초를 잊지 말자.

 

 

 

댓글()

[Quartz] CronExpression getNextInvalidTimeAfter

JAVA|2019. 7. 2. 14:30

개발한 스케줄러에서는 cronExpression 해석부분 처리를 위해 Quartz의 CronExpression 클래스를 활용하고 있습니다.

(expression 파싱과 다음 시간 구하는 부분은 굳이.. 새로 개발할 필요성을 느끼지 못해서..)

 

스케줄 시작시간 시간부터 해당 스케줄의 다음 invoke 타임을 구하기 때문에

ex) 등록 9시, 시작시간 10시, cronExpression 5초마다 기존에는 10시 5초 부터 스케줄 실행.. 사용자는 10시부터 실행되길 기대

 

 

스케줄 시작시간(startTime) 시점과 invoke 타임이 일치하는 경우 invoke가 안되는 부분이 있었습니다.

때문에, "아 그럼 미리 invokeTime 을 구해두면 되겠구나" 라는 생각으로 미리 구하자는 생각으로 작업에 들어갔고,

 

1. 현재 시간이 스케줄 시작시간 보다 이른경우 스케줄 시작시간 시점으로 다음 스케줄 시간을 구해 놓자

 

라고 생각하고 테스트 작성 후 개발했지만 생각과 다르게 동작했습니다..

(10시를 넘겼는데, 계속 nextInvokeTime은 10시 5초..)

해서.. 코드를 열어보니..

준 시간에 1000, 즉 1초를 더하고 있었습니다..;;

(하긴.. getTimeAfter이니.. quartz cron 표현식 상 초단위이기 때문인것..)

 

결국 교훈은 가져다 쓴 코드는 코드를 까보고 이해하고 쓰자.. 입니다.

 

 

 

댓글()

Spring Cloud Config Server

JAVA/Spring|2019. 6. 25. 19:00

요즘 Spring Boot로 배포함에 따라 jar 하나만 배포하는 경우가 많습니다.

때문에 Config 파일은 어떻게 배포해야할지 고려해야할 부분들이 생겼고

 

간단하면서 단순한 두가지 방법이 있습니다.

 

1. Jar에 설정 파일을 포함한다.

   jar내에 config 파일을 포함시켜 배포할 수 있지만 설정을 변경을 해야하는 경우 재배포 하거나

   jar내에 config 파일을 수정할수도 있습니다.

   하지만 간단히 설정만 바꾸고자 하는데 너무 과분하다고 생각이 듭니다.

 

2. 외부에 config 파일을 두고 boot를 띄울때 커맨드를 주어 config를 잡는다.

   java -jar example.jar --spring.config.location:....

  설정파일이 외부에 있기 때문에 설정하기에는 편합니다.

  하지만 배포해야할 서버, 설정을 변경해야 할 서버가 많아진다면 

  노가다이기도 하고, 변경하다가 놓치거나 잘못 변경하는 실수가 발생할 수 있습니다.

  물론 변경 내역 관리가 안되는건 덤입니다.

 

위에 문제점들을 개선할수 있는 방법으로 Spring Cloud Config Server를 활용할수 있습니다.

 

'Spring Cloud Config Server' 란

 

각 서버가 Config 파일을 직접 관리하거나 가지고 있는것이 아니라 'Spring Cloud Config Server'를 통해 설정을 가져오며 'Spring Cloud Config Server'는 설정 파일을 git이나 파일 스토리지에 관리하여 Client 요청에 따라 config 내용을 전달해줍니다.

 

'Spring Cloud Config Server'를 활용하게 되면 App(그림에서는 Microservice) 배포시에

각종 설정들(DB나 기타 설정들)은 고려하지 않아도 되며 'Spring Cloud Config Server' 과 profile을 통해 각 설정들을 얻어올수 있습니다.

 

아직  코드를 깊게 보지 않았으나

조금 사용 해봤을때 일단 설정은 굉장히 간단하며

 

추후 깊게 볼 필요(기본적으로 지원하는 git이나 file이 아니라 커스텀 작업을 해야한다면)가 생기면 추가로 살펴보려고 합니다.

잠깐 살펴보니 JDBC Backend랑 있네요

https://cloud.spring.io/spring-cloud-config/2.1.x/single/spring-cloud-config.html#_jdbc_backend

 

 

참고로 'Spring Cloud Config Server' 를 WebFlux로 띄워볼까 하여 시도해봤지만

@EnableWebFlux 옵션을 추가하면 기동시 에러가 발생합니다.

에러와 Spring starter에서 'Spring Cloud Config Server'만 선택시 tomcat이 함께 포함되는것을 봐서는 내부에 @EnableWebMvc가 포함되어있는것으로 예상됩니다.

 

 

설정

'Spring Cloud Config Server'를 사용하기 위해서는

 

1. @EnableConfigServer 설정

2. git url 및 접속정보 설정(private일 경우 userusername, password 설정 필요)

 

3. git에 설정파일 push(설정파일은 프로젝트명-profile.yml)  , yml아니라 properties도 가능

4. url로 테스트(url:port/프로젝트명/profile)

 

refresh 주기라던가 즉시 refresh 등 실제 사용에 필요한 부분들은 

차차 필요시 알아볼 예정입니다.

 

'Spring Cloud Config Client' 는 건너뛸수 없으니 조만간 블로그로 정리 예정입니다.

댓글()

[SpringBoot] Embedded Tomcat, NioEndpoint

JAVA/Spring|2019. 6. 11. 19:22

SpringBoot Async 처리를 다시 학습 중에

어떻게 Embedded Tomcat에서는 요청을 처리하고 있는지 파악하기 위해 조금 코드를 살펴봤다.

 

 

일단 크게 별다른 설정을 하지 않고 디버그 모드로 붙었을 때 아래와 같이 스레드가 떠있다.

대략 오늘 살펴보려는 스레드는 4가지 정도다

 

  • http-nio-8080-Acceptor-0
    • Http Port accept 하는 스레드(ServerSocket.accept())
    • 관련 클래스는 org.apache.tomcat.util.net.Acceptor
    • accept 되면 getpoller() 후 채널을 regist 한다
      • getpoller는 poller가 여러 개의 경우 rr로 동작한다.
  • http-nio-8080-ClientPoller-0~1
    • Accept 된 Socket을 Selector에 OP_READ를 등록하고 Selector를 epoll 한다.
    • 관련 클래스는 org.apache.tomcat.util.net.NioEndpoint$Poller
    • 요청이 들어오면(read가 불리면), ThreadPoolExecutor.execute를 부르게 되고, 결국에 TaskQueue에 offer()를 부르게 된다.
    • poller가 하나가 아니라서 어떤 기준으로 동작 하나 살펴보니 아래와 같이 값이 설정되어있으며
      • polle는 많아도 2개
      • 기본 동작은 selector는 select()로 blocking이 아닌 select(1000), 1초마다 깨어 난다.

  • http-nio-8080-exec-1~10
    • 실제 ServletThread
    • TaskQueue(BlockingQueue)를 take 하며 대기, poller 혹은 다른 곳에서 TaskQueue에 이벤트를 넣어주면 동작
    • 선후 처리 및 Controller 코드가 보통 이 스레드를 통해 불리게 되며 Async 처리 이후 다시 돌아와(받은 스레드가 대기하여 다시 돌아온다는 이야기는 아니고, ServletThread->Async->ServletThread) 처리
    •  

  • MvcAsync
    • Controller에서 Async 처리(Callable이라던지 기타 등등..) 이후 응답을 직접 하려나? 해서 살펴보니
    • 마지막에 다시 TaskQueue에 offer를 하고 있었다.

 

 

전체 처리를 대략적으로 그리면 아래와 같이 될 것 같다.

 

 

댓글()

[MyBatis] MyBatis 문법 파서

DB|2019. 5. 29. 19:13

업무상 MyBatis를 그대로 활용하기보다 해당 기능을 녹이고

MyBatis 문법을 가능한 그대로 입력 받기 위해, 그리고 파싱하기위해 

MyBatis 문법을 파악하고 녹일 필요가 있었다.

 

MyBatis은 보통 XML 파일에 작성이 되며 내부 문법도 XML 문서내에 xml 형식으로 입력된다.

 

대략 작성해보면...(parameter랑 다 생략)

ex)

<select id="aaa">

  SELECT ename, empno, dept FROM EMP

  <where>

  <if test="ename !=null">

      ename=#{ename}

  </if>

  </where>

</select>

 

지금 예로는 if element 안에 test라는 attribute가 나온다는걸 알수 있는데

메뉴얼만 보기에는 모든 attribute나 element가 어떤것들이 더 사용되는지

element간에 관계가 어떻게 되는지 파악하기 어렵다

(예를 들어 <select>안에 <where> 가 들어갈수 있는데 <if>안에는 어떤 element가 들어갈 수 있는지)

 

그래서 혹시 xsd(XML Schema Definition, 말그대로 XML의 정의서)이 없을까?

해서 소스를 열어보니 마침 xsd 파일이 존재 했다

 

mybatis/org.apache.ibatis.builder.xml/mybatis-mapper.xsd

 

업무상 문법을 확장할 필요도 있기 때문에 그대로 가져다 사용하진 않았지만

xsd 파일을 통해 element관계가 엮이고 엮여있는것을 확인했고

덕분에 문법(입력할 element) 들을 빠르게 파악할수 있어

mybatis에 대한 파서를 커스텀 하여 만들수 있었다.

 

 

 

 

 

'DB' 카테고리의 다른 글

[DB] 기초부터다시, ACID, CAP  (0) 2020.09.14
GraphQL 끄적끄적  (0) 2020.08.20
기초부터다시, 조인  (0) 2020.08.18
[MongoDB] 해킹된 후 보안 설정 관련  (0) 2019.12.23
DB 스키마를 관리하자  (0) 2018.12.20

댓글()

[Spring][Thymeleaf] if else그리고 조건문

JAVA/Spring|2019. 5. 22. 19:30

Spring Boot에서 View를 주로 Thymeleaf를 사용하고 있다.

 

생각 안나는 것들은 필요할때마다 검색해서 사용하고 있기 때문에

 

정리해본다.

 

예제는 dutch 값이 모델에 있을경우 dutch.name을 확인 후 selected 세팅을 하도록

 

단순 if

<option  th:if="${dutch!=null}" 
th:each="coffee : ${T(com.meteor.coffee.CoffeeEnum).values()}" 
th:value="${coffee.name()}" th:text="#{${coffee.name()}+'.name'}"
th:selected="${coffee.name() == dutch.coffeeKind.name()}">
</option>

 

 

if-else

<option  th:if="${dutch!=null}" 
th:each="coffee : ${T(com.meteor.coffee.CoffeeEnum).values()}" 
th:value="${coffee.name()}" th:text="#{${coffee.name()}+'.name'}"
th:selected="${coffee.name() == dutch.coffeeKind.name()}">
</option>

<option  th:unless="${dutch!=null}" 
th:each="coffee : ${T(com.meteor.coffee.CoffeeEnum).values()}" 
th:value="${coffee.name()}" th:text="#{${coffee.name()}+'.name'}">
</option>

 

여기서 기억에 남는건 unless에 들어가는 조건이 위의 if 조건에 들어간것과 같아야 한다는것

 

 

추가적으로, 변수 내에서도 분기가 가능하다

<form method="post"> 에서 method를 분기 처리한다면

 

<form method="${dutch==null ? 'post':'put'}"> 처럼 표현이 가능하다.

물론 put은 

<input type="hidden" name="_method" value="put">

으로 표현되겠지만..

 

 

댓글()