[Spring] MSA 아키텍처에서 Redis를 활용한 분산락 적용을 Spring AOP를 활용해 재사용성 높게 적용하는 방법

가오리·2024년 2월 17일
1

BackEnd

목록 보기
1/13
post-thumbnail

build.gradle 라이브러리 추가

// Redis
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.redisson:redisson-spring-boot-starter:3.17.7'

Redis 분산락 설정

@Configuration
public class RedissonConfig {
	// redis 위치 지정
    @Value("${spring.cache.redis.host}")
    private String redisHost;
    @Value("${spring.cache.redis.port}")
    private int redisPort;

    private static final String REDISSON_HOST_PREFIX = "redis://";

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress(REDISSON_HOST_PREFIX + redisHost + ":" + redisPort);
        return Redisson.create(config);
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisHost, redisPort);
    }

	// 캐시를 위한 설정
    @Bean
    public CacheManager cacheManager() {
        RedisCacheManager.RedisCacheManagerBuilder builder =
                RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory());
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(
                                new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
                .entryTtl(Duration.ofMinutes(30)); // 캐시 수명 30분
        builder.cacheDefaults(configuration);
        return builder.build();
    }
}

분산락 어노테이션 정의

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
    String value() default "lock";
}
  • @Target : 해당 Annotation 사용 가능한 대상을 지정 (필수)
  • @Retention : 해당 Annotation이 적용되고 유지되는 범위 설정
    • RetentionPolicy.RUNTIME : JVM 환경에서 실제로 사용하기 위해 필수로 설정

분산락 Aspect 구현

@Aspect
@Component
public class DistributedLockAspect {

    private final RedissonClient redissonClient;

    @Autowired
    public DistributedLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Around("@annotation(distributedLock)")
    public Object lockAround(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        RLock lock = redissonClient.getLock(distributedLock.value());

        try {
            boolean isLocked = lock.tryLock(1, 3, TimeUnit.SECONDS);
            if (!isLocked) {
        		System.out.println("lock 획득 실패");
        		return;
			}
    	} catch (InterruptedException e) {
    		throw new RuntimeException(e);
		} finally {
    		if (lock.isLocked() && lock.isHeldByCurrentThread()) {
        		lock.unlock();
    		}
		}
	}
}

분산락 AOP로 적용

@DistributedLock
public void createResponse(@RequestBody SurveyResponseDto surveyForm) {
	surveyService.createSurveyAnswer(surveyForm);
}

결론

Spring AOP로 분산락을 정의해놓고 분산락이 필요한 곳에 어노테이션으로 가져다 쓰면 매우 편리하다.

분산 락(Distributed Lock)은 여러 서비스나 여러 인스턴스가 동시에 같은 리소스(예: 데이터베이스)에 접근하려 할 때 동시성 문제를 해결하기 위해 사용됩니다. 분산 락을 사용하면 한 번에 하나의 서비스만 해당 리소스를 수정할 수 있게 되어 데이터의 일관성을 보장할 수 있습니다.

각 서비스가 자체 데이터베이스를 가지고 있고, 서비스 간에 데이터 공유가 없는 경우에는 분산 락은 필요하지 않을 수 있습니다.

동일한 서비스의 여러 인스턴스가 동일한 데이터베이스에 접근하려고 할 때에는 로컬 락이나 데이터베이스의 트랜잭션 기능을 사용하여 동시성을 제어할 수 있습니다.

profile
가오리의 개발 이야기

0개의 댓글