1. ๊ฐ์
Spring @Scheduled ๋ ๋ ํ๋ฆฌ์นด๊ฐ ๋์ด๋ฉด ๋ ๋ค ๋๋ค. ShedLock ์ ๊ณต์ ์ ์ฅ์(Redis, DB)์ ๋ฝ์ ๊ฑธ์ด ํ ๋ฒ๋ง ๋๊ฒ ํ๋ค. ์ด๋
ธํ
์ด์
ํ๋๋ก ๋๋์ ์ข์๋ฐ, โ์กฐ์ฉํโ ๋ผ๋ ๋จ์ด๊ฐ ์๊พธ ๋ฐ๋ผ๋ถ๋๋ค. ๋ฝ์ด ์์ผ๋ฉด ๋ณธ๋ฌธ์ ์กฐ์ฉํ ๊ฑด๋๋ด๋ค. ์ด๊ฒ ์ค๊ณ ์๋์ด๊ณ , ํ
์คํธ์ ๋ง๋๋ฉด ํจ์ ์ด ๋๋ค.
2. ํต์ฌ ๋ด์ฉ
2-1. ๊ธฐ๋ณธ ๋์
@Scheduled(cron = "0 * * * * *")
@SchedulerLock(name = "expireReservations", lockAtMostFor = "PT10M", lockAtLeastFor = "PT30S")
public void expireReservations() { ... }- ์คํ ์ ์
name์ผ๋ก ๋ฝ์ ์๋ํ๋ค. ์ด๋ฏธ ์์ผ๋ฉด ๋ณธ๋ฌธ์ ์คํํ์ง ์๊ณ ์ ์ ์ข ๋ฃ ํ๋ค. ์์ธ ์์. ๋ก๊ทธ๋ DEBUG. lockAtMostFor: ์ก์ด ์ฃฝ์ด๋ ์ด ์๊ฐ ๋ค์ ๋ฝ์ด ํ๋ฆฐ๋ค. ์คํ ์๊ฐ์ ์ํ๋ณด๋ค ๋๋ํ๊ฒ.lockAtLeastFor: ์คํ์ด ๋นจ๋ฆฌ ๋๋๋ ์ด ์๊ฐ์ ๋ฝ์ ์ ์งํ๋ค. ๋ ํ๋ฆฌ์นด ๊ฐ ์๊ณ ์ฐจ์ด๋ก ๊ฐ์ cron ์ด ๋ ๋ฒ ๋๋ ๊ฒ์ ๋ง๋๋ค.
2-2. ํจ์ 1: ํ ์คํธ๊ฐ ํ๋ก์ ๋น์ ์ง์ ํธ์ถํ๋ค
ํตํฉํ
์คํธ๊ฐ ๊ฐํ์ ์ผ๋ก ์คํจํ๋ค. expected: EXPIRED but was: WAITLISTED. ์ฌ์คํํ๋ฉด ํต๊ณผ. ์ธ ๊ฒน์ด์๋ค.
- ํ
์คํธ ํ๋กํ์ผ์์
@EnableScheduling์ด ๋ ๊ณณ์ ์์ด ํ์ชฝ๋ง ๊บผ๋ ์ง์ง cron ์ด ์ค์ํธ ์ค์ ๋๋ค. - ๊ทธ cron ์ด
lockAtLeastFor = PT30S์ง๋ฆฌ ๋ฝ์ ์ก๋๋ค. ์คํ์ 3ms ์ง๋ง ๋ฝ์ 30์ด. - ํ
์คํธ๊ฐ
@Autowired๋ก ๋ฐ์ ์ค์ผ์ค๋ฌ ๋น์ ShedLock ํ๋ก์ ๋ค. ๊ทธ 30์ด ์์ ํ ์คํธ๊ฐscheduler.expireReservations()๋ฅผ ๋ถ๋ฅด๋ฉด ํ๋ก์๊ฐ โ๋ฝ ์์โ ์ผ๋ก ๋ณธ๋ฌธ์ ๊ฑด๋๋ด๋ค.
ํ ์คํธ๋ ๋ณธ๋ฌธ์ด ๋์๋ค๊ณ ๋ฏฟ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋จ์ธํ๋ค. ๋ณธ๋ฌธ์ ์ ๋์๋ค. ์คํจ. ํ๋ฅ ์ 30์ด ์ฐฝ / ์ค์ํธ ๊ธธ์ด โ 10%. ๋ฑ โ๊ฐ๋โ.
// ํ๋ก์๋ฅผ ๋ฒ๊ฒจ ๋ณธ๋ฌธ์ ์ง์ ํธ์ถ
ReservationScheduler target = AopTestUtils.getTargetObject(schedulerBean);
target.expireReservations();๋๋ ํ
์คํธ ํ๋กํ์ผ์์ @EnableScheduling ์ ํ์คํ ๋๊ณ (๋ ๊ณณ ๋ชจ๋), ShedLock ์ @Profile("!test") ๋ก ๋บ๋ค. ๊ฒฐ์ ์ ์ฌํ์ Redis ์ ๋ฝ ํค๋ฅผ ๋ฏธ๋ฆฌ ์ฌ๋ ๊ฒ.
๋ฐฐ์ด ๊ฒ: ์คํจํ์ง ์๊ณ ์คํ๋์ง ์๋ ์ฝ๋ ๋ catch ์๋ ๋ก๊ทธ์๋ ์ ๋จ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฒ์ ์ธ์ด โ์ฟผ๋ฆฌ ๊ฒฝํฉโ ๊ฐ์ค์ ํ๋ ธ๋ค. ํ๋ณด ํ์ด ํญ์ 1๊ฑด์ธ ๊ฑธ ํ์ธํ๊ณ ๋์์ผ ๊ฐ์ค์ ๋ฒ๋ ธ๋ค.
2-3. ํจ์ 2: ๋์ ์ก์๋ ์ด๋ ธํ ์ด์ ์ด ์ ๊ฑธ๋ฆฐ๋ค
SchedulingConfigurer.addTriggerTask() ๋ก ๋ฑ๋กํ ์ก(์ฃผ๊ธฐ๋ฅผ ๋ฐํ์์ ๋ฐ๊พธ๋ ์ก)์ AOP ํ๋ก์๋ฅผ ๊ฑฐ์น์ง ์๋๋ค. @SchedulerLock ์ ๋ถ์ฌ๋ ์๋ฌด ์ผ์ด ์ ์ผ์ด๋๋ค. ์กฐ์ฉํ. ๋ ํ๋ฆฌ์นด ๋์ด ๋ ๋ค ๋๋ค.
// ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ๋ฝ
lockingTaskExecutor.executeWithLock(
Runnable { syncCalendars() },
LockConfiguration(Instant.now(), "iCalSync", Duration.ofMinutes(10), Duration.ofSeconds(30))
)DefaultLockingTaskExecutor ๋ฅผ ๋น์ผ๋ก ๋๊ณ ์ง์ ๊ฐ์ผ๋ค. ์ด๋
ธํ
์ด์
์ด ์ ๊ฑธ๋ฆฌ๋ ๊ณณ์ ์ด๊ฒ๋ฟ์ด ์๋๋ค. ๊ฐ์ ํด๋์ค ์์์ this.method() ๋ก ๋ถ๋ฅด๋ ๊ฒ๋ ํ๋ก์๋ฅผ ์ ํ๋ค. @Transactional ๊ณผ ๊ฐ์ ํจ์ .
2-4. ํจ์ 3: lockAtLeastFor ๋ฅผ ๊ธธ๊ฒ ์ก์ผ๋ฉด ์๋ ์คํ์ด ๋งํ๋ค
๊ด๋ฆฌ์ ํ๋ฉด์ โ์ง๊ธ ๋๊ธฐํโ ๋ฒํผ์ด ์๋ค. ์ด ๋ฒํผ์ด ์ค์ผ์ค๋ฌ์ ๊ฐ์ ๋ฝ ์ด๋ฆ์ ์ฐ๋ฉด, cron ์ด ๋ฐฉ๊ธ ๋์ lockAtLeastFor ์ฐฝ ์์ด๋ฉด ๋ฒํผ์ด ์กฐ์ฉํ ์๋ฌด๊ฒ๋ ์ ํ๋ค. ์ฌ์ฉ์๋ โ๋ฒํผ์ด ๊ณ ์ฅ๋ฌ๋คโ.
์ ํ์ง๋ ๋์ด๋ค. ์๋ ์คํ์ ๋ค๋ฅธ ๋ฝ ์ด๋ฆ์ ์ฐ๊ฑฐ๋(๊ทธ๋ฌ๋ฉด cron ๊ณผ ๊ฒน์น ์ ์๋ค), ๋ฝ ํ๋ ์คํจ๋ฅผ ์ฌ์ฉ์์๊ฒ ์๋ฆฐ๋ค. executeWithLock ์ ๋ฐํ๊ฐ์ด๋ LockProvider.lock() ์ Optional ์ด ๋น์๋์ง ๋ณด๊ณ โ๋ฐฉ๊ธ ๋๊ธฐํ๋์ต๋๋ค. ์ ์ ํ ๋ค์โ ๋ฅผ ๋์ ๋ค.
2-5. ํจ์ 4: ๋ฝ ์ ์ฅ์๊ฐ ์ฃฝ์ผ๋ฉด
Redis ๋ฅผ ๋ฝ ์ ์ฅ์๋ก ์ฐ๋ฉด Redis ๊ฐ ์ฃฝ์์ ๋ @SchedulerLock ๋ฉ์๋๋ ์์ธ๋ก ์คํจ ํ๋ค. ๋ฝ์ ๋ชป ์ก์ผ๋ฉด ์คํํ์ง ์๋ ๊ฒ์ด ShedLock ์ ์์ ๋ฐฉํฅ์ด๋ผ ๋ง๋ค. ๊ทธ๋ฐ๋ฐ Redis ํด๋ผ์ด์ธํธ์ ํ์์์์ด ๊ธฐ๋ณธ 60์ด๋ฉด, cron ์ค๋ ๋๊ฐ 60์ด๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค. ์ค์ผ์ค๋ฌ ์ค๋ ๋ ํ์ด ์์ผ๋ฉด ๋ค๋ฅธ ์ก๋ ๋ฐ๋ฆฐ๋ค. Redis ํ์์์์ ์งง๊ฒ(3์ด).
2-6. ๋ช ๊ฐ๊น์ง ๋๋
ํ ํ๋ก์ ํธ์ @SchedulerLock ์ด 15๊ณณ์ด๋ค. ์์ฝ ๋ง๋ฃ, ๋
ธ์ผ ์ฒ๋ฆฌ, ์๋ฆผ ๋ฆฌ๋ง์ธ๋, ์ธ๋ถ ๋ฐ์ดํฐ ๋๊ธฐํ, ํ์ผ ์ ๋ฆฌ, ํํด ๋ฐ์ดํฐ ํผ์ง, ๋ฐฑํ ๋ฌ๋. ๋ฝ ์ด๋ฆ์ด 15๊ฐ๋ค. ์ด๋ฆ์ด ๊ฒน์น๋ฉด ์๋ก ๋ง๋๋ค. ์ด๋ฆ ๋ชฉ๋ก์ ํ enum ์ด๋ ์์ ํด๋์ค์ ๋ชจ์ ์ค๋ณต์ ์ปดํ์ผ ์์ ์ ์ก๋ ํธ์ด ๋ซ๋ค.
๋ฐฑํ์ฒ๋ผ ํ ๋ฒ ๋๊ณ ๋๋ ์ก์ ํฌ ์ค์์น๋ฅผ ๋ฐ๋ก ๋๋ค. Redis ์ ํ๋๊ทธ ํค๋ฅผ ๋๊ณ ์ก์ด ์์ํ ๋ ์ฝ๋๋ค. ์ก์ด ์๋ชป ๋๊ธฐ ์์ํ์ ๋ ์ฌ๋ฐฐํฌ ์์ด ๋ฉ์ถ ์ ์๋ค. dry-run ๋ชจ๋๋ฅผ ๋จผ์ ํ ๋ฐํด ๋๋ฆฌ๊ณ ์ผ๋ 2๋จ๊ณ.
3. ๋ง๋ฌด๋ฆฌ
์์ฝ
- ShedLock ์ ๋ฝ์ด ์์ผ๋ฉด ๋ณธ๋ฌธ์ ์กฐ์ฉํ ๊ฑด๋๋ด๋ค. ์์ธ๋ ๋ก๊ทธ๋ ์๋ค.
- ํ ์คํธ๊ฐ ํ๋ก์ ๋น์ ๋ถ๋ฅด๋ฉด ์ง์ง cron ์ ๋ฝ ์ฐฝ์์ ์คํต๋๋ค.
AopTestUtils.getTargetObject๋ก ๋ฒ๊ธด๋ค.- ๋์ ์กยท
this.ํธ์ถ์ ์ด๋ ธํ ์ด์ ์ด ์ ๊ฑธ๋ฆฐ๋ค.LockingTaskExecutor๋ก ์ง์ .- ์๋ ์คํ ๋ฒํผ์ ๋ฝ ํ๋ ์คํจ๋ฅผ ์ฌ์ฉ์์๊ฒ ๋งํด์ผ ํ๋ค.
- ๋ฝ ์ ์ฅ์ ํ์์์์ ์งง๊ฒ. ๋ฝ ์ด๋ฆ์ ํ ๊ณณ์ ๋ชจ์ ์ค๋ณต์ ์ก๋๋ค.