카테고리:

업데이트:

1. 배경

현재 트리플콤마의 배치 서버는 하나의 서버와 함께 Blue-Green 배포 방식을 사용하고 있습니다.

단일 서버이기 때문에 평소에는 문제가 없지만 배포하는 경우 일시적으로 서버가 두 개 생길 수 있는데 바로 다음과 같은 경우입니다.

이렇게 되는 경우 각 배치 서버에서 실행되고 있는 스케줄러가 중복으로 실행되는 가능성이 존재하게 되는데 제가 이번 스프린트에서 맡은 알림 서비스의 경우 동일한 유저에게 동일한 알림이 중복되어 발송될 수 있습니다.

따라서 이러한 문제점을 해결하기 위해 Shedlock을 도입했습니다.

2. Shedlock

Shedlock이란, 예약된 작업이 여러 인스턴스에서 중복 실행되지 않도록 하는 Lock입니다.

만약, 특정 작업을 한 서버에서 실행 중이면 다른 서버는 실행을 기다리지 않고 무시합니다. 이러한 Lock은 JDBC 데이터베이스나 Redis, Mongo 등 기타 외부 저장소를 사용할 수 있습니다.

3. Usage

  • Dependency

      implementation 'net.javacrumbs.shedlock:shedlock-spring:${shedlock_version}'
    


  • Configuration

      @EnableSchedulerLock(
          defaultLockAtLeastFor = "60s",    // lock이 설정되어야 하는 최소 시간 (디폴트)
          defaultLockAtMostFor = "120s"     // lock이 설정되어야 하는 최대 시간 (디폴트)
      )
    


  • LockProvider 생성 (Bean 등록)

    LockProvider는 여러 구현체가 있습니다.

    그 중, 가장 많이 사용하는 두 구현체는 다음과 같이 사용할 수 있습니다.

    1. jdbcTemplateLockProvider

      • 테이블 생성

          # 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);
        
      • Dependency

          implementation("net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.8.0")
        
          ... (기타 DBMS 의존성)
        
      • Bean 설정

          @Bean
          public LockProvider lockProvider(DataSource dataSource) {  
              return JdbcTemplateLockProvider(  
                  JdbcTemplateLockProvider.Configuration.builder()  
                      .withJdbcTemplate(JdbcTemplate(dataSource))  
                      .usingDbTime()  // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2  
                      .build()  
              )  
          }
        


    2. RedisLockProvider

      • Dependency

          implementation 'net.javacrumbs.shedlock:shedlock-provider-redis-spring:${shedlock_version}'
        
      • Redis 설정

          @Bean
          public LettuceConnectionFactory redisConnectionFactory() {
              return new LettuceConnectionFactory();
          }
        
          @Bean
          public RedisTemplate<String, String> redisTemplate() {
              RedisTemplate<String, String> template = new RedisTemplate<>();
              template.setConnectionFactory(redisConnectionFactory());
              return template;
          }
        
      • Bean 설정

          @Bean
          public LockProvider lockProvider(RedisTemplate<String, String> redisTemplate) {
              return new RedisLockProvider(redisTemplate);
          }
        


  • Scheduler Lock 설정

      @Scheduled(cron = "0 0 * * * ?")
      @SchedulerLock(name = "scheduledTaskName", lockAtLeastFor = "15s", lockAtMostFor = "30s")
      public void scheduledTask() {
          // Your scheduled task logic here
      }
    

4. JdbcTemplateLockProvider vs RedisLockProvider

Usage 중간 부분에서 LockProvider의 구현체를 두 가지 언급했습니다.

바로 JdbcTemplateLockProviderRedisLockProvider인데 둘의 차이는 다음과 같습니다.

  • JdbcTemplateLockProvider
    • ShedLockd을 위한 전용 테이블 필요
    • SQL 명령어 (INSERT, UPDATE, SELECT)를 통해 Lock 관리
    • 기존 관계형 DB 인프라를 그대로 이용 가능
    • DB 성능에 의존, 높은 빈도의 Lock/Unlock의 경우 부하 발생 가능성


  • RedisLockProvider
    • Redis 설정이 필요
    • Redis 명령어 (SETNX, EXPIRE, DEL)를 통해 Lock 관리
    • 높은 성능과 빠른 응답 시간, 분산 환경에서 일관성 보장
    • ShedLock만을 사용하기 위해 Redis를 구축하는 것은 비추


이처럼 두 Provider에서도 이러한 차이가 있고 다른 Provider도 마찬가지입니다.

따라서 여러 구현체들 중 본인의 프로젝트 환경에 맞게 사용하면 됩니다.

Reference

            
              📕 개인 기록용 블로그입니다.
              😊 오타나 잘못된 정보가 있을 경우 댓글이나 메일로 말씀해주시면 바로 수정하겠습니다! 😊
          

댓글남기기