[제가 해결한 방법은 글 제일 하단 쯤에 있습니다.]
내가 유지보수하는 플렛폼, 1개의 서비스를 운영함에 있어 tomcat 서버 1개에 여러개의 host를 설정하여 운영하고 있다.
이미지, 은행 관련 업무 등 적절하게 프로젝트를 나눠서 관리하고 있는 셈이다.
여러개의 프로젝트를 tomcat server.xml 파일에서 host로 분기하여 관리하고 있고 프로젝트 2개에서 quartz를 돌리고 있다.
A프로젝트에서는 여러 개의 trigger를 만들어 quartz를 돌려도 설정한 시간에 문제 없이 작동이 되었다.
다만,, 나머지 B프로젝트에서 돌리는 quartz는 돌릴 때마다 동일한 trigger가 1-2초 텀을 두고
또는 1초의 텀도 두지 않고 동시에 실행이 되는 문제를 보였다.
B프로젝트에서 quartz가 중복 실행되는 문제는 내가 입사하기 전부터 문제였으며 중복 실행 문제로 인하여 quartz를 돌려야 한다면
A프로젝트에서 quartz를 돌려야 했고, quartz를 돌리기 위해서 B프로젝트에 있는 소스를
A프로젝트에 중복으로 작성하는 모습이 보여졌다.
내가 quartz를 돌릴 작업은 없었고 단 한번 있었지만 quartz를 필요로 하는 작업이 중단되어 크게 의미가 있지 않아 굳이 쿼츠가 잘 돌아가는 A프로젝트에 코드를 중복으로 만들어서 쿼츠를 돌리고 싶지는 않아서 그저 내부적인 알림 용도였고 중요도가 낮았기에 크게 신경을 쓰지 않았다.
하지만 이제 쿼츠가 잘 돌아가지 않는 B프로젝트에서 쿼츠를 돌릴 때가 왔다.
local에서는 분명히 한번만 돌아가는데 서버 반영을 하면 역시나 쿼츠 동일 trigger가 중복으로 동시에 실행이 되었다.
아래는 내가 서치한 quartz 중복 실행 원인 몇가지이다.
- web.xml에 servlet-context.xml 파일을 중복으로 로드하게 구현해서
- quartz를 실행하는 bean을 중복으로 등록했거나 어딘가에서 또 중복으로 불러오고 있어서.
- servlet-context.xml 파일에 bean을 등록하고 quartz 구현부(QuartzJobBean 상속 받은 클래스)에서 @Component 어노테이션을 작성하면서 빈이 한번 더 등록되어서.
.......
많은 원인들이 있었지만 대체적으로 위 3가지 원인을 제일 많이 접하였고 해결 방안도 바로 찾을 수 있었다.
하지만 아무것도 나에게는 해당되지 않았기에 해결되지도 않았다.
servlet-context.xml 파일을 2번 로드 하는 것 같지도 않았고, servlet-context.xml 파일 내에서 bean을 중복으로 등록하지도 않았으며
quartz구현부 클래스에 @Component 어노테이션을 제거해도 쿼츠는 중복으로 실행 되었다.
다른 해결 방법으로는 tomcat server.xml 파일의
autoDeploy="true" -> autoDeploy="false"
autoDeploy="false" 로 변경을 해보라고 해도 해결되지 않았다. 그 외 다른 값들도 false로 수정해도 마찬가지였다.
<Host name="admin.test.com" appBase="webapps\www.test.com" unpackWARs="true" autoDeploy="true" >
<Valve className="org.apache.catalina.~~~~" directory="logs"
prefix="admin.test.log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
<!-- 아래로 변경 -->
<Host name="admin.test.com" appBase="\" unpackWARs="true" autoDeploy="true" >
<Context path="" docBase="www.test.com" reloadable="true"/>
<Valve className="org.apache.catalina.~~~~" directory="logs"
prefix="admin.test.log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
appBase를 "\"으로 또는 ""으로 변경해주고
Context를 추가해서 docBase를 설정해주라고 해도 나는 해결되지 않았다,,,...
아마도 다른 분들의 경우와 동일한 경우가 아니였나보다.
다른 분들은 위 키워드로 찾아봐주셔도 해결이 될 수도 있을 것 같습니다.
일단 열심히 서칭한 결과 내가 운영하는? 유지보수하는? 관리하는?
서비스의 쿼츠가 2번 중복으로 실행되는 문제의 원인을 알아냈다.
<Engine name="Catalina" defaultHost="www.test.com">
<Host name="www.test.com" appBase="webapps\www.test.com" unpackWARs="true" autoDeploy="true" >
<Valve className="org.apache.catalina.~~~~" directory="logs"
prefix="www.test.log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
<Host name="admin.test.com" appBase="webapps\www.test.com" unpackWARs="true" autoDeploy="true" >
<Valve className="org.apache.catalina.~~~~" directory="logs"
prefix="admin.test.log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
위는 내가 운영하는 서비스의 tomcat server.xml 파일 일부 수정본이다.
우리 서비스는 각 서버네임에 따라서 실행시킬 프로젝트를 구분하고 있는데 그 중 2개의 서버네임이 동일한 appBase를 바라보고 있다.
이 말은 1개의 프로젝트를 war 파일로 배포했지만 2개의 host로, 인스턴스가 1개가 아닌 2개가 생성된 것이다.
쿼츠를 돌리면서 2개의 인스턴스가 생겨나면 무엇이 문제일까?
클라이언트의 호출을 받아 각 서버네임으로 별도의 추가 작업을 해주기 위해서 저렇게 host를 2개로 나눠서 관리를 해왔다.
아, 맨 위에서 쿼츠 중복 실행 문제가 없다는 A프로젝트는 별도 host이며 appBase는 다른 프로젝트(경로)를 바라보고 있다.
클아이언트의 호출을 받아 서버네임으로 분기되어 서비스가 작동될 때는 크게 문제가 되지 않아 보였지만
(문제를 해결하고 보니 서비스가 상당히 가벼워진 느낌!)
쿼츠가 실행될 때는 문제가 안될 수가 없었던 구조인 것이다.
쿼츠는 클라이언트 호출과는 상관없이 백그라운드에서 독립적으로 실행되는 작업으로,
내 경우의 문제는 ???
2개의 host로 설정된 2개의 인스턴스에서 독자적으로 각각 쿼츠가 실행된 것이다....
원인을 알았으니 해결을 해야한다.
quartz clustering 이라는 것을 찾아보았다. 열심히 찾았다. 해답은 얻지 못했다. 나에게는 어렵게 느껴졌고, 어렵고 ,,,, 좀 그랬다. 라는 말보다는 뭔가 이거보다 쉬운 방법이 있을 것 같았다!!
quartz clustering은 단순히 quartz의 중복 실행을 막기 위한 방법 같지가 않았다. 더 좋은, 우월한 녀석이라고 해야 할까.
host name을 얻어서 쿼츠를 1번만 돌리고 싶었다.
host name을 얻지 못했다. quartz는 백그라운드에서 작동되는 녀석으로 HttpServletRequest 사용을 권장하지 않았고
내가 방법을 찾지 못한 것인지 도저히 나로써는,,, host name을 얻을 수가 없었다.
tomcat 객체를 생성해보기도 하고 MBeanServer로 defaultHost를 얻으려 시도도 해봤는데 내가 얻은 defaultHost는 server.xml 파일에 직접 설정 해놓은 defaultHost로써 구분을 할 수 없는 명이었다.
엔진을 2개로 나눠서 설정을 해도 된다고도 하던데 그렇게 해보지는 않았다. 아, 엔진명을 얻을 수 있어 보여서 2개로 나눌까 하다가 그것도 현재 쿼츠를 돌리는 엔진명을 얻을 수 있는게 아니라 특정 엔진명으로 검색을 해서 host명을 조회할 수 있는 것 같았다.
나는 ,,, 당장 지금, 현재. 쿼츠를 돌리는 시점의 host name을 알고 싶었다.
결과는?
실패
다시 찾는다. 서칭한다.
한글로 검색했을 때 내가 볼거는 다 봤다,, 비슷비슷하다.
탐착치 않은 영어 실력으로 검색 시작.
검색 결과를 연다. 닫는다. 연다. 마음에 들지 않는다. 닫는다.
연다. 닫기를 반복하다가 제법 나의 상황과 비슷한 사람의 글을 발견했다. 그래서 그런가 조금 읽힌다.
답글도 읽어보자.
오? 마음에 드는 답글을 찾았다.
바로 반영해본다.
실패했다. 좌절.
생각한다. 될 것 같았는데, 안됐다.
답글 다시 본다. 흠.. 내가 잘못 반영했다.
다시 반영해서 쿼츠 실행 테스트.
결과는??
성공 !!
한번만 돌았다.
제법 간단해 보이는 해결책이었다.
<Engine name="Catalina" defaultHost="www.test.com">
<Host name="www.test.com" appBase="webapps\www.test.com" unpackWARs="true" autoDeploy="true" >
<Valve className="org.apache.catalina.~~~~" directory="logs"
prefix="www.test.log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
<Host name="admin.test.com" appBase="webapps\www.test.com" unpackWARs="true" autoDeploy="true" >
<Valve className="org.apache.catalina.~~~~" directory="logs"
prefix="admin.test.log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
<!-- 아래로 수정 -->
<Engine name="Catalina" defaultHost="www.test.com">
<Host name="www.test.com" appBase="webapps\www.test.com" unpackWARs="true" autoDeploy="true" >
<Alias>admin.test.com</Alias>
<Valve className="org.apache.catalina.~~~~" directory="logs"
prefix="www.test.log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
같은 appBase를 바라보고 있는 Host를 2개에서 1개로 줄였다.
하나를 Host name(server name)으로 설정하고 같은 appBase를 바라보아야 하는 다른 서버 네임을 Alias로 설정하였다.
의문을 갖었다.
www~ 서버네임과 admin~서버네임을 구분해서 서비스를 관리해야 하는데 Host로 설정하지 않은 Alias가 서버네임으로 분기가 될까 싶었다. 그래서 또 열심히 찾아보았다.
다행히도 Alias로 설정한 server name은 Host의 추가 Host 개념이며 클라이언트가 접속하는 서버네임으로 구분이 되었다.
HttpServletRequest request
String serverName = request.getServerName();
// www.test.com 호출
String serverName = request.getServerName();
// serverName = www.test.com
// admin.test.com 호출
String serverName = request.getServerName();
// serverName = admin.test.com
request로 getServerName()을 얻어오면 클라이언트가 호출한 서버네임(Alias로 지정한 서버네임 포함)을
각각 잘 구분해서 조회할 수 있는 것이다.
나의 경우에는 위와 같이 server.xml 파일을 수정해서 quartz 중복 실행 문제를 해결할 수 있었다.
같은 appBase를 바라보던, 인스턴스가 2개이던 이전의 서비스를 Host 1개에 Alias를 추가해서 동일 appBase를 1개로 추리고나니
quartz는 다행히도 1번만 실행이 되었다고 한다.
혹시나 나와 비슷한 상황에서 위와 같이 처리를 했는데 quartz가 여러번 돈다면
servlet-context.xml 파일 trigger 선언부에 아래 코드를 추가 해주면 된다.
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="testTrigger"/>
</list>
</property>
<property name="quartzProperties">
<props>
<!-- 다른 Quartz 속성들 -->
<prop key="org.quartz.threadPool.threadCount">1</prop>
</props>
</property>
</bean>
console을 확인해보면 bean#0 이던가 bean0#이던가 quartz 가 생성되고 quartz worker가
worker-1, worker-2, worker-3 ....... worker-10 까지 돌고 있을 수도 있다.
quartz 스레드는 기본적으로 10번 돌게 설정이 되어 있다고 한다.
그 기본 10번의 설정 값을 1번으로 설정해주면 trigger를 1번씩만 실행할 수 있게 된다.
이 작업은 quartz가 중복 실행되는 현상을 해결하는 근본적인 해답이 아님을 참고해야 한다.
열심히 정리를 하긴 했는데,. 잘 정리를 했는지.. 후.. 내일도 화이팅.
[아래는 내가 문제를 해결한 참고 링크]
'Java' 카테고리의 다른 글
java 리터럴이란? 기본적인 데이터 타입, 자료형의 종류와 함께 이해해보자. (2) | 2024.03.13 |
---|---|
대체 JVM이 무엇이기에 Java를 공부하는 나를 이토록 괴롭히는거니? (0) | 2024.03.12 |
quartz 중복 방지, quartz 중복 실행 해결, trigger 실행 카운트, 참고만해주세요 (0) | 2023.05.16 |
[ Spring Boot ] webapp > WEB-INF > views 구조로 JSP 연동, 이미지는 workspace인 webapp 바로 하위에 위치해야 불러 올 수 있다!! (0) | 2023.05.16 |
스프링으로 서비스 구동중 람다 표현식 사용시 오류 이슈 (0) | 2022.07.20 |