728x90
반응형
728x90
반응형
728x90
반응형

나는 개인 싱글서버로 웹서버를 개발중이다.

그런데 어제 스케줄 실행에서 로그를 기록하는 것을 서버에 적용한 후 스케줄이 중복 실행되는 걸 발견했다.

큰 이상을 유발할 수 있는 상황은 아니었으나 그래도 깔끔하지 못하니 수정하고 싶었다.

 

검색을 해보니 Tomcat, spring 설정의 문제를 확인해 보라는 내용이 있었지만, 내 경우는 적용할 수 가 없었다.

(이건 내 지식이 얕기 때문에 이해를 제대로 하지 못해 적용하지 못했을 것이다.)

 

추가로 검색을 해보니 멀티 서버의 경우에서 스케줄 중복을 방지하는 방법을 발견했다.

넓게 본다면 비슷한 의도로 중복실행 방지를 가능하게 처리하는 방법이 될 것으로 생각하고 적용 했으며, 잘 반영 되었다.

 

* 참고한 좋은글

 

ShedLock 사용하기

www.baeldung.com/shedlock-spring 같은 잡을 수행하는 각각 다른 서버에서의 인스턴스 A,B가 있을 때, A,B 둘 중 하나가 수행하도록, 2개 이상의 서버에서 중복 수행을 방지하도록 Lock 을 걸게 하는 라이브

eunbc-2020.tistory.com

 

 

* 내 서버 환경 : spring boot 2.4.3, 스케줄 적용 중

 

자세한건 위 글을 참고하시고, 내 기준에서 추가로 보강한 내용만 적는다.

 

1. 테이블 생성 (mysql)

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS `bookkeeping`.`SHEDLOCK` (
  `NAME` VARCHAR(64NOT NULL COMMENT '스케줄잠금이름',
  `LOCK_UNTIL` TIMESTAMP(3NULL COMMENT '잠금기간',
  `LOCKED_AT` TIMESTAMP(3NULL COMMENT '잠금일시',
  `LOCKED_BY` VARCHAR(255NULL COMMENT '잠금신청자',
  PRIMARY KEY (`NAME`))
ENGINE = InnoDB
COMMENT = '스케줄 잠금'
cs

 

2. Bean 주입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
 
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
 
@Configuration
public class SchedulerConfig {
 
    @Bean
    public LockProvider lockProvider(JdbcTemplate dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }
}
cs

 

이상.

오늘도 잘 해결해서 보람차다. ㅠㅠ 

 

 

3. 21.11.24 추가 이슈.

스케줄 실행시 언제나 에러가 난다는 것이다.

스케줄의 name 값으로 select를 먼저하고, 확인해서 insert, update로 분기해서 진행해야 하는데 무조건 insert 후 에러가 나면 update를 하는거다.(어쨌든 에러가 나도 실행은 잘 되긴 한다.)

하지만 이 excption이 눈에 거슬려서 고쳐보려 했지만 해결 방법을 아직 찾지는 못했다.

* 에러 전문

1. INSERT INTO shedlock(name, lock_until, locked_at, locked_by) VALUES('job_10min', '11/22/2021 12:28:00.003', '11/22/2021 12:20:00.050', 'DESKTOP-VMFC4B1')

{FAILED after 16 msec}

java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'job_10min' for key 'PRIMARY'

at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)

at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1092)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1040)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1347)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1025)

at net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy.executeUpdate(PreparedStatementSpy.java:1080)

at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)

at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)

at org.springframework.jdbc.core.JdbcTemplate.lambda$update$2(JdbcTemplate.java:965)

at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651)

at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960)

at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:981)

at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:328)

at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:333)

at net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateStorageAccessor.lambda$insertRecord$0(JdbcTemplateStorageAccessor.java:65)

at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)

at net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateStorageAccessor.insertRecord(JdbcTemplateStorageAccessor.java:63)

at net.javacrumbs.shedlock.support.StorageBasedLockProvider.doLock(StorageBasedLockProvider.java:81)

at net.javacrumbs.shedlock.support.StorageBasedLockProvider.lock(StorageBasedLockProvider.java:65)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)

