// 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'
@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
@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();
}
}
}
}
@DistributedLock
public void createResponse(@RequestBody SurveyResponseDto surveyForm) {
surveyService.createSurveyAnswer(surveyForm);
}
Spring AOP
로 분산락을 정의해놓고 분산락이 필요한 곳에 어노테이션으로 가져다 쓰면 매우 편리하다.
분산 락(Distributed Lock)은 여러 서비스나 여러 인스턴스가 동시에 같은 리소스(예: 데이터베이스)에 접근하려 할 때 동시성 문제를 해결하기 위해 사용됩니다. 분산 락을 사용하면 한 번에 하나의 서비스만 해당 리소스를 수정할 수 있게 되어 데이터의 일관성을 보장할 수 있습니다.
각 서비스가 자체 데이터베이스를 가지고 있고, 서비스 간에 데이터 공유가 없는 경우에는 분산 락은 필요하지 않을 수 있습니다.
동일한 서비스의 여러 인스턴스가 동일한 데이터베이스에 접근하려고 할 때에는 로컬 락이나 데이터베이스의 트랜잭션 기능을 사용하여 동시성을 제어할 수 있습니다.