ShedLockμΌλ‘ λ€μ€ μΈμ€ν΄μ€ νκ²½μμ λ¨μΌ μ€μΌμ€λ¬ λμ 보μ₯νκΈ°
κ°μ
μ€νλ§μμ μ 곡νλ @EnableScheduling μ΄λ Έν μ΄μ μ κ°νΈνκ² μ€μΌμ€λ§ μμ μ μ€μ ν μ μκ² ν΄ μ£Όμ§λ§, λ€μ€ μΈμ€ν΄μ€ νκ²½μμλ λμΌν μμ μ΄ μ¬λ¬ λ² μ€νλ μ μλ€λ λ¬Έμ κ° μ‘΄μ¬ν©λλ€.
μ΄ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄μ λ€μν λ°©λ²μ΄ μμ§λ§, ShedLockμ΄λΌλ μ€νμμ€ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νλ©΄ μμ½κ² ν΄κ²°ν μ μμ΅λλ€. ShedLockμ μ€νλ§ μ€μΌμ€λ§κ³Ό ν¨κ» μ¬μ©λ μ μμΌλ©°, μ¬λ¬ μΈμ€ν΄μ€κ° λμΌν μ€μΌμ€λ§ μμ μ λμμ μ€ννλ κ²μ λ°©μ§ν΄ μ€λλ€.
ν μ€νΈ μ½λλ κΉνλΈμμ νμΈνμ€ μ μμ΅λλ€.
ShedLock μ΄ν΄νκΈ°
ShedLockμ λΆμ°λ μμ€ν νκ²½μμ λμΌν μ€μΌμ€λ§ μμ μ΄ μ€λ³΅μΌλ‘ μνλλ κ²μ λ°©μ§νλ λΌμ΄λΈλ¬λ¦¬μ λλ€. μ΄ λΌμ΄λΈλ¬λ¦¬λ μ§μ λ μμ μ μ κΈ λ©μ»€λμ¦μ μ μ©νμ¬, ν΄λΉ μμ μ΄ μμ ν μ’ λ£λ λκΉμ§ λ€λ₯Έ μΈμ€ν΄μ€μμλ λμΌν μμ μ μνν μ μκ² ν©λλ€.
ShedLockμ λ΄λΆ λμ λ°©μμ λ€μκ³Ό κ°μ΅λλ€:
- μ κΈ νλ: ShedLockμ λ°μ΄ν°λ² μ΄μ€, Redis, Zookeeper λ±μ μΈλΆ μ μ₯μλ₯Ό μ¬μ©νμ¬ μ κΈ μνλ₯Ό κ΄λ¦¬ν©λλ€. μ€μΌμ€λ§ μμ μ΄ μ€νλκΈ° μ μ ShedLockμ λ¨Όμ μΈλΆ μ μ₯μμμ ν΄λΉ μμ μ μ κΈ μνλ₯Ό νμΈν©λλ€. λ§μ½ ν΄λΉ μμ μ λν μ κΈμ΄ μ΄λ―Έ νλλμ΄ μλ κ²½μ°, λ€λ₯Έ μΈμ€ν΄μ€μμλ ν΄λΉ μμ μ μννμ§ μμ΅λλ€.
- μ κΈ μ μ§: μ€μΌμ€λ§ μμ μ΄ μμλλ©΄, μ κΈμ νλν μΈμ€ν΄μ€λ μμ μ μνν©λλ€. μ΄λ, ShedLockμ μ κΈμ μ μ§νμ¬ λ€λ₯Έ μΈμ€ν΄μ€μμ λμΌν μμ μ μ€λ³΅μΌλ‘ μ€ννμ§ λͺ»νλλ‘ ν©λλ€. μ κΈμ μΈλΆ μ μ₯μμ μ μ₯λλ©°, μΌμ μκ° λμ μ μ§λ©λλ€.
- μ κΈ κ°±μ : μ€μΌμ€λ§ μμ μ μ€ν μκ°μ΄ μ κΈμ μ μ§ μκ°λ³΄λ€ κΈΈ κ²½μ°, ShedLockμ μ κΈμ κ°±μ νμ¬ μμ μ΄ μλ£λ λκΉμ§ λ€λ₯Έ μΈμ€ν΄μ€μμ μμ μ μννμ§ λͺ»νλλ‘ ν©λλ€. μ΄λ₯Ό ν΅ν΄ μμ μ μ€λ³΅ μ€νμ λ°©μ§ν©λλ€.
- μ κΈ ν΄μ : μ€μΌμ€λ§ μμ μ΄ μλ£λλ©΄, ShedLockμ μ κΈμ μλμΌλ‘ ν΄μ ν©λλ€. μ κΈμ μΈλΆ μ μ₯μμμ μ κ±°λμ΄ λ€μ μ€ν μ£ΌκΈ°μμ λ€λ₯Έ μΈμ€ν΄μ€κ° μμ μ μνν μ μκ² λ©λλ€.
ShedLockμ λΆμ°λ νκ²½μμ μ€μΌμ€λ§ μμ μ μ€λ³΅ μ€νμ λ°©μ§νμ¬ μμ€ν μ μμ μ±κ³Ό μΌκ΄μ±μ 보μ₯ν©λλ€. μΈλΆ μ μ₯μλ₯Ό μ¬μ©νμ¬ μ κΈ μνλ₯Ό κ΄λ¦¬νλ―λ‘, μ¬λ¬ μΈμ€ν΄μ€ κ°μ μ κΈ μ 보λ₯Ό 곡μ ν μ μμ΅λλ€. μ΄λ₯Ό ν΅ν΄ λμΌν μμ μ΄ μ€λ³΅μΌλ‘ μ€νλλ κ²μ λ°©μ§νκ³ , μμ μ μ€ν μμμ μΌκ΄μ±μ μ μ§ν μ μμ΅λλ€.
ShedLock μ¬μ©νκΈ°
ShedLockμ λ€μν RDBMS, NOSQL, In-Memory DB λ± λ€μν λ°μ΄ν°λ² μ΄μ€μμ μ¬μ©λ μ μλλ°, ν μ€νΈλ MariaDBλ₯Ό μ¬μ©νλ νκ²½μμ μ§νν΄ λ³΄κ² μ΅λλ€.
μμ‘΄μ± μΆκ°
Gradleμ μλ λΌμ΄λΈλ¬λ¦¬λ₯Ό μΆκ°ν©λλ€.
implementation 'net.javacrumbs.shedlock:shedlock-spring:5.10.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.10.0'
lockProvider μ μ
λ€μμΌλ‘ Configλ₯Ό μ€μ ν©λλ€.
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-task-pool-");
scheduler.initialize();
taskRegistrar.setTaskScheduler(scheduler);
}
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.usingDbTime()
.build()
);
}
}
μμ μ½λμμ @EnableSchedulerLock(defaultLockAtMostFor = "PT30S")λ₯Ό ν΅ν΄ ShedLockμ μ€μΌμ€λ¬ μ κΈ κΈ°λ₯μ νμ±ννλ©°, defaultLockAtMostForλ κΈ°λ³Έ μ κΈμ μ΅λ μ μ§ μκ°μ μ€μ ν©λλ€.
lockProviderλ ShedLockμ΄ μ κΈ μνλ₯Ό κ΄λ¦¬νλ λ° μ¬μ©λλ beanμ λλ€. μ¬κΈ°μλ JdbcTemplateLockProviderλ₯Ό μ¬μ©νμ¬ λ°μ΄ν°λ² μ΄μ€ μ°κ²°μ κ΄λ¦¬νλ DataSourceλ₯Ό μΈμλ‘ λ°μ΅λλ€. μ΄λ₯Ό ν΅ν΄ ShedLockμ λ°μ΄ν°λ² μ΄μ€λ₯Ό μ΄μ©νμ¬ μ κΈ μνλ₯Ό κ΄λ¦¬ν μ μμ΅λλ€.
μμ μ ν μ€μ λ§κ³ λ JdbcTemplateLockProviderμ λ€μ΄κ° μ μλ μ΅μ λ€μ μλμ κ°μ΅λλ€.
- withJdbcTemplate(JdbcTemplate jdbcTemplate): JdbcTemplate κ°μ²΄λ₯Ό μ€μ .
- usingDbTime(): λ°μ΄ν°λ² μ΄μ€ μλ²μ μκ°μ μ¬μ©νλλ‘ μ€μ . μ΄ μ΅μ μ μ¬μ©νλ©΄ μ ν리μΌμ΄μ μλ²μ λ°μ΄ν°λ² μ΄μ€ μλ² κ°μ μκ° μ°¨μ΄λ₯Ό κ³ λ €νμ§ μμλ λλ€.
- withLockId(String lockId): μ κΈμ IDλ₯Ό μ€μ . λμΌν IDλ₯Ό κ°μ§ μ κΈμ λμΌν λ μ½λλ₯Ό μ¬μ©.
- withColumnNames(ColumnNameProvider columnNames): μ¬μ©ν ν μ΄λΈμ μ»¬λΌ μ΄λ¦μ μ€μ . ColumnNameProvider μΈν°νμ΄μ€λ₯Ό ꡬννμ¬ μ¬μ©.
- usingDbTime(ZoneId zone): λ°μ΄ν°λ² μ΄μ€ μλ²μ μκ°λλ₯Ό μ€μ . μ΄ μ΅μ μ usingDbTime()μ κ°μ΄ μ¬μ©λμ§λ§, νΉμ μκ°λλ₯Ό μ€μ ν μ μλ€.
- withTimeZone(TimeZone timeZone): λ°μ΄ν°λ² μ΄μ€μ μκ°λλ₯Ό μ€μ .
- withTableName(String tableName): μ κΈμ μ μ₯ν ν μ΄λΈ μ΄λ¦μ μ€μ .
- withIsolationLevel(IsolationLevel isolationLevel): νΈλμμ 격리 μμ€μ μ€μ .
- withTransactionManager(PlatformTransactionManager transactionManager): νΈλμμ κ΄λ¦¬μλ₯Ό μ€μ .
@SchedulerLock μ€μ
@Slf4j
@Component
public class Scheduling {
@Scheduled(cron = "*/5 * * * * *")
@SchedulerLock(name = "scheduled1", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
public void schedule2() {
log.info("Scheduled task executed at {}", LocalDateTime.now());
}
@Scheduled(cron = "*/5 * * * * *")
@SchedulerLock(name = "scheduled2", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
public void schedule2() {
log.info("Scheduled task executed at {}", LocalDateTime.now());
}
@Scheduled(cron = "*/5 * * * * *")
@SchedulerLock(name = "scheduled3", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
public void schedule3() {
log.info("Scheduled task executed at {}", LocalDateTime.now());
}
}
μμ μ½λλ @Scheduledμ @SchedulerLock μ΄λ Έν μ΄μ μ μ¬μ©νμ¬ μ€μΌμ€λ§ μμ μ μ μν μμ μ λλ€. @SchedulerLock μ΄λ Έν μ΄μ μλ λ€μκ³Ό κ°μ μμ±λ€μ΄ μμ΅λλ€.
- name (νμ): μ κΈμ μ΄λ¦μ μ§μ ν©λλ€. κ°μ μ΄λ¦μ κ°μ§ μ κΈμ λμΌν λ μ½λλ₯Ό μ¬μ©νλ©°, κ³ μ ν΄μΌ ν©λλ€.
- lockAtMostFor (νμ): μ κΈμ΄ μ΅λλ‘ μ μ§λλ μκ°μ μ§μ ν©λλ€. μ΄ μκ° μ΄νμλ μ κΈμ΄ μλμΌλ‘ ν΄μ λλ©°, μ΄ μμ±μ 'PT30S', '10m', '2h' λ±μ ISO 8601 νμμ λ¬Έμμ΄μ μ¬μ©νμ¬ μ§μ ν©λλ€. μ΄ κ°μ λ°λμ μμ μ μμ μ΅λ μ€ν μκ°λ³΄λ€ μ»€μΌ νλλ°, λ§μ½ μμ μ΄ μ΄ μκ°μ μ΄κ³Όνμ¬ μ€νλλ€λ©΄, λ€λ₯Έ μΈμ€ν΄μ€μμ μ κΈμ νλνκ³ λμΌν μμ μ μ€νν μ μμ΅λλ€.
- lockAtLeastFor(μ ν): μ κΈμ΄ μ΅μλ‘ μ μ§λλ μκ°μ μ§μ ν©λλ€. μ κΈμ΄ νλλ ν, μ΄ μκ° μ΄λ΄μλ μ κΈμ΄ ν΄μ λμ§ μμΌλ©°, μ΄ μμ± λν ISO 8601 νμμ λ¬Έμμ΄μ μ¬μ©νμ¬ μ§μ ν μ μμ΅λλ€. μ΄ κ°μ μ€μ νλ©΄, μμ μ΄ λΉ λ₯΄κ² μνλ νμλ μΌμ μκ° λμμ λ€λ₯Έ μΈμ€ν΄μ€μμ λμΌν μμ μ΄ μ€νλμ§ μλλ‘ ν μ μμ΅λλ€.
- mode(μ ν): μ κΈ λͺ¨λλ₯Ό μ€μ ν©λλ€. κΈ°λ³Έκ°μ 'IMMEDIATE'λ‘, μ΄ λͺ¨λμμλ μ κΈμ μ¦μ νλνλ €κ³ μλνλλ°, λ§μ½ μ κΈμ νλνμ§ λͺ»νλ©΄ μμ μ μ¦μ μ€ν¨ν©λλ€. λ€λ₯Έ μ΅μ μΌλ‘λ 'EAGER'κ° μμΌλ©°, μ΄ λͺ¨λμμλ μ κΈμ νλνκΈ° μν΄ μ§μ λ μκ° λμ λκΈ°ν©λλ€.
ShedLock ν μ΄λΈ μμ±
-- MySQL, MariaDB
CREATE TABLE shedlock
(
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
-- Postgres
CREATE TABLE shedlock
(
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP NOT NULL,
locked_at TIMESTAMP NOT NULL,
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
-- Oracle
CREATE TABLE shedlock
(
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL,
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
-- MS SQL
CREATE TABLE shedlock
(
name VARCHAR(64) NOT NULL,
lock_until datetime2 NOT NULL,
locked_at datetime2 NOT NULL,
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
-- DB2
CREATE TABLE shedlock
(
name VARCHAR(64) NOT NULL PRIMARY KEY,
lock_until TIMESTAMP NOT NULL,
locked_at TIMESTAMP NOT NULL,
locked_by VARCHAR(255) NOT NULL
);
μ€μ shedlockμ λ°μ΄ν°λ μμ κ°μ΄ μ μ₯λλλ°, μ¬κΈ°μ μ€μν μ¬μ€μ ShedLockμ μ€μΌμ€λ§ μμ μ΄ μ€νλ λλ§λ€ μλ‘μ΄ λ μ½λλ₯Ό μμ±νλ κ²μ΄ μλ, ν΄λΉ μμ μ μ κΈ λ μ½λλ₯Ό μ λ°μ΄νΈνλ€λ μ μ λλ€. μ΄λ λ°μ΄ν°λ² μ΄μ€μ λΆνλ₯Ό μ€μ΄λ λ° λμμ΄ λλ©°, λμμ μνλλ μμ μ ν¨κ³Όμ μΌλ‘ κ΄λ¦¬ν μ μμ΅λλ€.(λ°λΌμ μμ μ μ€ν λ‘κ·Έλ₯Ό κΈ°λ‘νλ €λ©΄ λ³λλ‘ λ³λμ λ‘κΉ μμ€ν μ ꡬμΆνκ±°λ, μ€μΌμ€λ§ μμ λ΄μμ λ‘κ·Έλ₯Ό κΈ°λ‘νλλ‘ μ½λλ₯Ό μμ±ν΄μΌ ν©λλ€.)
μλ¬ ν΄κ²° κ³Όμ
μ¬κΈ°λΆν°λ μ κ° ShedLockμ μ μ©νλ©΄μ κ²ͺμλ μλ¬μ λν΄ μ 리ν©λλ€.
1. μμ‘΄μ± λ²μ μλ¬
νλ‘μ νΈμ JDK λ²μ κ³Ό ShedLock λΌμ΄λΈλ¬λ¦¬μ λΉλλ JDK λ²μ μ΄ μΌμΉνμ§ μμ λ°μνλ μλ¬μ λλ€. μ΄ κ²½μ°, νλ‘μ νΈμ JDK λ²μ μ λ§λ ShedLock λΌμ΄λΈλ¬λ¦¬μ λ²μ μ μ¬μ©ν΄μΌ ν©λλ€. μλ₯Ό λ€μ΄, JDK 17 λ²μ μ΄μ μ μ¬μ©νλ κ²½μ° ShedLock λΌμ΄λΈλ¬λ¦¬ v4.44.0μ μ¬μ©ν΄μΌ ν©λλ€. μ΄ μ 보λ ShedLockμ READMEμ VERSION λͺ©μ°¨μμ νμΈν μ μμ΅λλ€.
κΉνλΈλ₯Ό 보면 JDK 17 λ²μ μ΄μ μ v4.44.0, μ΄νλ v5.1.0μ μ¨μΌ νλ€κ³ λͺ μλμ΄ μμ΅λλ€.
2. shedlock ν μ΄λΈ μ°Ύμ μ μμ
ERROR 14:15:00 [scheduled-task-1] n.j.s.p.j.JdbcTemplateStorageAccessor, insertRecord at line 73; Unexpected exception
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [INSERT IGNORE INTO shedlock(name, lock_until, locked_at, locked_by) VALUES(?, TIMESTAMPADD(MICROSECOND, ?, UTC_TIMESTAMP(3)), UTC_TIMESTAMP(3), ?)]; SQL state [3D000]; error code [1046]; No database selected; nested exception is java.sql.SQLException: No database selected
at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1542)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:667)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:981)
...
λ°μ΄ν°λ² μ΄μ€κ° shedlock ν μ΄λΈμ μ°Ύμ§ λͺ»ν΄ λ°μνλ μλ¬μ λλ€. μ΄ κ²½μ°, DataSource μ΄κΈ°ν μμ μ€ν€λ§λ₯Ό μ§μ ν΄ μ£Όμ΄μΌ ν©λλ€. μλ₯Ό λ€μ΄, jdbc:mysql://localhost:3306/my_schemaμ κ°μ΄ μ§μ ν μ μμ΅λλ€.
νμ§λ§, λ κΉλν ν΄κ²° λ°©λ²μ ShedLock μ€μ μμ withTableNameλ©μλλ₯Ό μ¬μ©νμ¬ μ€ν€λ§μ ν μ΄λΈ μ΄λ¦μ μ§μ νλ κ²μ λλ€. λ€μκ³Ό κ°μ΄ μ€μ ν μ μμ΅λλ€.(곡μλ¬Έμμ Configure LockProvider λΆλΆμ μμΈν λμμμ΅λλ€.)
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withTableName("schema.shedlock")
.withJdbcTemplate(new JdbcTemplate(dataSource))
.usingDbTime()
.build()
);
}
μ μ€μ μμ "schema.shedlock"μ {μ€ν€λ§}.{ν μ΄λΈλͺ }μ ννλ‘ μ§μ λμ΄ μμ΅λλ€. μ΄λ κ² νλ©΄, νΉμ μ€ν€λ§ λ΄μ shedlock ν μ΄λΈμ μ νν μ°Ύμ μ¬μ©ν μ μμ΅λλ€.