at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)

at com.sun.proxy.$Proxy61.lock(Unknown Source)

at net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor.executeWithLock(DefaultLockingTaskExecutor.java:63)

at net.javacrumbs.shedlock.spring.aop.MethodProxyScheduledLockAdvisor$LockingInterceptor.invoke(MethodProxyScheduledLockAdvisor.java:85)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at packageName.core.schedule.cron.ScheduleCron$$EnhancerBySpringCGLIB$$5253d941.job_10min()

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)

at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)

at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95)

at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)

at java.util.concurrent.FutureTask.run(Unknown Source)

at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(Unknown Source)

at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at java.lang.Thread.run(Unknown Source)

 

 

 

728x90
반응형
728x90
반응형

특정 url을 특정서버 예를들면 운영서버에서는 사용하고 싶지 않았다.

하지만 그렇다고 주석처리는 싫다! 왜냐하면 그때 그때 주석처리를 하고 안하고 하면 실수가 있기 때문이다.

 

그래서 생각한 것은 아래와 같다.

1. application.properties의 값을 이용해 on/off 하는 방법을 찾는다.

2. 기존에 테스트서버와 운영서버 war를 동일한 파일로 했었는데 파일명을 다르게 하여 구분한다.

 

내 개발환경은 간단하게 아래와 같다.

1. spring boot 2.4.3

2. java 1.8

3. gradle 6.8.3

 

이제부터 적용한 내용이다!

시작하기에 앞서 application.properties는 서버별로 분리되어야 한다. (아래 링크 참조)

 

spring boot profile 설정 파일을 분리해 봅시다.

 spring으로 crud 하기 전에, 필요한 지식들을 먼저 보고 가겠습니다. spring boot 프로젝트를 보면, 왠 properties 파일들을 보게 됩니다. 이들은, 셋팅 파일인데요. 환경에 따라서, 이 값을 다르게 하고

codingdog.tistory.com

 

1. Controller 활성화, 비활성화 : @ConditionalOnExpression

 

Can a spring boot @RestController be enabled/disabled using properties?

Given a "standard" spring boot application with a @RestController, eg @RestController @RequestMapping(value = "foo", produces = "application/json;charset=UTF-8") public class MyController { @

stackoverflow.com

 

내가 적용한 샘플은 아래와 같다.

1) Controller에 @ConditionalOnExpression 사용 (4번라인)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
 
@ResController
@ConditionalOnExpression("${my.controller.enabled:false}")
public class SampleContoroller {
    
    @GetMapping(value = "/callUrl1")
    public String call1() {
        ....
    }
 
    @GetMapping(value = "/callUrl2")
    public String call2() {
        ....
    }
    
}
 
cs

2) 각각의 application.properties에 필요에 따라 my.controller.enabled 값 설정

1
2
# controller enabled/disabled : true/false or remove
my.controller.enabled=true
cs

이렇게 하면 설정에 맞춰 해당 컨트롤러를 활성화 할 수 있다.

 

2. war 파일 배포시 실수를 줄이기 위해 테스트서버, 운영서버 용 파일을 구분한다.

이것을 위해 application.properties의 spring.profiles.active 값을 build.gradle에서 읽어 war 파일명에 넣어 해결한다.

 

How to use spring properties in gradle.build?

How can I use properties configured in resources/application.properties in gradle.build? I would like to get something like this : flyway { url = MAP_WITH_PROPERTIES['spring.datasource.url'] ...

stackoverflow.com

 

1) build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// read application.properties values
import java.util.Properties
def props = new Properties()
file('src/main/resources/application.properties').withInputStream {
    props.load(it)
}
def active = props['spring.profiles.active']
 
....
 
// war file name custom
version = '0.0.1-SNAPSHOT' +'-' + active
 
....
cs

 

이렇게 함으로서 의도했던 내용을 모두 반영했다.

 

추가로, timestemp를 war 파일명에 넣기

 

Add a time stamp to the JAR file name in Gradle

Java, gradle, jar

linuxtut.com

 

 

728x90
반응형

+ Recent posts